/*
*Copyright(c)2016, Jeffrey Lee
*Allrightsreserved.
*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdio.h>
#include <string.h>
#include <oslib/os.h>

#include "cursor.h"
#include "pixtrans.h"
#include "proto.h"

typedef struct {
  uint16_t x;
  uint16_t y;
  uint16_t w;
  uint16_t h;
  uint32_t enc;
} cursor_rect;

bool cursor_supported(vncserv *serv)
{
  return serv && (serv->encodings.cursor || serv->encodings.xcursor) && serv->pointer_active;
}

bool cursor_will_send(vncserv *serv, const pointer_shape *shape)
{
  int w = shape->w*4;
  int h = shape->h;
  const char *data = shape->data;
  if((w < 1) || (w > SERVER_CURSOR_MAX_WIDTH) || (h < 1) || (h > SERVER_CURSOR_MAX_HEIGHT) || (!data) || !cursor_supported(serv))
  {
    return false;
  }
  return true;
}

bool cursor_send(vncserv *serv, const pointer_shape *shape, cursor_temp_buffer *temp)
{
  int w = shape->w*4;
  int h = shape->h;
  int x = shape->x;
  int y = shape->y;
  const char *data = shape->data;

  /* Basic buffer space check */
  if (vncserv_get_tx_space(serv) < CURSOR_MAX_PACKET)
  {
    return false;
  }

  cursor_rect rect;
  rect.x = Swap16IfLE(x);
  rect.y = Swap16IfLE(y);
  rect.w = Swap16IfLE(w);
  rect.h = Swap16IfLE(h);


  bool use_xcursor = false;
  int xcursor_fg_idx = 1;
  int xcursor_bg_idx = 2;
  if (serv->encodings.xcursor && (serv->config.prefer_xcursor != -1))
  {
    if (serv->config.prefer_xcursor || !serv->encodings.cursor)
    {
      use_xcursor = true;
    }
    else
    {
      /* Examine the image and see if only two colours are in use
         If so, use xcursor encoding (smaller packet size, more accurate colours) */
      uint32_t seen_1 = 0;
      uint32_t seen_2 = 0;
      uint32_t seen_3 = 0;
      for(int iy=0;iy<h;iy++)
        for(int ix=0;ix<w;ix+=4)
        {
          uint32_t c = data[(ix+iy*w)/4];
          seen_1 |= (c & ((~c)>>1)); /* Low bit of each pixel set if it's colour 1 */
          seen_2 |= ((~c) & (c>>1)); /* Low bit of each pixel set if it's colour 2 */
          seen_3 |= (c & (c>>1)); /* Low bit of each pixel set if it's colour 3 */
        }
      if (!(seen_1 & 0x55))
      {
        use_xcursor = true;
        xcursor_fg_idx = 3;
      }
      else if (!(seen_2 & 0x55))
      {
        use_xcursor = true;
        xcursor_bg_idx = 3;
      }
      else if (!(seen_3 & 0x55))
      {
        use_xcursor = true;
      }
    }
  }


  if (!use_xcursor)
  {
    /* Set cursor using Cursor encoding */
    rect.enc = Swap32IfLE(CURSOR_ENCODING);
    vncserv_write(serv, &rect, sizeof(rect));

    /* Copy the data into two buffers */
    char *col,*mask;
    col = temp->cursor.col;
    mask = temp->cursor.mask;
    memset(col,0,w*h*4);
    memset(mask,0,((w+7)/8)*h);
    uint32_t cols[4];
    /* Compute colours */
    for(int i=1;i<4;i++)
    {
      if ((os_state.myscreen.format.depth < 8) && !serv->clientformat.true_colour_flag && (serv->clientformat.depth > os_state.myscreen.format.depth))
      {
        /* Pointer colours are packed after end of main palette */
        cols[i] = (1<<os_state.myscreen.format.depth)+i-1;
      }
      else
      {
        xos_read_palette(i,25,&cols[i],NULL);
        cols[i] = pixtrans_do_8888(serv,cols[i]);
      }
    }
    /* Convert image */
    for(int iy=0;iy<h;iy++)
      for(int ix=0;ix<w;ix++)
      {
        uint32_t c = data[(ix+iy*w)/4];
        c = (c >> ((ix & 3)*2)) & 3;
        if(c != 0)
        {
          /* Set colour stuff */
          uint32_t cc = cols[c];
          int ofs = (ix+iy*w)*(serv->clientformat.bits_per_pixel>>3);
          int count = serv->clientformat.bits_per_pixel;
          while (count > 0)
          {
            col[ofs++] = cc;
            cc >>= 8;
            count -= 8;
          }
          /* Set mask stuff */
          c = 128 >> (ix & 7);
          mask[(ix+iy*w)/8] |= c;
        }
      }
    vncserv_write(serv, col, w*h*serv->clientformat.bits_per_pixel>>3);
    vncserv_write(serv, mask, ((w+7)/8)*h);
  }
  else
  {
    /* Set cursor using XCursor encoding */
    rect.enc = Swap32IfLE(XCURSOR_ENCODING);
    vncserv_write(serv, &rect, sizeof(rect));
    
    char *col,*mask;
    col = temp->xcursor.col;
    mask = temp->xcursor.mask;
    memset(col,0,((w+7)/8)*h);
    memset(mask,0,((w+7)/8)*h);
    uint32_t fg,bg;
    xos_read_palette(xcursor_fg_idx,25,&fg,NULL);
    xos_read_palette(xcursor_bg_idx,25,&bg,NULL);
    /* Convert image */
    for(int iy=0;iy<h;iy++)
      for(int ix=0;ix<w;ix++)
      {
        uint32_t c = data[(ix+iy*w)/4];
        c = (c >> ((ix & 3)*2)) & 3;
        if(c != 0)
        {
          int bit = 128 >> (ix & 7);
          int ofs = (ix+iy*w)/8;
          if (c == xcursor_fg_idx)
            col[ofs] |= bit;
          mask[ofs] |= bit;
        }
      }
    vncserv_write(serv, ((uint8_t*) &fg)+1, 3);
    vncserv_write(serv, ((uint8_t*) &bg)+1, 3);
    vncserv_write(serv, col, ((w+7)/8)*h);
    vncserv_write(serv, mask, ((w+7)/8)*h);
  }

  return true;
}
