/**********************************************************************
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/kernel.hh"
#include "memory/malloc.hh"
#include "error/alert.hh"
#include "device/device.hh"
#include "device/event.hh"
#include "time/time.hh"
#include "threads/threads.hh"
#include "isllist.hh"

enum { I4_SHOW_NONE,
       I4_SHOW_ALL,
       I4_SHOW_NON_TRIVAL,        // excludes mouse move & window messages     
};

int i4_show_events=I4_SHOW_NONE;

i4_kernel_device_class i4_kernel;


struct defered_event
{
  i4_event_handler_reference_class send_to;
  defered_event *next;
  i4_event *ev_copy;
  defered_event(i4_event_handler_class *_send_to, 
                i4_event *ev) : ev_copy(ev->copy())
  {
    send_to.reference(_send_to);
  }

  ~defered_event()
  {
    delete ev_copy;
  }

};

  
struct event_handler_delete_node
{
  i4_event_handler_class *who;
  event_handler_delete_node *next;
  event_handler_delete_node(i4_event_handler_class *who)
    : who(who) {}
};


static defered_event *defered_list;



static i4_critical_section_class list_lock;
static i4_isl_list list;
typedef i4_isl_list::iterator def_iter;

static i4_isl_list eh_delete_list;
static i4_time_class last_user_input;

// r includes event and who to send to
void i4_kernel_device_class::send(i4_event_reaction_class *r)
{
  if (r && r->handler_reference.get())
    send_event(r->handler_reference.get(), r->event);
}



#ifdef DEBUG
void i4_kernel_device_class::show_pending()
{
  list_lock.lock();

  i4_isl_list::iterator i=list.begin();

  for (;i!=list.end();++i)
    i4_warning("'%s' for '%s'",i->ev_copy->name(), i->send_to.get()->name());    
  
  list_lock.unlock();
}
#endif

void i4_kernel_device_class::deque_events(i4_event_handler_class *for_who)
{
  list_lock.lock();

  i4_isl_list::iterator i=list.begin(), last=list.end(), q;
  for (; i!=list.end(); )
  {
    if (i->send_to.get()==for_who)
    {
      if (last==list.end())
        list.erase();
      else
        list.erase_after(last);
      

      q=i;
      ++i;

      list_lock.unlock();      // in case deleted events send events
      delete &*q;
      list_lock.lock();
    }
    else
    {
      last=i;
      ++i;
    }
  }
  
  list_lock.unlock();
}


void i4_kernel_device_class::send_event(i4_event_handler_class *send_to, i4_event *ev)
{


  if (ev->when()!=i4_event::NOW)
  {   
#ifndef I4_RETAIL
    if (i4_show_events==I4_SHOW_ALL || 
        (i4_show_events==I4_SHOW_NON_TRIVAL && 
         !(ev->type()==i4_event::MOUSE_MOVE || ev->type()==i4_event::WINDOW_MESSAGE)))
      i4_warning("queing : '%s' to '%s'",ev->name(), send_to->name());
#endif
    defered_event *dv=new defered_event(send_to,ev);

    list_lock.lock(); 
    list.insert_end(*dv);   
    list_lock.unlock();
  } else 
  {
#ifndef I4_RETAIL
    if (i4_show_events==I4_SHOW_ALL || 
        (i4_show_events==I4_SHOW_NON_TRIVAL && 
         !(ev->type()==i4_event::MOUSE_MOVE || ev->type()==i4_event::WINDOW_MESSAGE)))
      i4_warning("sending : '%s' to '%s'",ev->name(), send_to->name());
#endif

    send_to->call_stack_counter++;
    events_sent++;
    send_to->receive_event(ev);
    send_to->call_stack_counter--;
  }  
}

i4_bool i4_kernel_device_class::process_events()       // returns true if an event was dispatched  
{
  i4_bool ret=i4_F;
  for (i4_device_class *d=device_list; d; d=d->next)
    if (d->process_events())
      ret=i4_T;

  ret=(i4_bool)(flush_events() | ret); 

  check_for_idle();

  return ret;
}


i4_bool i4_kernel_device_class::flush_events()
{


  i4_bool ret=i4_F;

  // send any events that were qued
  while (list.begin()!=list.end())
  {
    list_lock.lock();
    i4_isl_list::iterator old=list.begin();
    list.erase();
    list_lock.unlock();

    // make sure event handler is still around..
    i4_event_handler_class *eh=old->send_to.get();   
    if (eh)
    {
#ifdef DEBUG
      i4_event *ev=old->ev_copy;
      if (i4_show_events==I4_SHOW_ALL || 
          (i4_show_events==I4_SHOW_NON_TRIVAL && 
           !(ev->type()==i4_event::MOUSE_MOVE || ev->type()==i4_event::WINDOW_MESSAGE)))
        i4_warning("sending : '%s' to '%s'",old->ev_copy->name(), eh->name());
#endif

      eh->call_stack_counter++;
      events_sent++;
      eh->receive_event(old->ev_copy);
      eh->call_stack_counter--;
    }

    delete &*old;

    ret=i4_T;
  }


  list_lock.lock();
  // delete any event handlers that are qued for deletion
  i4_isl_list::iterator i=eh_delete_list.begin(), 
    last=eh_delete_list.end(), q;


  while (i!=eh_delete_list.end())
  {
    if (!i->who->thinking())
    {
      q=i;
      ++i;
      
      if (last!=eh_delete_list.end())
        eh_delete_list.erase_after(last);
      else
        eh_delete_list.erase();

      list_lock.unlock();     // unlock because deleted object might send events

      delete q->who;
      delete &*q;

      list_lock.lock();
    }
    else ++i;
  }


  list_lock.unlock();

  return ret;
}


void i4_kernel_device_class::request_events(i4_event_handler_class *for_who, w32 event_types)
{
  int type=0;
  while (event_types)
  {
    if (event_types&1)
      response[type]=new response_type(for_who, response[type]);

    event_types>>=1;    
    type++;
  }
}

// unrequest_events tells any devices sending event_types events to you to stop
void i4_kernel_device_class::unrequest_events(i4_event_handler_class *for_who, w32 event_types)
{
  int type=0;
  while (event_types)
  {
    if (event_types&1)
    {
      response_type *last=0, *p;
      for (p=response[type]; p && p->who!=for_who; p=p->next)
        last=p;
        
        
      
      if (!p)
        i4_error("unrequesting events & not installed");
      else
      {
        //        for_who->dereference();
        if (response[type]->who==for_who)
          response[type]=response[type]->next;
        else
          last->next=p->next;

        delete p;
      }
    }
    event_types>>=1;
    type++;
  }
}

void i4_kernel_device_class::add_device(i4_device_class *device)       // returns 16 bits device id
{
  device->next=device_list;
  device_list=device;
}

void i4_kernel_device_class::remove_device(i4_device_class *device)
{
  i4_device_class *last=0, *p;
  for (p=device_list; p && p!=device; p=p->next)
    last=p;

  if (!p)
    i4_error("remove device : device not found");
  if (last)
    last->next=device->next;
  else
    device_list=device->next;
}


void i4_kernel_device_class::not_idle()
{
  last_user_input.get();
}

void i4_kernel_device_class::check_for_idle()
{
  if (can_send_idle)
  {
    i4_time_class now;
    if (now.milli_diff(last_user_input)>milliseconds_before_idle_events_sent)
    {
      i4_user_idle_event_class uiev;
      broadcast_event_type(&uiev, i4_device_class::FLAG_IDLE);
      can_send_idle=i4_F;
    }  
  }
}

void i4_kernel_device_class::set_milliseconds_before_idle_events_sent(w32 milli_seconds)
{
  milliseconds_before_idle_events_sent=milli_seconds;
  check_for_idle();
}

i4_kernel_device_class::i4_kernel_device_class()
{
  events_sent=0;
  can_send_idle=i4_F;
  milliseconds_before_idle_events_sent=1000;

  memset(response,0,sizeof(response));
  device_list=0;
}


void i4_kernel_device_class::broadcast_event_type(i4_event *ev, w32 event_type)
{
  if (event_type & (i4_device_class::FLAG_MOUSE_MOVE | 
                    i4_device_class::FLAG_MOUSE_BUTTON_DOWN | 
                    i4_device_class::FLAG_MOUSE_BUTTON_UP |
                    i4_device_class::FLAG_KEY_PRESS |                    
                    i4_device_class::FLAG_KEY_RELEASE))
  {
    can_send_idle=i4_T;
    last_user_input.get();      
  }

  int type=0;
  while ((event_type&1)==0)
  {
    event_type>>=1;
    type++;
  }

  for (response_type *r=response[type]; r; r=r->next)
    send_event(r->who, ev);

}


void i4_kernel_device_class::delete_handler(i4_event_handler_class *handler)
{
  if (handler->thinking())
    eh_delete_list.insert(*(new event_handler_delete_node(handler)));
  else
  {
    //i4_warning("need to fix this handler deletion thing, trey");
    ///*
    if (!valid_ptr(handler))
    {
      valid_ptr(handler);
      i4_warning("bad handler pointer");
    }
    else
    //*/
      delete handler;
  }
}