/*
    Bazoo_worms the game
    Copyright (C) 2011  Pavel Prochazka

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "ctype.h"
#include "MG_lib.h"
#include "WRMS_include.h"
#include "stages.h"
#include "gui.h"

/* simple gui system */

static void
widget_destroy (SPRITE * self)
{
  MG_send_msg (self, "on_widget_destroy");

  free (MG_get_key (self, "symb_name"));
  free (MG_get_key (self, "key_ticks"));
  MG_remove_list (MG_get_key (self, "timers"), free);

  LIST *attachments = self->attached_sprites;

  while (attachments)
    {
      MG_remove_sprite (attachments->value);
      attachments = attachments->node;
    }
}

int
WRMS_get_screen_w ()
{
  MACHINE *m = MG_get_machine ();
  return m->screen->w;
}

int
WRMS_get_screen_h ()
{
  MACHINE *m = MG_get_machine ();
  return m->screen->h;
}

void
WRMS_widget_remove (SPRITE * self)
{
  MG_remove_sprite (self);
}

static void
msg_dispatcher (SPRITE * self)
{
  LIST *timers = MG_get_key (self, "timers");
  WRMS_timer *t = NULL;

  while (timers)
    {
      t = (WRMS_timer *) timers->value;

      if (t->ticks == 0)
	{
	  t->func (self, t->args);
	}
      t->ticks += MG_get_system_dt ();
      if (t->ticks >= t->max_ticks)
	t->ticks = 0;

      timers = timers->node;
    }

  POINT mouse = { MG_get_mouse_x (), MG_get_mouse_y () };
  MACHINE *m = MG_get_machine ();
  SDL_Event ev;
  LIST *l = m->events;

  while (l)
    {
      ev = *((SDL_Event *) l->value);
      if ((ev.type == SDL_MOUSEBUTTONUP)
	  && pixel_in_rect_p (self->dest, mouse))
	{
	  if (SDL_ShowCursor (SDL_QUERY) == SDL_DISABLE)
	    return;

	  MG_send_msg (self, "on_click");
	}
      else if (ev.type == SDL_KEYDOWN)
	{
	  MG_send_msg (self, "on_keydown");
	}
      else if (ev.type == SDL_QUIT)
	{
	  MG_send_msg (self, "on_quit");
	}
      l = l->node;
    }
}

void
WRMS_widget_center (SPRITE * owner, SPRITE * centralized)
{
  sprite_align_mid (centralized, owner);
}

SPRITE *
WRMS_widget_parent (SPRITE * self)
{
  return MG_get_key (self, "parent");
}

SPRITE *
WRMS_widget_make (SDL_Surface * img, SDL_Rect * r, const char *symb_name)
{
  SPRITE *out = MG_make_sprite (img, r);

  MG_install_callback (out, "on_iter", msg_dispatcher);
  MG_install_callback (out, "on_destroy", widget_destroy);

  if (symb_name)
    MG_set_key (out, "symb_name", strdup (symb_name));

  MG_set_key (out, "parent", NULL);
  MG_set_key (out, "key_ticks", make_int (KEY_REPEAT));

  return out;
}

SPRITE *
WRMS_widget_bind (SPRITE * owner, SPRITE * attachment)
{
  SPRITE *out = attachment;
  MG_attach_sprite (owner, attachment, 0, 0);
  MG_set_key (attachment, "parent", owner);

  MG_send_msg (owner, "on_attach");
  return out;
}

SPRITE *
WRMS_widget_unbind (SPRITE * owner, SPRITE * attachment)
{
  SPRITE *out = MG_detach_sprite (owner, attachment);
  MG_send_msg (owner, "on_detach");
  MG_set_key (attachment, "parent", NULL);
  return out;
}

void
WRMS_widget_install_timer (SPRITE * widget, int ticks,
			   void (*func) (SPRITE *, void *), void *args)
{
  WRMS_timer *t = (WRMS_timer *) malloc (sizeof (WRMS_timer));
  memset (t, 0, sizeof (WRMS_timer));

  t->ticks = 0;
  t->max_ticks = ticks;
  t->func = func;
  t->args = args;

  LIST *timers = MG_get_key (widget, "timers");
  MG_add (&timers, t);
  MG_set_key (widget, "timers", timers);
}

static void
container_destroy (SPRITE * self)
{
  MG_send_msg (self, "on_dialog_destroy");

  free (MG_get_key (self, "type"));
  if (self->img)
    sprite_clear_surface (self);
  free (MG_get_key (self, "border"));
}

static void
container_attach (SPRITE * self)
{
  int *type = (int *) MG_get_key (self, "type");
  if (!type)
    {
      printf ("warn: not a container\n");
      return;
    }

  int count = MG_list_length (self->attached_sprites);
  SPRITE *attachement = MG_list_nth (self->attached_sprites, count - 1);
  int *border_ptr = MG_get_key (self, "border");
  int border = (border_ptr ? *border_ptr : 0);

  if (count == 1)
    {
      if ((self->dest.w < attachement->dest.w)
	  || (self->dest.h < attachement->dest.h))
	{
	  MG_set_rect (self, rect (self->dest.x,
				   self->dest.y,
				   attachement->dest.w + (2 * border),
				   attachement->dest.h + (2 * border)));
	}

      MG_sprite_set_position (attachement, self->dest.x + border,
			      self->dest.y + border);
      return;
    }

  SPRITE *prelast = MG_list_nth (self->attached_sprites, count - 2);

  if (*type == WRMS_CONTAINER_VERTICAL)
    {
      SDL_Rect change = self->dest;

      MG_sprite_set_position (attachement, self->dest.x + border,
			      prelast->dest.y + prelast->dest.h);

      if (attachement->dest.w > (self->dest.w - (2 * border)))
	{
	  change.w = attachement->dest.w + (2 * border);
	}

      if (change.h <
	  ((attachement->dest.y - change.y) + attachement->dest.h + border))
	change.h =
	  (attachement->dest.y - self->dest.y) + attachement->dest.h + border;

      MG_set_rect (self, change);
    }
  else if (*type == WRMS_CONTAINER_HORIZONTAL)
    {
      SDL_Rect change = self->dest;
      MG_sprite_set_position (attachement, prelast->dest.x + prelast->dest.w,
			      self->dest.y + border);

      if (attachement->dest.h > (self->dest.h + (2 * border)))
	{
	  change.w =
	    (attachement->dest.w - self->dest.x) + attachement->dest.h +
	    border;
	}

      MG_set_rect (self, change);
    }
}

SPRITE *
WRMS_container_make (SDL_Surface * bckg, WRMS_container_type t)
{
  SPRITE *out = WRMS_widget_make (bckg, NULL, NULL);

  MG_install_callback (out, "on_attach", container_attach);
  MG_install_callback (out, "on_widget_destroy", container_destroy);

  /* TODO: memory leak */
  MG_set_key (out, "type", make_int (t));
  return out;
}

void
WRMS_container_clear (SPRITE * container)
{
  MG_destructive_map (container->attached_sprites,
		      (void (*)(void *)) MG_remove_sprite);
  MG_remove_list (container->attached_sprites, NULL);
  container->attached_sprites = NULL;
}

void
WRMS_container_set_border (SPRITE * container, Uint32 border)
{
  /* TODO: memory leak */
  MG_set_key (container, "border", make_int (border));
}

SPRITE *
WRMS_widget_text (const char *title, const char *font, int size, SDL_Color c)
{
  SDL_Surface *txt = render_text (title, c, size, font);
  SPRITE *out = WRMS_widget_make (txt, NULL, NULL);
  MG_install_callback (out, "on_widget_destroy", sprite_clear_surface);
  return out;
}

SPRITE *
WRMS_textarea_make (const char *txt)
{
  char *working = strdup (txt);
  char *tmp = working;

  SPRITE *out = WRMS_container_make (NULL, WRMS_CONTAINER_VERTICAL);

  char *token = strtok (tmp, "\n");

  while (token)
    {
      SPRITE *row =
	WRMS_widget_text (token, WRMS_FONT, 14, WRMS_FONT_MENU_COLOR);
      WRMS_widget_bind (out, row);
      token = strtok (NULL, "\n");
    }
  free (working);

  return out;
}

SPRITE *
WRMS_dialog_make (const char *title, int w, int h)
{
#define DIALOG_BORDER 3

  SDL_Surface *dialog_rect =
    surface_make_rect (w, h, DIALOG_COLOR, DIALOG_BORDER_COLOR,
		       DIALOG_BORDER);
  SPRITE *out = WRMS_container_make (dialog_rect, WRMS_CONTAINER_VERTICAL);

  WRMS_container_set_border (out, DIALOG_BORDER);

  /* make title bar */
  SPRITE *title_w =
    WRMS_widget_text (title, WRMS_FONT, 18, DIALOG_TITLE_COLOR);
  WRMS_widget_bind (out, title_w);
  MG_move_sprite (title_w, (out->dest.w / 2) - (title_w->dest.w / 2), 0);

  /* make body */
  SDL_Rect r =
    rect (0, 0, out->dest.w - (2 * DIALOG_BORDER),
	  out->dest.h - 3 * (title_w->dest.h));
  SDL_Surface *cont_rect =
    surface_make_rect (r.w, r.h, DIALOG_COLOR, DIALOG_BORDER_COLOR,
		       DIALOG_BORDER);
  SPRITE *body = WRMS_container_make (cont_rect, WRMS_CONTAINER_VERTICAL);
  WRMS_container_set_border (body, 4);
  MG_set_rect (body, r);
  WRMS_widget_bind (out, body);

  /* make button bar */
  int buttons_h =
    out->dest.h - ((body->dest.y + body->dest.h) + DIALOG_BORDER);

  cont_rect =
    surface_make_rect (r.w, buttons_h, DIALOG_COLOR, DIALOG_BORDER_COLOR,
		       DIALOG_BORDER);
  SPRITE *button_bar =
    WRMS_container_make (cont_rect, WRMS_CONTAINER_HORIZONTAL);
  WRMS_container_set_border (button_bar, 4);

  MG_set_rect (button_bar, rect (0, 0, w, buttons_h));
  WRMS_widget_bind (out, button_bar);

  return out;
}

SPRITE *
WRMS_dialog_body (SPRITE * dialog)
{
  return MG_list_nth (dialog->attached_sprites, 1);
}

SPRITE *
WRMS_dialog_buttons (SPRITE * dialog)
{
  return MG_list_nth (dialog->attached_sprites, 2);
}

SPRITE *
WRMS_button_make (const char *title, SDL_Surface * background, SDL_Color c)
{
  SPRITE *out = WRMS_widget_make (background, NULL, NULL);
  SPRITE *button_txt = WRMS_widget_text (title, WRMS_FONT, 20, c);

  if (!background)
    {
      MG_set_rect (out, button_txt->dest);
    }

  WRMS_widget_bind (out, button_txt);
  WRMS_widget_center (out, button_txt);
  return out;
}


/* editbox impl */
static char *
string_end (const char *str)
{
  char *it = (char *) str;
  while (*it)
    {
      it++;
    }
  return it;
}

static void
on_edit_destroy (SPRITE * editbox)
{
  free (MG_get_key (editbox, "enter_mode_p"));
  free (MG_get_key (editbox, "max_chars"));
  free (MG_get_key (editbox, "editable_string"));
}

void
WRMS_update_text (SPRITE * wtext, SDL_Color c, Uint16 size, const char *txt)
{
  if (wtext->img)
    SDL_FreeSurface (wtext->img);

  MG_set_img (wtext, render_text (txt, c, size, WRMS_FONT));

  SDL_Rect r =
    rect (0, 0, (wtext->img ? wtext->img->w : 0),
	  (wtext->img ? wtext->img->h : 0));
  MG_set_srcrect (wtext, r);
}

static void
editbox_update (SPRITE * editbox, SDL_Color c)
{
  char *str = MG_get_key (editbox, "editable_string");

  WRMS_update_text (editbox, c, EDITABLE_TEXT_SIZE, str);
}

static void
on_edit_keydown (SPRITE * editbox)
{
  int *enter_mode_p = MG_get_key (editbox, "enter_mode_p");
  int *max_chars = MG_get_key (editbox, "max_chars");

  if (*enter_mode_p)
    {
      char *str = MG_get_key (editbox, "editable_string");
      char *end = string_end (str);

      /* we can write into it */
      if (MG_key_down_p (SDLK_RETURN))
	{
	  if (strlen (str) <= 1)	/* cannot accept zero length string */
	    return;

	  if (end != str)
	    {
	      *(end - 1) = '\0';
	      *enter_mode_p = 0;
	    }

	  MG_send_msg (editbox, "on_change");
	  editbox_update (editbox, EDITABLE_TEXT_INACTIVE);
	  SDL_ShowCursor (SDL_ENABLE);
	  return;
	}
      else if (MG_key_down_p (SDLK_BACKSPACE))
	{
	  if ((end - 2) >= str)
	    {
	      *(end - 2) = '_';
	      *(end - 1) = '\0';
	    }
	  else
	    {
	      *(str) = '_';
	      *(str + 1) = '\0';
	    }
	}
      else
	{
	  NODE *it = MG_get_machine ()->events;
	  SDL_Event *ev = NULL;

	  if (*max_chars <= (strlen (str) - 1))
	    return;

	  while (it)
	    {
	      ev = (SDL_Event *) it->value;
	      if (ev->type == SDL_KEYDOWN)
		break;
	      it = it->node;
	    }

	  char ansi_char = (char) ev->key.keysym.sym;

	  /* filter only 'a' - 'z' */
	  //if( !(ansi_char >= 'a' && ansi_char <= 'z') )
	  if (!isalnum (ansi_char))
	    return;

	  /* ASCII */
	  *(end + 1) = '\0';
	  *(end - 1) = ansi_char;
	  *(end) = '_';
	}

      editbox_update (editbox, EDITABLE_TEXT_ACTIVE);
    }
}

static void
on_edit_activate (SPRITE * edit)
{
  int *enter_mode_p = MG_get_key (edit, "enter_mode_p");
  char *txt = MG_get_key (edit, "editable_string");
  *enter_mode_p = 1;

  /* place '_' at the end of input */
  char *end = string_end (txt);
  *end = '_';
  *(end + 1) = '\0';

  editbox_update (edit, EDITABLE_TEXT_ACTIVE);
  SDL_ShowCursor (SDL_DISABLE);
}

SPRITE *
WRMS_edit_box_make (int w, int h, int max_chars)
{
  SDL_Rect r = { 0, 0, w, h };
  SPRITE *out = WRMS_widget_make (NULL, &r, NULL);
  /* reserve a lot of chars for wide characters */
  char *string = (char *) malloc (sizeof (char) * 2 * ((max_chars) + 2));
  memset (string, 0, sizeof (char) * 2 * ((max_chars) + 2));

  MG_install_callback (out, "on_keydown", on_edit_keydown);
  MG_install_callback (out, "on_widget_destroy", on_edit_destroy);
  MG_install_callback (out, "on_click", on_edit_activate);
  MG_set_key (out, "enter_mode_p", make_int (0));
  MG_set_key (out, "max_chars", make_int (max_chars));
  MG_set_key (out, "editable_string", string);

  return out;
}

char *
WRMS_edit_box_set_text (SPRITE * editbox, const char *txt)
{
  int *enter_mode_p = MG_get_key (editbox, "enter_mode_p");
  int *max_chars = MG_get_key (editbox, "max_chars");
  char *str = MG_get_key (editbox, "editable_string");

  if (*enter_mode_p || !txt)
    return NULL;		/* can not set text during editing */

  memset (str, 0, 2 * (*max_chars + 2));
  strncpy (str, txt, *max_chars);

  editbox_update (editbox, EDITABLE_TEXT_INACTIVE);

  return str;
}

char *
WRMS_edit_box_get_text (SPRITE * editbox)
{
  return (char *) MG_get_key (editbox, "editable_string");
}

static void
error_dialog_accept (SPRITE * self)
{
  SPRITE *container = WRMS_widget_parent (self);
  SPRITE *dialog = WRMS_widget_parent (container);

  MG_send_msg (dialog, "on_error_dialog_accept");
  MG_remove_sprite (dialog);
}

SPRITE *
WRMS_dialog_error (const char *title, const char *body)
{
  SPRITE *dialog =
    WRMS_dialog_make (title, ERROR_DIALOG_WIDTH, ERROR_DIALOG_HEIGHT);
  SPRITE *text_body = WRMS_textarea_make (body);
  WRMS_widget_bind (WRMS_dialog_body (dialog), text_body);

  SPRITE *button_ok = WRMS_button_make ("OK", NULL, EDITABLE_TEXT_INACTIVE);
  MG_install_callback (button_ok, "on_click", error_dialog_accept);

  SPRITE *bb = WRMS_dialog_buttons (dialog);

  WRMS_widget_bind (bb, button_ok);
  WRMS_widget_center (bb, button_ok);

  return dialog;
}

static void
on_yes (SPRITE * button_yes)
{
  SPRITE *hcontainer = WRMS_widget_parent (button_yes);
  SPRITE *vcontainer = WRMS_widget_parent (hcontainer);
  SPRITE *dialog = WRMS_widget_parent (vcontainer);

  MG_send_msg (dialog, "on_yes");
  MG_remove_sprite (dialog);
}

static void
on_no (SPRITE * button_no)
{
  SPRITE *hcontainer = WRMS_widget_parent (button_no);
  SPRITE *vcontainer = WRMS_widget_parent (hcontainer);
  SPRITE *dialog = WRMS_widget_parent (vcontainer);

  MG_send_msg (dialog, "on_no");
  MG_remove_sprite (dialog);
}

SPRITE *
WRMS_dialog_yesno (const char *title, const char *question)
{
  SPRITE *dialog =
    WRMS_dialog_make (title, YESNO_DIALOG_WIDTH, YESNO_DIALOG_HEIGHT);

  SPRITE *text_body = WRMS_textarea_make (question);

  WRMS_widget_bind (WRMS_dialog_body (dialog), text_body);

  SPRITE *butc = WRMS_container_make (NULL, WRMS_CONTAINER_HORIZONTAL);

  SPRITE *button_yes = WRMS_button_make ("YES", NULL, EDITABLE_TEXT_INACTIVE);

  SPRITE *button_no = WRMS_button_make ("NO", NULL, EDITABLE_TEXT_INACTIVE);

  MG_install_callback (button_yes, "on_click", on_yes);
  MG_install_callback (button_no, "on_click", on_no);

  WRMS_widget_bind (butc, button_yes);
  WRMS_widget_bind (butc, button_no);
  MG_move_sprite (button_no,
		  YESNO_DIALOG_WIDTH - button_yes->dest.w -
		  button_no->dest.w - 12, 0);

  WRMS_widget_bind (WRMS_dialog_buttons (dialog), butc);
  return dialog;
}
