/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "device/key_man.hh"
#include "device/event.hh"
#include "file/file.hh"
#include "error/alert.hh"
#include "device/kernel.hh"
#include "memory/growheap.hh"

i4_key_man_class i4_key_man;

i4_key_man_class::i4_key_man_class()
{
  loaded=0;
  active_list=0;
  context_list=0;
  command_list=0;
  char_heap=0;
  memset(keys, 0, sizeof(keys));
}


// end commands no longer appropriate for for the current modifiers
void i4_key_man_class::end_actives(int matches_key, i4_time_class &time)
{  
  key_item *last=0;
  for (key_item *j=active_list; j; )
  {
    if (j->modifier_flags!=key_modifiers_pressed || j->key==matches_key)
    {
      key_item *q=j;
      j=j->next_active;

      if (last)
        last->next_active=q->next_active;
      else
        active_list=active_list->next_active;
      
      i4_end_command_event_class kcmd( (*command_list)[q->command_id], q->command_id, time);
      send_event_to_agents(&kcmd, FLAG_END_COMMAND);

      q->command_active=0;
    }
    else 
    {
      last=j;
      j=j->next_active;
    }
  }
}


void i4_key_man_class::get_modifiers(int k_mod)
{
  if (k_mod)                     // turn left & right into same thing
  {
    if (k_mod & I4_MODIFIER_SHIFT)
      k_mod=I4_MODIFIER_SHIFT;
    if (k_mod & I4_MODIFIER_CTRL)
      k_mod=I4_MODIFIER_CTRL;
    if (k_mod & I4_MODIFIER_ALT)
      k_mod=I4_MODIFIER_ALT;

    // don't uses these modifiers
    if (k_mod & (I4_MODIFIER_WINDOWS | I4_MODIFIER_CAPS | I4_MODIFIER_NUMLOCK))
      k_mod &= ~(I4_MODIFIER_WINDOWS | I4_MODIFIER_CAPS | I4_MODIFIER_NUMLOCK);
  }

  if (context_list)
    k_mod &= ~((*context_list)[context_id].modifiers_taken);


  key_modifiers_pressed=k_mod;
}


void i4_key_man_class::add_active(i4_key_man_class::key_item *i, i4_time_class &time)
{
  if (!i->command_active)
  {
    i->command_active=1;
    i->next_active=active_list;
    active_list=i;

    i4_do_command_event_class kcmd( (*command_list)[i->command_id], i->command_id, time);
    send_event_to_agents(&kcmd, FLAG_DO_COMMAND);
  }
}


void i4_key_man_class::receive_event(i4_event *ev)
{
  if (!loaded) return ;

  if (ev->type()==i4_event::KEY_PRESS)
  {
    CAST_PTR(kev, i4_key_press_event_class, ev);
    int old_modifiers=key_modifiers_pressed;

    get_modifiers(kev->modifiers);

    if (old_modifiers!=key_modifiers_pressed)
    {
      for (key_item *i=active_list; i; i=i->next_active)
      {
        for (key_item *j=keys[i->key]; j; j=j->next)
        {
          if (j!=i && j->modifier_flags==key_modifiers_pressed && 
              (j->context_mask&(1<time);

        }
      }

      end_actives(-1, kev->time);
    }

    for (key_item *i=keys[kev->key_code]; i; i=i->next)
      if (key_modifiers_pressed == i->modifier_flags && (i->context_mask& (1<time);

  } else if (ev->type()==i4_event::KEY_RELEASE)
  {
    CAST_PTR(kev, i4_key_press_event_class, ev);

    int old_modifiers=key_modifiers_pressed;
    get_modifiers(kev->modifiers);

    if (old_modifiers!=key_modifiers_pressed)
    {
      for (key_item *i=active_list; i; i=i->next_active)
      {
        for (key_item *j=keys[i->key]; j; j=j->next)
        {
          if (j!=i && j->modifier_flags==key_modifiers_pressed && 
              (j->context_mask & (1<time);

        }
      }

      end_actives(-1, kev->time);
    }


    end_actives(kev->key_code, kev->time);    
  }
}

static i4_bool is_white(char *s)
{
  if (*s==' ' || *s=='\n' || *s=='\r' || *s=='\t') 
    return i4_T;
  else return i4_F;
}

static void skip_white(char *&s)
{
  while (*s && is_white(s)) s++;
}

static i4_bool i4_go_key_start(char *&s)
{
  while (*s && *s!='(')
  {
    if (*s=='#') 
    {
      while (*s && (*s!='\n' && *s!='\r')) 
        s++;
    } else s++;
  }
  
  if (*s)
  {
    while (*s && *s!=' ') s++;
    skip_white(s);
    return i4_T;
  }
  else return i4_F;  
}

static char get_char(char *&s)
{
  if (*s=='\\')
  {
    s+=2;
    if (s[-1]=='n') return '\n';
    if (s[-1]=='r') return '\r';
    if (s[-1]=='t') return '\t';
    if (s[-1]=='b') return '\b';    
    if (s[-1]=='\\') return '\\';    
  }
  else 
  {
    s++;
    return s[-1];
  }
  return '\\';
}

static void i4_read_str(char *&s, char *buf)
{
  skip_white(s);
  if (s[0]=='"')
  {
    s++;
    while (*s && *s!='"')      
      *(buf++)=get_char(s);
    *buf=0;
    s++;
  }
  else
  {
    *(buf++)=*(s++);
    while (*s && !is_white(s) && *s!=')') 
      *(buf++)=get_char(s);
    *buf=0;
  }
}

int i4_key_man_class::acquire_modifiers_for_contexts(int context_mask, int mod, char *key_name)
{
  int c=context_mask, i=0, total=0, skip_this_key=0;
  while (c)
  {
    if (c&1)
    {
      if (((*context_list)[i].modifiers_used & mod))
      { 
        i4_alert(i4gets("modifier_in_use"),200, key_name);
        return 0;
      }
      else
      {
        (*context_list)[i].modifiers_used |= mod;
        (*context_list)[i].modifiers_taken |= mod;
      }
    }

    c>>=1;
    i++;
  }
 
  return 1;
}

int i4_key_man_class::use_modifiers_for_contexts(int context_mask, int mod, char *key_name)
{
 int c=context_mask, i=0;

  while (c)
  {
    if (c&1)
    {
      if (((*context_list)[i].modifiers_taken & mod))
      { 
        i4_alert(i4gets("modifier_in_use"),200, key_name);
        return 0;
      }
      else
        (*context_list)[i].modifiers_used |= mod;
    }

    c>>=1;
    i++;
  }

  return 1;
}

i4_bool i4_key_man_class::load(const i4_const_str &filename)
{
  check_init();
  i4_file_class *fp=i4_open(filename);
  if (!fp) return i4_F;

  int size=fp->size();
  void *mem=i4_malloc(size+1,"");
  fp->read(mem,size);
  delete fp;
  
  char *c=(char *)mem;
  c[size]=0;

  char tmp[256];
  
  int x=0;
  while (i4_go_key_start(c))
  {
    w16 mod;
    i4_key key;
    char key_name[256],cmd[256];
    int skip_key=0;

    x++;
    i4_read_str(c,key_name);    
    if (!i4_find_key(i4_const_str(key_name), key, mod))
    {
      i4_alert(i4gets("no_key"),100, key_name, &filename);
      skip_key=1;
    }

    i4_read_str(c,cmd);
    int id=get_command_id(cmd);

    int context_mask=0;
    do
    {
      i4_read_str(c,tmp);
      if (tmp[0] && tmp[0]!=')')
        context_mask|=(1<next)
        {        
          if (i->modifier_flags==mod && (i->context_mask & context_mask))
            i4_error("attempting to assign command %s but key %s (%d) already command %s",
                     (*command_list)[i->command_id], key_name, key, (*command_list)[id]);

        }

        keys[key]=new key_item(context_mask, id, mod, key, keys[key]);    
      }
    }
  }

  i4_free(mem);
  loaded=i4_T;
  return i4_T;
}


void i4_key_man_class::uninit()
{
  if (!command_list) return;

  i4_time_class now;
  while (active_list)
    end_actives(active_list->key, now);

  int i;
  for (i=0; inext;
      delete ki;
    }
  }

  delete command_list;  command_list=0;
  delete context_list;  context_list=0;
  delete char_heap;     char_heap=0;

  i4_kernel.unrequest_events(this, 
                             i4_device_class::FLAG_KEY_PRESS | i4_device_class::FLAG_KEY_RELEASE);
}

char *i4_key_man_class::alloc_str(char *s)
{
  int l=strlen(s)+1;
  char *t=(char *)char_heap->malloc(l,"");
  memcpy(t,s,l);
  return t;
}

void i4_key_man_class::check_init()
{
  if (!context_list)
  {
    context_list = new i4_array(32,32);
    command_list = new i4_array(32,32);
    char_heap = new i4_grow_heap_class(2048, 2048);

    i4_kernel.request_events(this, 
                             i4_device_class::FLAG_KEY_PRESS | i4_device_class::FLAG_KEY_RELEASE);
  }
}

int i4_key_man_class::get_context_id(char *context_name)
{
  check_init();
  int s=context_list->size();
  for (int i=0; isize()==32)
    i4_error("max contexts exceed with %s", context_name);

  context *c=context_list->add();
  c->name=alloc_str(context_name);
  c->modifiers_taken=0;
  c->modifiers_used=0;

  return s;
}

int i4_key_man_class::get_command_id(char *command)
{
  check_init();
  int s=command_list->size();
  for (int i=0; iadd(alloc_str(command));
  return s;
}


i4_bool i4_key_man_class::get_key_for_command(int command_id, i4_key &key, w16 &mod)
{
  for (int i=0; inext)
    {
      if ((k->context_mask & (1<command_id == command_id))
      {
        key=i;
        mod=k->modifier_flags;
        return i4_T;
      }
    }
  }

  return i4_F;
}



void i4_key_matchup_class::add(char *command, int remap)   
{ 
  matchup.insert(new command_matchup(i4_key_man.get_command_id(command), remap)); 
}

int i4_key_matchup_class::remap(int command_id) 
{
  command_matchup f=command_matchup(command_id,0);
  command_matchup *m=matchup.find(&f);
  if (m)
    return m->remap_id;
  else return -1;
}