/*
 *	Universal Terminal Interface -- Colors and Attributes
 *
 *	(c) 1997--1999 Martin Mares, <mj@ericsson.cz>
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "termint.h"

/* Supported attributes and colors */

ulg supported_attrs;
int num_colors, num_real_colors;
byte default_color;

static byte *str_set_back, *str_set_fore;

static byte bcol_to_color[64], bcol_to_attr[64];
static byte fcol_to_color[64], fcol_to_attr[64];

byte set_attrs, set_color;
byte cls_attrs, cls_color;
byte write_attrs, write_color;

char *set_attr[8], *reset_attr[8];

/* Binary logarithm */

static inline byte
byte_binlog(byte k)
{
  return (k & 0xf0) ?
    ((k & 0xc0) ? ((k & 0x80) ? 7 : 6) : ((k & 0x20) ? 5 : 4)) :
    ((k & 0x0c) ? ((k & 0x08) ? 3 : 2) : ((k & 0x02) ? 1 : 0)) ;
}

/* Cost analysis */

#define IS_POWER_OF_TWO(x) !((x) & ((x) - 1))

int
attr_change_cost(byte oc, byte nc, byte oa, byte na, int *rem)
{
  int best, cost_toggle, cost_clear, cost_setall, alg, i;
  byte m, da;

  best = 0;
  if (oa != na)
    {
      da = oa ^ na;
      if (IS_POWER_OF_TWO(da))		/* Simple cases first (where global off might be too long) */
	{
	  byte l = byte_binlog(da);
	  best = ((na & da) ? cost_attr_set : cost_attr_reset) [l];
	  if (best < 100)
	    {
	      alg = 1;
	      goto docol;
	    }
	}

      cost_toggle = 0;
      cost_clear = cost_attr_reset_all;
      cost_setall = cost_attr_set_all;
      m = 0x40;
      for(i=6; i>=0; i--, m>>=1)
	{
	  if (da & m)
	    {
	      if (na & m)
		{
		  cost_toggle += cost_attr_set[i];
		  cost_clear += cost_attr_set[i];
		  if (m & (ATTR_ITALIC | ATTR_SHADOW))
		    cost_setall += cost_attr_set[i];
		}
	      else
		{
		  cost_toggle += cost_attr_reset[i];
		  if (m & (ATTR_ITALIC | ATTR_SHADOW))
		    cost_setall += cost_attr_reset[i];
		}
	    }
	  else if (na & m)
	    cost_clear += cost_attr_set[i];
	}
      if (BACK(nc) != BACK(default_color)) /* Unfortunately, attr clear also resets colors */
	cost_clear += cost_set_back;
      if (FORE(nc) != FORE(default_color))
	cost_clear += cost_set_fore;
      if ((da | oa) & ATTR_ALTERNATE)
	cost_clear += (na & ATTR_ALTERNATE) ? cost_attr_set[AI_ALTERNATE] : cost_attr_reset[AI_ALTERNATE];
      if (da & ATTR_ALTERNATE)
	cost_toggle += (na & ATTR_ALTERNATE) ? cost_attr_set[AI_ALTERNATE] : cost_attr_reset[AI_ALTERNATE];
      best = cost_toggle, alg = 2;
      if (cost_clear < best)
	{
	  best = cost_clear, alg = 3;
	  oc = nc;			/* Color setting cost already counted */
	}
      if (cost_setall < best)
	best = cost_setall, alg = 4;
    }
  else
    alg = 0;

docol:

  if (oc != nc)
    {
      if (BACK(oc) != BACK(nc))
	best += cost_set_back;
      if (FORE(oc) != FORE(nc))
	best += cost_set_fore;
    }

  if (rem)
    *rem = alg;
  return best;
}

/* Set colors and attributes to new value */

void
send_attrs(byte attrs, byte color)
{
  if (attrs != set_attrs)
    {
      int alg, cost, i;
      byte delta = attrs ^ set_attrs;
      byte m;

      cost = attr_change_cost(0, 0, set_attrs, attrs, &alg);
#ifdef DEBUG
      if (cost > 100)
	fatal_error("Attribute change failed: %02x -> %02x!", set_attrs, attrs);
#endif
      TR("set_attrs: %02x -> %02x, cost=%d, alg=%d\n", set_attrs, attrs, cost, alg);

      switch (alg)
	{
	case 1:				/* Short-cut */
	  i = byte_binlog(delta);
	  send(((attrs & delta) ? set_attr : reset_attr) [i]);
	  TR("\tShortcut %set of %02x\n", (attrs & delta) ? "s" : "res", delta);
	  set_attrs = attrs;
	  goto adone;
	case 2:				/* Toggling */
	  break;
	case 3:
	  send(STR(NORMAL));		/* Clearing */
	  TR("\tAttribute clear\n");
	  if ((attrs | set_attrs) & ATTR_ALTERNATE) /* Alternate might have been cleared */
	    set_attrs = ~attrs & ATTR_ALTERNATE;
	  else
	    set_attrs = 0;
	  set_color = default_color;
	  break;
	case 4:				/* Set all */
	  TR("\tUsing general attribute set\n");
	  sendp(STR(SET_ALL_ATTRS), !!(attrs & ATTR_STANDOUT), !!(attrs & ATTR_UNDERLINE),
		!!(attrs & ATTR_INVERSE), 0, !!(attrs & ATTR_DIM), !!(attrs & ATTR_BRIGHT),
		0, 0, !!(attrs & ATTR_ALTERNATE));
	  set_attrs = (set_attrs & (ATTR_ITALIC | ATTR_SHADOW))
	    | (attrs & ~(ATTR_ITALIC | ATTR_SHADOW));
	  set_color = default_color;
	  break;
	default:
	  fatal_error("send_attrs confused by alg %d", alg);
	}

      m = 0x80;
      i = 7;
      while (delta = set_attrs ^ attrs)
	{
	  if (delta & m)
	    {
	      TR("\t%setting %02x\n", (attrs & m) ? "S" : "Res", m);
	      send(((attrs & m) ? set_attr : reset_attr) [i]);
	      set_attrs ^= m;
	    }
	  m >>= 1U;
	  i--;
	}
    }

adone:
  if (color != set_color)
    {
      TR("set_color: %02x -> %02x\n", set_color, color);
      if (BACK(color) != BACK(set_color))
	sendp(str_set_back, BACK(color));
      if (FORE(color) != FORE(set_color))
	sendp(str_set_fore, FORE(color));
      set_color = color;
    }
}

void
force_attrs(byte attrs, byte color)
{
  TR("Forcing attributes and color\n");
  send(STR(NORMAL));
  send(reset_attr[AI_ALTERNATE]);
  set_attrs = 0;
  sendp(str_set_back, BACK(color));
  sendp(str_set_fore, FORE(color));
  set_color = color;
  send_attrs(attrs, color);
}

/* Setting of colors and attributes */

void
Tphysattr(ulg back, ulg fore, ulg attrs)
{
  write_color = COLOR(back, fore);
  write_attrs = attrs & supported_attrs;
}

void
Tsetattr(ulg back, ulg fore, ulg attrs)
{
  ulg a;

  if (back >= num_colors)
    back = num_colors - 1;
  if (fore >= num_colors)
    fore = num_colors - 1;
  a = bcol_to_attr[back] | fcol_to_attr[fore];
  write_color = COLOR(bcol_to_color[back], fcol_to_color[fore]);
  a |= attrs;
  a &= supported_attrs;
  write_attrs = a;
}

void
color_setup(void)
{
  if (INT(MAGIC_COOKIE_WIDTH) < 0)
    {
      num_real_colors = INT(NUM_OF_COLORS);
      if (num_real_colors < 0)
	num_real_colors = 0;
      else if (num_real_colors > 16)
	num_real_colors = 16;
      str_set_back = STR(SET_ANSI_BACKGROUND) ? : STR(SET_BACKGROUND);
      str_set_fore = STR(SET_ANSI_FOREGROUND) ? : STR(SET_FOREGROUND);
      if (BOOL(HLS_COLORS) || !str_set_back || !str_set_fore || CTRL(NO_COLORS))
	num_real_colors = 0;
      if (set_attr[AI_ITALIC] = STR(ITALIC_ON))
	{
	  supported_attrs |= ATTR_ITALIC;
	  reset_attr[AI_ITALIC] = STR(ITALIC_OFF);
	}
      if (set_attr[AI_SHADOW] = STR(SHADOW_ON))
	{
	  supported_attrs |= ATTR_SHADOW;
	  reset_attr[AI_SHADOW] = STR(SHADOW_OFF);
	}
      num_colors = num_real_colors;
      if (set_attr[AI_BRIGHT] = STR(BRIGHT))
	supported_attrs |= ATTR_BRIGHT;
      if (set_attr[AI_DIM] = STR(HALFBRIGHT))
	supported_attrs |= ATTR_DIM;
      if (!num_real_colors)
	{
	  if (set_attr[AI_STANDOUT] = STR(STANDOUT_ON))
	    {
	      supported_attrs |= ATTR_STANDOUT;
	      reset_attr[AI_STANDOUT] = STR(STANDOUT_OFF);
	    }
	  if (set_attr[AI_INVERSE] = STR(INVERSE))
	    supported_attrs |= ATTR_INVERSE;
	}
    }
  if (STR(ALTERNATE))
    supported_attrs |= ATTR_ALTERNATE;
  if (INT(UNDERLINE_COOKIE_WIDTH) < 0)
    {
      if (set_attr[AI_UNDERLINE] = STR(UNDERLINE_ON))
	{
	  supported_attrs |= ATTR_UNDERLINE;
	  reset_attr[AI_UNDERLINE] = STR(UNDERLINE_OFF);
	}
    }
  if (HAVE(COLORS_DISABLE_ATTRS) && num_colors)
    {
      ulg k = INT(COLORS_DISABLE_ATTRS);
      if (k & 2)
	supported_attrs &= ~ATTR_UNDERLINE;
      if (k & 16)
	supported_attrs &= ~ATTR_DIM;
      if (k & 32)
	supported_attrs &= ~ATTR_BRIGHT;
      if (k & 256)
	supported_attrs &= ~ATTR_ALTERNATE;
    }
      TR("CCCC %d %d\n", num_real_colors, num_colors);
  if (num_colors)			/* Have real coloring */
    {
      int i,j;
      i = 0;
      default_color = COLOR(0, num_real_colors-1);
      for(j=0; j<num_real_colors; j++)
	{
	  fcol_to_attr[i] = 0;
	  fcol_to_color[i++] = j;
	}
      if (supported_attrs & ATTR_BRIGHT)
	for(j=0; j<num_real_colors; j++)
	  {
	    fcol_to_attr[i] = ATTR_BRIGHT;
	    fcol_to_color[i++] = j;
	  }
      if (supported_attrs & ATTR_DIM)
	for(j=0; j<num_real_colors; j++)
	  {
	    fcol_to_attr[i] = ATTR_DIM;
	    fcol_to_color[i++] = j;
	  }
      num_colors = i;
      memcpy(bcol_to_attr, fcol_to_attr, num_colors);
      memcpy(bcol_to_color, fcol_to_color, num_colors);
    }
  else					/* Imaginary colors */
    {
      fcol_to_attr[0] = supported_attrs & ATTR_INVERSE;
      fcol_to_attr[1] = supported_attrs & ATTR_INVERSE;
      fcol_to_attr[2] = supported_attrs & ATTR_INVERSE;
      fcol_to_attr[3] = supported_attrs & ATTR_INVERSE;
      fcol_to_attr[4] = 0;
      fcol_to_attr[5] = supported_attrs & ATTR_DIM;
      fcol_to_attr[6] = 0;
      fcol_to_attr[7] = supported_attrs & ATTR_BRIGHT;
      bcol_to_attr[0] = 0;
      bcol_to_attr[1] = supported_attrs & ATTR_DIM;
      bcol_to_attr[2] = 0;
      bcol_to_attr[3] = supported_attrs & ATTR_BRIGHT;
      bcol_to_attr[4] = supported_attrs & ATTR_INVERSE;
      bcol_to_attr[5] = supported_attrs & ATTR_INVERSE;
      bcol_to_attr[6] = supported_attrs & ATTR_INVERSE;
      bcol_to_attr[7] = supported_attrs & ATTR_INVERSE;
      num_colors = 8;
    }
  DBG(("attrs: %x, real_colors: %d, imag_colors: %d, def_color=%02x\n",
       supported_attrs, num_real_colors, num_colors, default_color));
  set_color = write_color = default_color;
}
