#include "aconfig.h"
#ifdef AA_DRIVER
#include <string.h>
#include <malloc.h>
#include <aalib.h>
#include <ui.h>
#include <unistd.h>
#include <xmenu.h>
#include <archaccel.h>
struct ui_driver aalib_driver;
static aa_palette palette;
static aa_palette aapalette;
static int useaapalette;
#define c context
static aa_context *c;
static unsigned char *secondary;
static int mouse;
static int nomouse;
static void aa_build_menus (void);
static void aa_remove_menus (void);
extern unsigned char *aa_chardata;
extern unsigned char *aa_colordata;
extern int aa_cursorx, aa_cursory;



static void
aa_print (int x, int y, CONST char *text)
{
  aa_puts (c, x / 2, y / 2, AA_SPECIAL, text);
}

static void
aa_display (void)
{
  int i;
  aa_renderpalette (c, useaapalette ? aapalette : palette,
		    &aa_defrenderparams, 0, 0, aa_scrwidth (c),
		    aa_scrheight (c));
  for (i = 0; i < aa_scrwidth (c) * aa_scrheight (c); i++)
    {
      if (aa_colordata[i] != 255)
	aa_text (c)[i] = aa_chardata[i], aa_attrs (c)[i] = aa_colordata[i];
    }
}
static void
aa_set_palette (ui_palette pal, int start, int end)
{
  int i;
  for (i = start; i <= end; i++)
    aa_setpalette (palette, i, pal[i - start][0], pal[i - start][1],
		   pal[i - start][2]);
  aa_display ();
  aa_flush (c);
}

static void
aa_flip_buffers (void)
{
  unsigned char *tmp;
  tmp = secondary;
  secondary = c->imagebuffer;
  c->imagebuffer = tmp;
}

static void
aa_free_buffers (char *b1, char *b2)
{
  free (aa_chardata);
  free (aa_colordata);
  free (secondary);
}

static int
aa_alloc_buffers (char **b1, char **b2)
{
  secondary = malloc (aa_imgwidth (c) * aa_imgheight (c));
  *(unsigned char **) b2 = secondary;
  *(unsigned char **) b1 = c->imagebuffer;
  aa_chardata = malloc (aa_scrwidth (c) * aa_scrheight (c));
  aa_colordata = malloc (aa_scrwidth (c) * aa_scrheight (c));
  return aa_imgwidth (c);	/* bytes per scanline */
}

static void
aa_getsize (int *w, int *h)
{
  aa_resize (c);
  *w = aa_imgwidth (c);
  *h = aa_imgheight (c);
}

static int mousex, mousey, mouseb;
static void
aa_processevents (int wait, int *mx, int *my, int *mb, int *k)
{
  int ch;
  static int keys;
  do
    {
      if ((ch = aa_getevent (c, wait)) != AA_NONE && ch != AA_MOUSE
	  && ch != AA_RESIZE && ch < 256)
	ui_key (ch);
      switch (ch)
	{
	case AA_BACKSPACE:
	  ui_key (UIKEY_BACKSPACE);
	  break;
	case AA_ESC:
	  ui_key (UIKEY_ESC);
	  break;
	case AA_RESIZE:
	  ui_resize ();
	  break;
	case AA_MOUSE:
	  aa_getmouse (c, &mousex, &mousey, &mouseb);
	  break;
	case AA_LEFT:
	  ui_key (UIKEY_LEFT);
	  if (c->kbddriver->flags & AA_SENDRELEASE)
	    keys |= 1;
	  break;
	case AA_LEFT | AA_RELEASE:
	  keys &= ~1;
	  break;
	case AA_RIGHT:
	  ui_key (UIKEY_RIGHT);
	  if (c->kbddriver->flags & AA_SENDRELEASE)
	    keys |= 2;
	  break;
	case AA_RIGHT | AA_RELEASE:
	  keys &= ~2;
	  break;
	case AA_UP:
	  ui_key (UIKEY_UP);
	  if (c->kbddriver->flags & AA_SENDRELEASE)
	    keys |= 4;
	  break;
	case AA_UP | AA_RELEASE:
	  keys &= ~4;
	  break;
	case AA_DOWN:
	  ui_key (UIKEY_DOWN);
	  if (c->kbddriver->flags & AA_SENDRELEASE)
	    keys |= 8;
	  break;
	case AA_DOWN | AA_RELEASE:
	  keys &= ~8;
	  break;
	}
      wait = 0;
    }
  while (ch != AA_NONE);
  *mx = mousex * 2;
  *my = mousey * 2;
  *k = keys;
  *mb = 0;
  if (mouseb & AA_BUTTON1)
    *mb |= BUTTON1;
  if (mouseb & AA_BUTTON2)
    *mb |= BUTTON2;
  if (mouseb & AA_BUTTON3)
    *mb |= BUTTON3;
  return;
}

#define NATTRS 7
static char *aadriver = NULL;
static char *kbddriver = NULL;
static char *mousedriver = NULL;
static char *deffont = NULL;
static int width, height, minwidth, minheight, maxwidth, maxheight, recwidth,
  recheight;
static int enable[NATTRS + 2], disable[NATTRS + 2];
static int extended, inverse, bright = 0, contrast = 0;
static float aa_gamma, dimmul, boldmul;
static int dithering[3];
static int randomval = 0;
static int masks[] =
  { AA_NORMAL_MASK, AA_DIM_MASK, AA_BOLD_MASK, AA_BOLDFONT_MASK,
  AA_REVERSE_MASK, AA_ALL, AA_EIGHT
};

#ifdef DESTICKY
extern int euid, egid;
#endif
static int
aa_initialize (void)
{
  int i, y;
  aa_parseoptions (NULL, NULL, NULL, NULL);	/*parse environment first */

  if (deffont != NULL)
    {
      for (y = 0; aa_fonts[y] != NULL; y++)
	{
	  if (!strcmp (deffont, aa_fonts[y]->name)
	      || !strcmp (deffont, aa_fonts[y]->shortname))
	    {
	      aa_defparams.font = aa_fonts[y];
	      break;
	    }
	}
    }

  if (extended)
    aa_defparams.supported |= AA_EXTENDED;

  for (i = 0; i < NATTRS; i++)
    {
      if (enable[i])
	aa_defparams.supported |= masks[i];
      if (disable[i])
	aa_defparams.supported &= ~masks[i];
    }

  for (i = 0; i < 3; i++)
    if (dithering[i])
      aa_defrenderparams.dither = i;
  if (randomval)
    aa_defrenderparams.randomval = randomval;
  if (bright)
    aa_defrenderparams.bright = bright;
  if (contrast)
    aa_defrenderparams.contrast = contrast;
  if (aa_gamma)
    aa_defrenderparams.gamma = aa_gamma;

  if (width)
    aa_defparams.width = width;
  if (height)
    aa_defparams.height = height;
  if (minwidth)
    aa_defparams.minwidth = minwidth;
  if (minheight)
    aa_defparams.minheight = minheight;
  if (maxwidth)
    aa_defparams.maxwidth = maxwidth;
  if (maxheight)
    aa_defparams.maxheight = maxheight;
  if (recwidth)
    aa_defparams.recwidth = recwidth;
  if (recheight)
    aa_defparams.recheight = recheight;

  if (aadriver != NULL)
    aa_recommendhidisplay (aadriver);
  if (kbddriver != NULL)
    aa_recommendhikbd (kbddriver);
  if (mousedriver != NULL)
    aa_recommendhimouse (mousedriver);
  if (dimmul)
    aa_defparams.dimmul = dimmul;
  if (boldmul)
    aa_defparams.boldmul = boldmul;
  if (inverse)
    aa_defrenderparams.inversion = 1;

#ifdef DESTICKY
  seteuid (euid);		/* We need supervisor rights to open mouse. */
  setegid (egid);
#endif
  c = aa_autoinit (&aa_defparams);
#ifdef DESTICKY
  seteuid (getuid ());		/* Don't need supervisor rights anymore. */
  setegid (getgid ());
#endif

  if (c == NULL)
    return 0;
  aa_autoinitkbd (c, AA_SENDRELEASE);
  if (!nomouse)
    mouse = aa_autoinitmouse (c, AA_MOUSEALLMASK);

  aalib_driver.width = aa_mmwidth (c) / 10.0;
  aalib_driver.height = aa_mmheight (c) / 10.0;
  aa_build_menus ();
  return (1);
}

static void
aa_uninitialise (void)
{
  aa_close (c);
  aa_remove_menus ();
}

static void
aa_get_mouse (int *x, int *y, int *b)
{
  if (mouse)
    aa_getmouse (c, &mousex, &mousey, &mouseb);
  *x = mousex * 2;
  *y = mousey * 2;
  *b = 0;
  if (mouseb & AA_BUTTON1)
    *b |= BUTTON1;
  if (mouseb & AA_BUTTON2)
    *b |= BUTTON2;
  if (mouseb & AA_BUTTON3)
    *b |= BUTTON3;
}

static void
aa_mousetype (int type)
{
#if (AA_LIB_VERSION>1||AA_LIB_MINNOR>0)
  if (type == REPLAYMOUSE)
    aa_hidemouse (c);
  else
    aa_showmouse (c);
#endif
}


#if AA_LIB_VERSION==1 && AA_LIB_MINNOR==0
#define SUPPORTED c->driver->params.supported
#else
#define SUPPORTED c->driverparams.supported
#endif


static int driver;
static int font;
static int mask;

static void
aa_save5 (void)
{
  struct aa_hardware_params p = aa_defparams;
  struct aa_context *sc;
  struct aa_savedata data;
  int i;
  char *name;
  char extension[100];
  if (aa_formats[driver]->flags & AA_USE_PAGES)
    strcpy (extension, "_0_0");
  else
    extension[0] = 0;
  strcat (extension, aa_formats[driver]->extension);

#ifdef DJGPP
  name = ui_getfile ("fr", extension);
  /*fit into ugly 8char. limit */
#else
  name = ui_getfile ("fract", extension);
#endif

  if (name == NULL)
    {
      aa_print (0, 0, "Save driver initialization failed");
      aa_flush (c);
      sleep (3);
      return;
    }
  for (i = 0; name[i] != '.' && name[i] != '_'; i++);
  name[i] = 0;
  strcat (name, "%c%e");

  p.minwidth = p.maxwidth = 0;
  p.minheight = p.maxheight = 0;
  p.width = aa_scrwidth (c);
  p.height = aa_scrheight (c);
  p.supported = mask;
  if (aa_formats[driver]->font == NULL)
    p.font = aa_fonts[font];

  data.format = aa_formats[driver];
  data.name = name;
  sc = aa_init (&save_d, &p, &data);
  if (sc == NULL)
    {
      aa_print (0, 0, "Save driver initialization failed");
      aa_flush (c);
      sleep (3);
      return;
    }
  memcpy (sc->imagebuffer, c->imagebuffer,
	  (aa_imgwidth (c) - 1) * aa_imgheight (c));
  aa_renderpalette (sc, useaapalette ? aapalette : palette,
		    &aa_defrenderparams, 0, 0, aa_scrwidth (c),
		    aa_scrheight (c));
  aa_flush (sc);
  aa_close (sc);
}

static void
aa_swinversion (struct uih_context *c)
{
  aa_defrenderparams.inversion ^= 1;
}
static int
aa_inversion (struct uih_context *c)
{
  return (aa_defrenderparams.inversion);
}
static int aacurpalette = 0;
static void
aa_setXpalette (struct uih_context *c, int m)
{
  int i, s;
  if (!m)
    {
      useaapalette = 0;
      aa_display ();
      return;
    }
  useaapalette = 1;
  aacurpalette = m;
  s = 1 << m;
  s--;
  for (i = 0; i < 255; i++)
    {
      aapalette[i] = (i & (s)) * 255 / s;
    }
}
static int
aa_getpalette (struct uih_context *c, int m)
{
  if (!useaapalette)
    return (!m);
  return (m == aacurpalette);
}
static void
aa_dither (struct uih_context *c, int m)
{
  aa_defrenderparams.dither = m;
}
static int
aa_getdither (struct uih_context *c, int mode)
{
  return ((int) aa_defrenderparams.dither == (int) mode);
}


static void
aa_sfont (struct uih_context *co, int i)
{
  aa_setfont (c, aa_fonts[i]);
}
CONST static char *CONST name[] = {
  "normal characters     ",
  "half bright(dim)      ",
  "double bright(bold)   ",
  "bold font             ",
  "reversed              ",
  "reserved characters   ",
  "non ascii characters  ",
  "leave menu",
};
CONST static char *CONST ttext[] = {
  "Use XaoS palette",
  "Black and white stripes",
  "4 gray palette",
  "16 gray palette",
  "32 gray palette",
  "64 gray palette",
  "128 gray palette",
  "256 gray palette"
};
static CONST int attribs[] = {
  AA_NORMAL_MASK,
  AA_DIM_MASK,
  AA_BOLD_MASK,
  AA_BOLDFONT_MASK,
  AA_REVERSE_MASK,
  AA_ALL,
  AA_EIGHT,
  0
};

#define MYNATTRS (sizeof(name)/sizeof(char *))
static void
aa_swattr (struct uih_context *co, int m)
{
  int mask;
  if (m < (int) MYNATTRS - 1)
    {
      mask = c->params.supported;
      mask ^= attribs[m];
      aa_setsupported (c, mask);
      ui_menu ("aa_attr");
    }
}
static int
aa_getattr (struct uih_context *co, int m)
{
  return (c->params.supported & attribs[m]);
}
static int
aa_getsave3 (struct uih_context *c, int m)
{
  return (mask & attribs[m]);
}
static void
aa_save3 (struct uih_context *co, int m)
{
  if (m < (int) MYNATTRS - 1)
    {
      mask ^= attribs[m];
      mask &= aa_formats[driver]->supported;
      ui_menu ("aa_save3");
    }
  else
    aa_save5 ();
}
static void
aa_save2 (struct uih_context *cc, int m)
{
  aa_display ();
  font = m;
  mask = aa_formats[driver]->supported & c->params.supported;
  if (!mask)
    mask = aa_formats[driver]->supported;
  ui_menu ("aa_save3");
}
static void
aa_save (struct uih_context *c, int m)
{
  driver = m;
  if (aa_formats[m]->font != NULL)
    aa_save3 (NULL, 0);
  else
    ui_menu ("aa_save2");
}

#define UI (MENUFLAG_NOOPTION|MENUFLAG_NOPLAY)
static CONST menuitem menuitems[] = {
  SUBMENU ("file", NULL, "Save as text file", "aa_format"),
  SUBMENU ("ui", NULL, "Attributes", "aa_attr"),
  SUBMENU ("ui", NULL, "Font", "aa_font"),
  MENUNOPCB ("ui", NULL, "Inversion", "aainversion", UI, aa_swinversion,
	     aa_inversion),
  SUBMENU ("ui", NULL, "Dithering mode", "aa_dithering"),
  SUBMENU ("", NULL, "Font for saved file", "aa_save2"),
  SUBMENU ("", NULL, "Save attributes", "aa_save3"),
  SUBMENU ("palettemenu", NULL, "Text palette", "aa_palette"),
};

static menuitem *fontmenus;
static menuitem *fontmenus2;
static menuitem *formatmenus;
static int nfonts, nformats;
static void
aa_build_menus ()
{
  int i;
  menu_add (menuitems, NITEMS (menuitems));
  menu_genernumbered (sizeof (ttext) / sizeof (char *), "aa_palette", ttext,
		      NULL, MENU_INT, MENUFLAG_RADIO, aa_setXpalette,
		      aa_getpalette, "palettemenu");
  menu_genernumbered (AA_DITHERTYPES, "aa_dithering",
		      (CONST char *CONST * CONST) aa_dithernames, NULL,
		      MENU_INT, MENUFLAG_RADIO, aa_dither, aa_getdither,
		      "dither");
  menu_genernumbered (MYNATTRS, "aa_attr", name, NULL, MENU_INT,
		      MENUFLAG_RADIO, aa_swattr, aa_getattr, "attribute");
  menu_genernumbered (MYNATTRS, "aa_save3", name, NULL, MENU_INT,
		      MENUFLAG_RADIO, aa_save3, aa_getsave3, "save");
  for (i = 0; aa_fonts[i] != NULL; i++);
  nfonts = i;
  fontmenus = malloc (sizeof (menuitem) * i);
  fontmenus2 = malloc (sizeof (menuitem) * i);
  for (i = 0; aa_fonts[i] != NULL; i++)
    {
      char s[256];
      sprintf (s, "font%i%s", i, aa_fonts[i]->shortname);
      fontmenus[i].name = aa_fonts[i]->name;
      fontmenus[i].shortname = strdup (s);
      fontmenus[i].menuname = "aa_font";
      fontmenus[i].key = NULL;
      fontmenus[i].type = MENU_INT;
      fontmenus[i].flags = UI;
      fontmenus[i].iparam = i;
      fontmenus[i].function = (void (*)(void)) aa_sfont;
      fontmenus2[i].name = aa_fonts[i]->name;
      fontmenus2[i].shortname = fontmenus[i].shortname;
      fontmenus2[i].menuname = "aa_save2";
      fontmenus2[i].key = NULL;
      fontmenus2[i].type = MENU_INT;
      fontmenus2[i].flags = UI;
      fontmenus2[i].iparam = i;
      fontmenus2[i].function = (void (*)(void)) aa_save2;
    }
  menu_add (fontmenus, nfonts);
  menu_add (fontmenus2, nfonts);
  for (i = 0; aa_formats[i] != NULL; i++);
  nformats = i;
  formatmenus = malloc (sizeof (menuitem) * i);
  for (i = 0; aa_formats[i] != NULL; i++)
    {
      formatmenus[i].name = aa_formats[i]->formatname;
      formatmenus[i].shortname = aa_formats[i]->formatname;
      formatmenus[i].menuname = "aa_format";
      formatmenus[i].key = NULL;
      formatmenus[i].type = MENU_INT;
      formatmenus[i].flags = UI;
      formatmenus[i].iparam = i;
      formatmenus[i].function = (void (*)(void)) aa_save;
    }
  menu_add (formatmenus, nformats);
}
static void
aa_remove_menus ()
{
  int i;
  menu_delnumbered (sizeof (ttext) / sizeof (char *), "palettemenu");
  menu_delnumbered (AA_DITHERTYPES, "dither");
  menu_delnumbered (MYNATTRS, "attribute");
  menu_delnumbered (MYNATTRS, "save");
  menu_delete (menuitems, NITEMS (menuitems));
  menu_delete (fontmenus, nfonts);
  menu_delete (fontmenus2, nfonts);
  free (fontmenus2);
  menu_delete (formatmenus, nformats);
  free (formatmenus);
  for (i = 0; aa_fonts[i] != NULL; i++)
    {
      free ((char *) fontmenus[i].shortname);
    }
  free (fontmenus);
}
static int cursorvisible = 1;
static void
aa_fflush (void)
{
  if (aa_cursorx < 0)
    {
      if (cursorvisible)
	{
	  aa_gotoxy (c, 0, 0);
	  aa_hidecursor (c);
	  cursorvisible = 0;
	}
    }
  else
    {
      aa_gotoxy (c, aa_cursorx, aa_cursory);
      if (!cursorvisible)
	aa_showcursor (c), cursorvisible = 1;
    }
  aa_flush (c);
}

static CONST struct params params[] = {
  {"", P_HELP, NULL, "AA driver options:"},
  {"-aadriver", P_STRING, &aadriver, "Select display driver used by aa-lib"},
  {"-kbddriver", P_STRING, &kbddriver,
   "Select keyboard driver used by aa-lib"},
  {"-mousedriver", P_STRING, &mousedriver,
   "Select keyboard driver used by aa-lib"},
  {"-font", P_STRING, &deffont, "Select font"},
  {"-width", P_NUMBER, &width, "Set width"},
  {"-height", P_NUMBER, &height, "Set height"},
  {"-minwidth", P_NUMBER, &minwidth, "Set minimal allowed width"},
  {"-minheight", P_NUMBER, &minheight, "Set minimal allowed height"},
  {"-maxwidth", P_NUMBER, &maxwidth, "Set maximal allowed width"},
  {"-maxheight", P_NUMBER, &maxheight, "Set maximal allowed height"},
  {"-recwidth", P_NUMBER, &recwidth, "Set recommended width"},
  {"-recheight", P_NUMBER, &recheight, "Set recommended height"},
  {"-normal", P_SWITCH, enable, "enable usage of narmal characters"},
  {"-nonormal", P_SWITCH, disable, "disable usage of narmal characters"},
  {"-dim", P_SWITCH, enable + 1,
   "enable usage of dim(half bright) characters"},
  {"-nodim", P_SWITCH, disable + 1,
   "disable usage of dim(half bright) characters"},
  {"-bold", P_SWITCH, enable + 2,
   "enable usage of bold(double bright) characters"},
  {"-nobold", P_SWITCH, disable + 2,
   "disable usage of bold(double bright) characters"},
  {"-boldfont", P_SWITCH, enable + 3, "enable usage of boldfont characters"},
  {"-noboldfont", P_SWITCH, disable + 3,
   "disable usage of boldfont characters"},
  {"-reverse", P_SWITCH, enable + 4, "enable usage of reversed characters"},
  {"-noreverse", P_SWITCH, disable + 4,
   "disable usage of reversed characters"},
  {"-all", P_SWITCH, enable + 5, "enable usage of reserved characters"},
  {"-eight", P_SWITCH, enable + 6, "enable usage of non ansii characters"},
  {"-extended", P_SWITCH, &extended,
   "enable usage of extended character set"},
  {"-inverse", P_SWITCH, &inverse, "enable inverse"},
  {"-bright", P_NUMBER, &bright, "set bright (0-255)"},
  {"-contrast", P_NUMBER, &contrast, "set contrast (0-255)"},
  {"-gamma", P_FLOAT, &aa_gamma, "set famma (0-1)"},
  {"-nodither", P_SWITCH, dithering, "Disable dithering"},
  {"-floyd_steinberg", P_SWITCH, dithering + 2,
   "Enable floyd steinberg dithering"},
  {"-error_distribution", P_SWITCH, dithering + 1,
   "Enable error distribution dithering"},
  {"-random", P_NUMBER, &randomval, "Set random dithering value"},
  {"-dimmul", P_FLOAT, &dimmul, "Multiply factor for dim color (5.3)"},
  {"-boldmul", P_FLOAT, &boldmul, "Multiply factor for bold color (5.3)"},
  {"-nomouse", P_SWITCH, &nomouse, "Disable mouse"},
  {NULL, 0, NULL, NULL}
};

struct ui_driver aalib_driver = {
  "aa",
  aa_initialize,
  aa_getsize,
  aa_processevents,
  aa_get_mouse,
  aa_uninitialise,
  NULL,				/*You should implement just one */
  aa_set_palette,		/*of these and add NULL as second */
  aa_print,
  aa_display,
  aa_alloc_buffers,
  aa_free_buffers,
  aa_flip_buffers,
  aa_mousetype,
  aa_fflush,
  2,				/*text width */
  2,				/*text height */
  params,
  PALETTE_REDISPLAYS | SCREENSIZE | UPDATE_AFTER_RESIZE | AALIB,	/*flags...see ui.h */
  0.0, 0.0,			/*width/height of screen in centimeters */
  0, 0,				/*resolution of screen for windowed systems */
  UI_C256,			/*Image type */
  0, 255, 255			/*start, end of palette and maximum allocatable */
    /*entries */
};

#endif
