/*
 * hextile.c
 *
 * Routines to implement Hextile Encoding
 */

/*
 *  OSXvnc Copyright (C) 2001 Dan McGuirk <mcguirk@incompleteness.net>.
 *  Original Xvnc code Copyright (C) 1999 AT&T Laboratories Cambridge.
 *  All Rights Reserved.
 *
 *  This is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 *  USA.
 */

/* the hextile encoder is this file works just like the original  I've just
 * tidied the code a bit and renamed a lot of things to integrate it with
 * the vnc server
 */

#include <stdio.h>

#include "vncserv.h"
#include "proto.h"
#include "vncbuffer.h"
#include "pixtrans.h"


static void send_hextile16(vncserv *serv, int x, int y, int w, int h);
static void test_colours16(CARD16 *data, int size, int *mono, int *solid, CARD16 *bg, CARD16 *fg);
static int subrect_encode16(CARD16 *data, int w, int h, CARD16 bg, CARD16 fg, int mono);

static void send_hextile8(vncserv *serv, int x, int y, int w, int h);
static void test_colours8(CARD8 *data, int size, int *mono, int *solid, CARD8 *bg, CARD8 *fg);
static int subrect_encode8(CARD8 *data, int w, int h, CARD8 bg, CARD8 fg, int mono);

#define HEXTILE_RAW                     (1 << 0)
#define HEXTILE_BACKGROUNDSPECIFIED     (1 << 1)
#define HEXTILE_FOREGROUNDSPECIFIED     (1 << 2)
#define HEXTILE_ANYSUBRECTS             (1 << 3)
#define HEXTILE_SUBRECTSCOLOURED        (1 << 4)


/*
 *
 */


int hextile_encode_and_send(vncserv *serv, int x, int y, int w, int h) {

  struct {
    CARD8 type;
    CARD8 pad1;
    CARD16 n;
  } data;
  struct {
    CARD16 x;
    CARD16 y;
    CARD16 w;
    CARD16 h;
    CARD32 encoding;
  } rect;

  vncbuffer_reset();

  data.type = 0;
  data.pad1 = 0;
  data.n    = Swap16IfLE(1);
  vncbuffer_write(&data, sizeof(data), serv);

  rect.x = Swap16IfLE(x);
  rect.y = Swap16IfLE(y);
  rect.w = Swap16IfLE(w);
  rect.h = Swap16IfLE(h);
  rect.encoding = Swap32IfLE(HEXTILE_ENCODING);
  vncbuffer_write(&rect, sizeof(rect), serv);

  switch (serv->clientbpp) {
  case 8:
    send_hextile8(serv, x, y, w, h);
    vncbuffer_flush(serv);
    return 1;
    break;
  case 16:
    send_hextile16(serv, x, y, w, h);
    vncbuffer_flush(serv);
    return 1;
    break;
  }

//  fprintf(stderr, "Hextile encoding : bpp %d?  (%08x)\n", serv->clientbpp, (int)serv);
  return 1;
}



static char output[16*16*8];  // large enough to hold any hextile coded 16x16 block
static int len;


/*
 * rfbSendHextiles 16 bpp
 */

static void send_hextile16(vncserv *serv, int rx, int ry, int rw, int rh) {
  int x, y, w, h;
  CARD16 bg = 0, fg = 0, newbg, newfg;
  int mono, solid;
  int validbg = 0;
  int validfg = 0;
  CARD16 clientblock[16*16*2];

  for (y = ry; y < ry+rh; y += 16) {
    for (x = rx; x < rx+rw; x += 16) {

      w = h = 16;
      if (rx+rw - x < 16)     w = rx+rw - x;
      if (ry+rh - y < 16)     h = ry+rh - y;

      pixtrans_do_it(serv, x, y, w, h, clientblock);

      // reset buffer at the start of each 16x16 block, write at the end
      output[0] = 0;
      len = 1;

      test_colours16(clientblock, w*h, &mono, &solid, &newbg, &newfg);

      if (!validbg || (newbg != bg)) {
        validbg = 1;
        bg = newbg;
        output[0] |= HEXTILE_BACKGROUNDSPECIFIED;
        output[len++] = ((char*)&(bg))[0];
        output[len++] = ((char*)&(bg))[1];
      }

      if (solid) {
        vncbuffer_write(output, len, serv);
        continue;
      }

      output[0] |= HEXTILE_ANYSUBRECTS;

      if (mono) {
        if (!validfg || (newfg != fg)) {
          validfg = 1;
          fg = newfg;
          output[0] |= HEXTILE_FOREGROUNDSPECIFIED;
          output[len++] = ((char*)&(fg))[0];
          output[len++] = ((char*)&(fg))[1];
        }
      } else {
        validfg = 0;
        output[0] |= HEXTILE_SUBRECTSCOLOURED;
      }

      if (!subrect_encode16(clientblock, w, h, bg, fg, mono)) {
        // encoding was too large, use raw
        validbg = validfg = 0;
        output[0] = HEXTILE_RAW;
        pixtrans_do_it(serv, x, y, w, h, output+1);
        len = 2*w*h + 1;
      }

      vncbuffer_write(output, len, serv);
    }
  }
}


static int subrect_encode16(CARD16 *data, int w, int h, CARD16 bg, CARD16 fg, int mono) {
  CARD16 cl2;
  int x, y;
  int i, j;
  int hx = 0, hy, vx = 0, vy;
  int hyflag;
  CARD16 *seg;
  CARD16 *line;
  int hw, hh, vw, vh;
  int thex, they, thew, theh;
  int numsubs = 0;
  int numsubrectslen;

  numsubrectslen = len;
  len++;

  for (y = 0; y < h; y++) {
    line = data + y*w;
    for (x = 0; x < w; x++) {
      if (line[x] != bg) {
        cl2 = line[x];
        hy = y-1;
        hyflag = 1;
        for (j = y; j < h; j++) {
          seg = data+(j*w);
          if (seg[x] != cl2)   break;
          i = x;
          while ((seg[i] == cl2) && (i < w)) i += 1;
          i -= 1;
          if (j == y) vx = hx = i;
          if (i < vx) vx = i;
          if ((hyflag > 0) && (i >= hx))
            hy += 1;
          else
            hyflag = 0;
        }
        vy = j-1;

        /* We now have two possible subrects: (x,y,hx,hy) and
         * (x,y,vx,vy).  We'll choose the bigger of the two.
         */
        hw = hx-x+1;
        hh = hy-y+1;
        vw = vx-x+1;
        vh = vy-y+1;

        thex = x;
        they = y;

        if ((hw*hh) > (vw*vh)) {
          thew = hw;
          theh = hh;
        } else {
          thew = vw;
          theh = vh;
        }

        if (len - numsubrectslen + 4 > 2*w*h)      return 0;

        numsubs += 1;

        if (!mono) {
          output[len++] = ((char*)&(cl2))[0];
          output[len++] = ((char*)&(cl2))[1];
        }

        output[len++] = (thex<<4)     | they;
        output[len++] = ((thew-1)<<4) | (theh-1);

        /*
         * Now mark the subrect as done.
         */
        for (j = they; j < (they+theh); j++)
          for (i = thex; i < (thex+thew); i++)
            data[j*w+i] = bg;
      }
    }
  }
  output[numsubrectslen] = numsubs;

  return 1;
}


/*
 * test_colours() tests if there are one (solid), two (mono) or more
 * colours in a tile and gets a reasonable guess at the best background
 * pixel, and the foreground pixel for mono.
 */

static void test_colours16(CARD16 *data, int size, int *mono, int *solid, CARD16 *bg, CARD16 *fg) {
  CARD16 colour1 = 0, colour2 = 0;
  int n1 = 0, n2 = 0;

  *mono = 1;
  *solid = 1;

  for (; size > 0; size--, data++) {

    if (n1 == 0)
      colour1 = *data;

    if (*data == colour1) {
      n1++;
      continue;
    }

    if (n2 == 0) {
      *solid = 0;
      colour2 = *data;
    }

    if (*data == colour2) {
      n2++;
      continue;
    }

    *mono = 0;
    break;
  }

  if (n1 > n2) {
    *bg = colour1;
    *fg = colour2;
  } else {
    *bg = colour2;
    *fg = colour1;
  }
}



/*
 * rfbSendHextiles 8 bpp
 */

static void send_hextile8(vncserv *serv, int rx, int ry, int rw, int rh) {
  int x, y, w, h;
  CARD8 bg = 0, fg = 0, newbg, newfg;
  int mono, solid;
  int validbg = 0;
  int validfg = 0;
  CARD8 clientblock[16*16*2];

  for (y = ry; y < ry+rh; y += 16) {
    for (x = rx; x < rx+rw; x += 16) {

      w = h = 16;
      if (rx+rw - x < 16)     w = rx+rw - x;
      if (ry+rh - y < 16)     h = ry+rh - y;

      pixtrans_do_it(serv, x, y, w, h, clientblock);

      // reset buffer at the start of each 16x16 block, write at the end
      output[0] = 0;
      len = 1;

      test_colours8(clientblock, w*h, &mono, &solid, &newbg, &newfg);

      if (!validbg || (newbg != bg)) {
        validbg = 1;
        bg = newbg;
        output[0] |= HEXTILE_BACKGROUNDSPECIFIED;
        output[len++] = bg;
      }

      if (solid) {
        vncbuffer_write(output, len, serv);
        continue;
      }

      output[0] |= HEXTILE_ANYSUBRECTS;

      if (mono) {
        if (!validfg || (newfg != fg)) {
          validfg = 1;
          fg = newfg;
          output[0] |= HEXTILE_FOREGROUNDSPECIFIED;
          output[len++] = fg;
        }
      } else {
        validfg = 0;
        output[0] |= HEXTILE_SUBRECTSCOLOURED;
      }

      if (!subrect_encode8(clientblock, w, h, bg, fg, mono)) {
        /* encoding was too large, use raw */
        validbg = validfg = 0;
        output[0] = HEXTILE_RAW;
        pixtrans_do_it(serv, x, y, w, h, output+1);
        len = w*h + 1;
      }

      vncbuffer_write(output, len, serv);
    }
  }
}


static int subrect_encode8(CARD8 *data, int w, int h, CARD8 bg, CARD8 fg, int mono) {
  CARD8 cl2;
  int x, y;
  int i, j;
  int hx = 0, hy, vx = 0, vy;
  int hyflag;
  CARD8 *seg;
  CARD8 *line;
  int hw, hh, vw, vh;
  int thex, they, thew, theh;
  int numsubs = 0;
  int numsubrectslen;

  numsubrectslen = len;
  len++;

  for (y = 0; y < h; y++) {
    line = data + y*w;
    for (x = 0; x < w; x++) {
      if (line[x] != bg) {
        cl2 = line[x];
        hy = y-1;
        hyflag = 1;
        for (j = y; j < h; j++) {
          seg = data+(j*w);
          if (seg[x] != cl2)   break;
          i = x;
          while ((seg[i] == cl2) && (i < w)) i += 1;
          i -= 1;
          if (j == y) vx = hx = i;
          if (i < vx) vx = i;
          if ((hyflag > 0) && (i >= hx))
            hy += 1;
          else
            hyflag = 0;
        }
        vy = j-1;

        /* We now have two possible subrects: (x,y,hx,hy) and
         * (x,y,vx,vy).  We'll choose the bigger of the two.
         */
        hw = hx-x+1;
        hh = hy-y+1;
        vw = vx-x+1;
        vh = vy-y+1;

        thex = x;
        they = y;

        if ((hw*hh) > (vw*vh)) {
          thew = hw;
          theh = hh;
        } else {
          thew = vw;
          theh = vh;
        }

        if (len - numsubrectslen + 2 > w*h)      return 0;

        numsubs += 1;

        if (!mono)      output[len++] = cl2;

        output[len++] = (thex<<4)     | they;
        output[len++] = ((thew-1)<<4) | (theh-1);

        /*
         * Now mark the subrect as done.
         */
        for (j = they; j < (they+theh); j++)
          for (i = thex; i < (thex+thew); i++)
            data[j*w+i] = bg;
      }
    }
  }
  output[numsubrectslen] = numsubs;

  return 1;
}


/*
 * test_colours() tests if there are one (solid), two (mono) or more
 * colours in a tile and gets a reasonable guess at the best background
 * pixel, and the foreground pixel for mono.
 */

static void test_colours8(CARD8 *data, int size, int *mono, int *solid, CARD8 *bg, CARD8 *fg) {
  CARD8 colour1 = 0, colour2 = 0;
  int n1 = 0, n2 = 0;

  *mono = 1;
  *solid = 1;

  for (; size > 0; size--, data++) {

    if (n1 == 0)
      colour1 = *data;

    if (*data == colour1) {
      n1++;
      continue;
    }

    if (n2 == 0) {
      *solid = 0;
      colour2 = *data;
    }

    if (*data == colour2) {
      n2++;
      continue;
    }

    *mono = 0;
    break;
  }

  if (n1 > n2) {
    *bg = colour1;
    *fg = colour2;
  } else {
    *bg = colour2;
    *fg = colour1;
  }
}
