/*
Copyright (C) 1997-2001 Id Software, Inc.

This program 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 program 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 program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include <string.h>
#include <ctype.h>

#include "client.h"

#include "qmenu.h"

static void Action_DoEnter(menuaction_s *a);
static void Action_Draw(menuaction_s *a);
static void Menu_DrawStatusBar(const char *string);
static void Menulist_DoEnter(menulist_s *l);
static void MenuList_Draw(menulist_s *l);
static void Separator_Draw(menuseparator_s *s);
static void Slider_DoSlide(menuslider_s *s, int dir);
static void Slider_Draw(menuslider_s *s);
static void SpinControl_DoEnter(menulist_s *s);
static void SpinControl_Draw(menulist_s *s);
static void SpinControl_DoSlide(menulist_s *s, int dir);

#define RCOLUMN_OFFSET 16
#define LCOLUMN_OFFSET -16

extern refexport_t re;
extern viddef_t viddef;

#define VID_WIDTH viddef.width
#define VID_HEIGHT viddef.height

#define Draw_Char re.DrawChar
#define Draw_Fill re.DrawFill

void Action_DoEnter(menuaction_s *a) {
  if(a->generic.callback)
    a->generic.callback(a);
}

void Action_Draw(menuaction_s *a) {
  if(a->generic.flags & QMF_LEFT_JUSTIFY) {
    if(a->generic.flags & QMF_GRAYED)
      Menu_DrawStringDark(a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y,
                          a->generic.name);
    else
      Menu_DrawString(a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y,
                      a->generic.name);
  } else {
    if(a->generic.flags & QMF_GRAYED)
      Menu_DrawStringR2LDark(a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y,
                             a->generic.name);
    else
      Menu_DrawStringR2L(a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y,
                         a->generic.name);
  }
  if(a->generic.ownerdraw)
    a->generic.ownerdraw(a);
}

bool Field_DoEnter(menufield_s *f) {
  if(f->generic.callback) {
    f->generic.callback(f);
    return true;
  }
  return false;
}

void Field_Draw(menufield_s *f) {
  int i;
  char tempbuffer[128] = "";

  if(f->generic.name)
    Menu_DrawStringR2LDark(f->generic.x + f->generic.parent->x + LCOLUMN_OFFSET, f->generic.y + f->generic.parent->y,
                           f->generic.name);

  strncpy(tempbuffer, f->buffer + f->visible_offset, f->visible_length);

  Draw_Char(f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y - 4, 18);
  Draw_Char(f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y + 4, 24);

  Draw_Char(f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y - 4,
            20);
  Draw_Char(f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y + 4,
            26);

  for(i = 0; i < f->visible_length; i++) {
    Draw_Char(f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y - 4, 19);
    Draw_Char(f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y + 4, 25);
  }

  Menu_DrawString(f->generic.x + f->generic.parent->x + 24, f->generic.y + f->generic.parent->y, tempbuffer);

  if(Menu_ItemAtCursor(f->generic.parent) == f) {
    int offset;

    if(f->visible_offset)
      offset = f->visible_length;
    else
      offset = f->cursor;

    if(((int)(Sys_Milliseconds() / 250)) & 1) {
      Draw_Char(f->generic.x + f->generic.parent->x + (offset + 2) * 8 + 8, f->generic.y + f->generic.parent->y, 11);
    } else {
      Draw_Char(f->generic.x + f->generic.parent->x + (offset + 2) * 8 + 8, f->generic.y + f->generic.parent->y, ' ');
    }
  }
}

bool Field_Key(menufield_s *f, int key) {
  extern int keydown[];

  switch(key) {
  case K_KP_SLASH:
    key = '/';
    break;
  case K_KP_MINUS:
    key = '-';
    break;
  case K_KP_PLUS:
    key = '+';
    break;
  case K_KP_HOME:
    key = '7';
    break;
  case K_KP_UPARROW:
    key = '8';
    break;
  case K_KP_PGUP:
    key = '9';
    break;
  case K_KP_LEFTARROW:
    key = '4';
    break;
  case K_KP_5:
    key = '5';
    break;
  case K_KP_RIGHTARROW:
    key = '6';
    break;
  case K_KP_END:
    key = '1';
    break;
  case K_KP_DOWNARROW:
    key = '2';
    break;
  case K_KP_PGDN:
    key = '3';
    break;
  case K_KP_INS:
    key = '0';
    break;
  case K_KP_DEL:
    key = '.';
    break;
  }

  if(key > 127) {
    switch(key) {
    case K_DEL:
    default:
      return false;
    }
  }

  /*
  ** support pasting from the clipboard
  */
  if((toupper(key) == 'V' && keydown[K_CTRL]) || (((key == K_INS) || (key == K_KP_INS)) && keydown[K_SHIFT])) {
    char *cbd;

    if((cbd = Sys_GetClipboardData()) != 0) {
      strtok(cbd, "\n\r\b");

      strncpy(f->buffer, cbd, f->length - 1);
      f->cursor = strlen(f->buffer);
      f->visible_offset = f->cursor - f->visible_length;
      if(f->visible_offset < 0)
        f->visible_offset = 0;

      free(cbd);
    }
    return true;
  }

  switch(key) {
  case K_KP_LEFTARROW:
  case K_LEFTARROW:
  case K_BACKSPACE:
    if(f->cursor > 0) {
      memmove(&f->buffer[f->cursor - 1], &f->buffer[f->cursor], strlen(&f->buffer[f->cursor]) + 1);
      f->cursor--;

      if(f->visible_offset) {
        f->visible_offset--;
      }
    }
    break;

  case K_KP_DEL:
  case K_DEL:
    memmove(&f->buffer[f->cursor], &f->buffer[f->cursor + 1], strlen(&f->buffer[f->cursor + 1]) + 1);
    break;

  case K_KP_ENTER:
  case K_ENTER:
  case K_ESCAPE:
  case K_TAB:
    return false;

  case K_SPACE:
  default:
    if(!isdigit(key) && (f->generic.flags & QMF_NUMBERSONLY))
      return false;

    if(f->cursor < f->length) {
      f->buffer[f->cursor++] = key;
      f->buffer[f->cursor] = 0;

      if(f->cursor > f->visible_length) {
        f->visible_offset++;
      }
    }
  }

  return true;
}

void Menu_AddItem(menuframework_s *menu, void *item) {
  if(menu->nitems == 0)
    menu->nslots = 0;

  if(menu->nitems < MAXMENUITEMS) {
    menu->items[menu->nitems] = item;
    ((menucommon_s *)menu->items[menu->nitems])->parent = menu;
    menu->nitems++;
  }

  menu->nslots = Menu_TallySlots(menu);
}

/*
** Menu_AdjustCursor
**
** This function takes the given menu, the direction, and attempts
** to adjust the menu's cursor so that it's at the next available
** slot.
*/
void Menu_AdjustCursor(menuframework_s *m, int dir) {
  menucommon_s *citem;

  /*
  ** see if it's in a valid spot
  */
  if(m->cursor >= 0 && m->cursor < m->nitems) {
    if((citem = Menu_ItemAtCursor(m)) != 0) {
      if(citem->type != MTYPE_SEPARATOR)
        return;
    }
  }

  /*
  ** it's not in a valid spot, so crawl in the direction indicated until we
  ** find a valid spot
  */
  if(dir == 1) {
    while(1) {
      citem = Menu_ItemAtCursor(m);
      if(citem)
        if(citem->type != MTYPE_SEPARATOR)
          break;
      m->cursor += dir;
      if(m->cursor >= m->nitems)
        m->cursor = 0;
    }
  } else {
    while(1) {
      citem = Menu_ItemAtCursor(m);
      if(citem)
        if(citem->type != MTYPE_SEPARATOR)
          break;
      m->cursor += dir;
      if(m->cursor < 0)
        m->cursor = m->nitems - 1;
    }
  }
}

void Menu_Center(menuframework_s *menu) {
  int height;

  height = ((menucommon_s *)menu->items[menu->nitems - 1])->y;
  height += 10;

  menu->y = (VID_HEIGHT - height) / 2;
}

void Menu_Draw(menuframework_s *menu) {
  int i;
  menucommon_s *item;

  /*
  ** draw contents
  */
  for(i = 0; i < menu->nitems; i++) {
    switch(((menucommon_s *)menu->items[i])->type) {
    case MTYPE_FIELD:
      Field_Draw((menufield_s *)menu->items[i]);
      break;
    case MTYPE_SLIDER:
      Slider_Draw((menuslider_s *)menu->items[i]);
      break;
    case MTYPE_LIST:
      MenuList_Draw((menulist_s *)menu->items[i]);
      break;
    case MTYPE_SPINCONTROL:
      SpinControl_Draw((menulist_s *)menu->items[i]);
      break;
    case MTYPE_ACTION:
      Action_Draw((menuaction_s *)menu->items[i]);
      break;
    case MTYPE_SEPARATOR:
      Separator_Draw((menuseparator_s *)menu->items[i]);
      break;
    }
  }

  item = Menu_ItemAtCursor(menu);

  if(item && item->cursordraw) {
    item->cursordraw(item);
  } else if(menu->cursordraw) {
    menu->cursordraw(menu);
  } else if(item && item->type != MTYPE_FIELD) {
    if(item->flags & QMF_LEFT_JUSTIFY) {
      Draw_Char(menu->x + item->x - 24 + item->cursor_offset, menu->y + item->y,
                12 + ((int)(Sys_Milliseconds() / 250) & 1));
    } else {
      Draw_Char(menu->x + item->cursor_offset, menu->y + item->y, 12 + ((int)(Sys_Milliseconds() / 250) & 1));
    }
  }

  if(item) {
    if(item->statusbarfunc)
      item->statusbarfunc((void *)item);
    else if(item->statusbar)
      Menu_DrawStatusBar(item->statusbar);
    else
      Menu_DrawStatusBar(menu->statusbar);

  } else {
    Menu_DrawStatusBar(menu->statusbar);
  }
}

void Menu_DrawStatusBar(const char *string) {
  if(string) {
    int l = strlen(string);
    int maxcol = VID_WIDTH / 8;
    int col = maxcol / 2 - l / 2;

    Draw_Fill(0, VID_HEIGHT - 8, VID_WIDTH, 8, 4);
    Menu_DrawString(col * 8, VID_HEIGHT - 8, string);
  } else {
    Draw_Fill(0, VID_HEIGHT - 8, VID_WIDTH, 8, 0);
  }
}

void Menu_DrawString(int x, int y, const char *string) {
  unsigned i;

  for(i = 0; i < strlen(string); i++) {
    Draw_Char((x + i * 8), y, string[i]);
  }
}

void Menu_DrawStringDark(int x, int y, const char *string) {
  unsigned i;

  for(i = 0; i < strlen(string); i++) {
    Draw_Char((x + i * 8), y, string[i] + 128);
  }
}

void Menu_DrawStringR2L(int x, int y, const char *string) {
  unsigned i;

  for(i = 0; i < strlen(string); i++) {
    Draw_Char((x - i * 8), y, string[strlen(string) - i - 1]);
  }
}

void Menu_DrawStringR2LDark(int x, int y, const char *string) {
  unsigned i;

  for(i = 0; i < strlen(string); i++) {
    Draw_Char((x - i * 8), y, string[strlen(string) - i - 1] + 128);
  }
}

void *Menu_ItemAtCursor(menuframework_s *m) {
  if(m->cursor < 0 || m->cursor >= m->nitems)
    return 0;

  return m->items[m->cursor];
}

bool Menu_SelectItem(menuframework_s *s) {
  menucommon_s *item = (menucommon_s *)Menu_ItemAtCursor(s);

  if(item) {
    switch(item->type) {
    case MTYPE_FIELD:
      return Field_DoEnter((menufield_s *)item);
    case MTYPE_ACTION:
      Action_DoEnter((menuaction_s *)item);
      return true;
    case MTYPE_LIST:
      //			Menulist_DoEnter( ( menulist_s * ) item );
      return false;
    case MTYPE_SPINCONTROL:
      //			SpinControl_DoEnter( ( menulist_s * ) item );
      return false;
    }
  }
  return false;
}

void Menu_SetStatusBar(menuframework_s *m, const char *string) { m->statusbar = string; }

void Menu_SlideItem(menuframework_s *s, int dir) {
  menucommon_s *item = (menucommon_s *)Menu_ItemAtCursor(s);

  if(item) {
    switch(item->type) {
    case MTYPE_SLIDER:
      Slider_DoSlide((menuslider_s *)item, dir);
      break;
    case MTYPE_SPINCONTROL:
      SpinControl_DoSlide((menulist_s *)item, dir);
      break;
    }
  }
}

int Menu_TallySlots(menuframework_s *menu) {
  int i;
  int total = 0;

  for(i = 0; i < menu->nitems; i++) {
    if(((menucommon_s *)menu->items[i])->type == MTYPE_LIST) {
      int nitems = 0;
      const char **n = ((menulist_s *)menu->items[i])->itemnames;

      while(*n)
        nitems++, n++;

      total += nitems;
    } else {
      total++;
    }
  }

  return total;
}

void Menulist_DoEnter(menulist_s *l) {
  int start;

  start = l->generic.y / 10 + 1;

  l->curvalue = l->generic.parent->cursor - start;

  if(l->generic.callback)
    l->generic.callback(l);
}

void MenuList_Draw(menulist_s *l) {
  const char **n;
  int y = 0;

  Menu_DrawStringR2LDark(l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y,
                         l->generic.name);

  n = l->itemnames;

  Draw_Fill(l->generic.x - 112 + l->generic.parent->x, l->generic.parent->y + l->generic.y + l->curvalue * 10 + 10, 128,
            10, 16);
  while(*n) {
    Menu_DrawStringR2LDark(l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET,
                           l->generic.y + l->generic.parent->y + y + 10, *n);

    n++;
    y += 10;
  }
}

void Separator_Draw(menuseparator_s *s) {
  if(s->generic.name)
    Menu_DrawStringR2LDark(s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->generic.name);
}

void Slider_DoSlide(menuslider_s *s, int dir) {
  s->curvalue += dir;

  if(s->curvalue > s->maxvalue)
    s->curvalue = s->maxvalue;
  else if(s->curvalue < s->minvalue)
    s->curvalue = s->minvalue;

  if(s->generic.callback)
    s->generic.callback(s);
}

#define SLIDER_RANGE 10

void Slider_Draw(menuslider_s *s) {
  int i;

  Menu_DrawStringR2LDark(s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET, s->generic.y + s->generic.parent->y,
                         s->generic.name);

  s->range = (s->curvalue - s->minvalue) / (float)(s->maxvalue - s->minvalue);

  if(s->range < 0)
    s->range = 0;
  if(s->range > 1)
    s->range = 1;
  Draw_Char(s->generic.x + s->generic.parent->x + RCOLUMN_OFFSET, s->generic.y + s->generic.parent->y, 128);
  for(i = 0; i < SLIDER_RANGE; i++)
    Draw_Char(RCOLUMN_OFFSET + s->generic.x + i * 8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y,
              129);
  Draw_Char(RCOLUMN_OFFSET + s->generic.x + i * 8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 130);
  Draw_Char((int)(8 + RCOLUMN_OFFSET + s->generic.parent->x + s->generic.x + (SLIDER_RANGE - 1) * 8 * s->range),
            s->generic.y + s->generic.parent->y, 131);
}

void SpinControl_DoEnter(menulist_s *s) {
  s->curvalue++;
  if(s->itemnames[s->curvalue] == 0)
    s->curvalue = 0;

  if(s->generic.callback)
    s->generic.callback(s);
}

void SpinControl_DoSlide(menulist_s *s, int dir) {
  s->curvalue += dir;

  if(s->curvalue < 0)
    s->curvalue = 0;
  else if(s->itemnames[s->curvalue] == 0)
    s->curvalue--;

  if(s->generic.callback)
    s->generic.callback(s);
}

void SpinControl_Draw(menulist_s *s) {
  char buffer[100];

  if(s->generic.name) {
    Menu_DrawStringR2LDark(s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET, s->generic.y + s->generic.parent->y,
                           s->generic.name);
  }
  if(!strchr(s->itemnames[s->curvalue], '\n')) {
    Menu_DrawString(RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y,
                    s->itemnames[s->curvalue]);
  } else {
    strcpy(buffer, s->itemnames[s->curvalue]);
    *strchr(buffer, '\n') = 0;
    Menu_DrawString(RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, buffer);
    strcpy(buffer, strchr(s->itemnames[s->curvalue], '\n') + 1);
    Menu_DrawString(RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y + 10,
                    buffer);
  }
}