#include <platform/platform.h>
#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "event.h"
#include "devices.h"
#include <malloc.h>
#include <time.h>
#include "memman.h"
#include <setjmp.h>
#include <signal.h>
#include <assert.h>
#include <string.h>


#define find_event_msg(where,what,res) \
     {\
     res=(where);\
     while (res!=NULL && res->event_msg!=(what)) res=res->next;\
     }
#define find_event_proc(where,what,res) \
     {\
     res=(where);\
     while (res!=NULL && res->proc!=(what)) res=res->next;\
     }

#define find_event_msg_proc(where,xmsg,xproc,res) \
     {\
     T_EVENT_ROOT *pt;     \
     find_event_msg(where,xmsg,pt);\
     if (pt) find_event_proc(pt->list,xproc,res) else res=NULL;\
     }



char exit_wait=0;
T_EVENT_ROOT *ev_tree=NULL;
char freeze_on_exit=0;
int ev_poz=0;
char *otevri_zavoru;

void **tasklist_sp;
void **tasklist_low;
void **tasklist_top;
int *tasklist_events;
char *task_info;
int taskcount=0;
int foretask=0;
int nexttask=0;
int32_t taskparam;

int32_t err_last_stack;
void *err_to_go;


T_EVENT_ROOT *add_event_message(T_EVENT_ROOT **tree,int msg)
  {
  T_EVENT_ROOT *r,*r1;

  if (*tree==NULL)
     {
     *tree=getmem(sizeof(T_EVENT_ROOT));
     r=*tree;
     r->next=NULL;
     }
  else
     {
     r=getmem(sizeof(T_EVENT_ROOT));
     r1=*tree;
     while (r1->next!=NULL) r1=r1->next;
     r->next=NULL;
     r1->next=r;
     }
  r->event_msg=msg;
  r->used=0;
  r->list=NULL;
  return r;
  }

T_EVENT_POINT *add_event(T_EVENT_ROOT **tree,int msg,EV_PROC proc,char end)
  {
  T_EVENT_ROOT *r;
  T_EVENT_POINT *p;

  find_event_msg(*tree,msg,r);
  if (r==NULL)
        {
        r=add_event_message(tree,msg);
        p=r->list=New(T_EVENT_POINT);
        p->next=NULL;
        }
  else if (end && r->list!=NULL)
     {
     T_EVENT_POINT *q=r->list;
     p=getmem(sizeof(T_EVENT_POINT));
     while (q->next!=NULL) q=q->next;
     p->next=NULL;
     q->next=p;
     }
  else
     {
     p=getmem(sizeof(T_EVENT_POINT));
     p->next=r->list;
     r->list=p;
     }
  p->proc=proc;
  p->nezavora=1;
  p->nezavirat=0;
  p->user_data=NULL;
  p->calls=0;
  return p;
  }

void delete_event_msg(T_EVENT_ROOT **tree,int msg)
  {
  T_EVENT_ROOT *r;

  r=*tree;
  if (r==NULL) return;
  if (r->event_msg==msg)
     {
     if (r->used) return;
     *tree=r->next;
     free(r);
     }
  else
     {
     T_EVENT_ROOT *p;
     while ((p=r->next)!=NULL && p->event_msg!=msg) r=p;
     if (p!=NULL)
        {
        if (p->used) return;
        r->next=p->next;
        free(p);
        }
     }
  }

void delete_event(T_EVENT_ROOT **tree,int msg,EV_PROC proc)
  {
  T_EVENT_ROOT *r;
  T_EVENT_POINT *p;

  find_event_msg(*tree,msg,r);
  if (r==NULL) return;
  p=r->list;
  if (p->proc==proc)
     {
     r->list=p->next;
     free(p);
     }
  else
     {
     T_EVENT_POINT *q;
     while ((q=p->next)!=NULL && q->proc!=proc) p=q;
     if (q!=NULL)
        {
        p->next=q->next;
        free(q);
        }
     }
  if (r->list==NULL) delete_event_msg(tree,msg);
  }

void force_delete_curr (T_EVENT_ROOT **tree,T_EVENT_ROOT *r, T_EVENT_POINT *p)
  {
  T_EVENT_POINT *q;


  q=r->list;
  if (q==p)
     {
     r->list=p->next;
     free(p);
     tree;
     if (r->list==NULL) delete_event_msg(tree,r->event_msg);
     }
  else
     {
     while (q->next!=p) q=q->next;
     q->next=p->next;
     free(p);
     }
  }


void enter_event(T_EVENT_ROOT **tree,EVENT_MSG *msg)
  {
  T_EVENT_ROOT *r;
  T_EVENT_POINT *p,*s;
  int ev=msg->msg;

  find_event_msg(*tree,msg->msg,r);
  if (r!=NULL)
     {
     r->used++;
     s=r->list;
     for(p=r->list;p!=NULL;)
        {
        s=p->next;
        if (p->proc!=NULL && p->nezavora)
           {
           T_EVENT_POINT *z=p;
           if (p->proc==PROC_GROUP) z=(T_EVENT_POINT *)p->user_data;
           p->nezavora=p->nezavirat;
           otevri_zavoru=&p->nezavora;
           p->calls++;
           EVENT_MSG cpy;
           cpy.msg = msg->msg;
           va_copy(cpy.data, msg->data);
           z->proc(&cpy,&(z->user_data));
           va_end(cpy.data);
           p->calls--;
           p->nezavora=1;
           if (cpy.msg==-2)
              {
              p->proc=NULL;
              cpy.msg=ev;
              }
           s=p->next;
           if (!p->calls && p->proc==NULL)
              force_delete_curr(tree,r,p);
           if (cpy.msg==-1)
               break;
           }
/*        if (p->next!=s)
           if (r->list!=p)
              {
              for(q=r->list;q!=NULL;q=q->next)
                 if (q->next==p)
                    {
                    s=p->next;
                    break;
                    }
                 else if (q->next==s) break;
              break;
              }
           else
              s=p->next;*/
        p=s;
        }
     r->used--;
  /*   for(p=r->list;p!=NULL;)
        {
        s=p->next;
        if (p->proc==NULL)
              force_delete_curr(tree,r,p);
        p=s;
        }*/
     }
  unsuspend_task(msg);
  }

typedef struct call_proc_context_t {
    EV_PROC proc;
    void **user;
}call_proc_context;

static int call_proc(EVENT_MSG *msg, void *ctx) {
    call_proc_context *c = ctx;
    c->proc(msg, c->user);
    return 0;
}

T_EVENT_POINT *install_event(T_EVENT_ROOT **tree, EVENT_MSG *msg, EV_PROC proc,char end)
//instaluje novou udalost;
  {
  void *user=NULL;
  T_EVENT_POINT *p;
  int ev_num = msg->msg;
  msg->msg = E_INIT;
  proc(msg, &user);
  p=add_event(tree,ev_num,proc,end);
  p->user_data=user;
  return p;
   }

void deinstall_event(T_EVENT_ROOT **tree,int32_t ev_num,EV_PROC proc,void *procdata)
//deinstaluje udalost;
  {
  T_EVENT_ROOT *r;
  T_EVENT_POINT *p;

  find_event_msg(*tree,ev_num,r);
  if (r==NULL) return;
  find_event_proc(r->list,proc,p);
  if (p==NULL) return;
  call_proc_context ctx = {proc, &p->user_data};
  send_message_to(call_proc, &ctx, E_DONE, procdata);
  if (p->user_data!=NULL) free(p->user_data);
  p->proc=NULL;
  p->user_data=NULL;
  if (!p->calls) force_delete_curr(tree,r,p);
  }

typedef void (*initproc)();

void tree_basics(T_EVENT_ROOT **ev_tree,EVENT_MSG *msg)
{
  initproc q;

  if (msg->msg==E_ADD || msg->msg==E_ADDEND)
     {
     T_EVENT_POINT *r;
     shift_message(msg);
     EV_PROC proc = va_arg(msg->data, EV_PROC);
     find_event_msg_proc(*ev_tree,msg->msg,proc,r);
     assert(r==NULL);
     if (r==NULL)
        install_event(ev_tree,msg,proc,msg->msg==E_ADDEND);
     return;
     }
     
  if (msg->msg==E_INIT)
     {      
      q = va_arg(msg->data, initproc);
      q();
     return;
     }
  if (msg->msg==E_DONE)
     {
     shift_message(msg);
     EV_PROC proc = va_arg(msg->data, EV_PROC);
     void *procdata = va_arg(msg->data, void *);
     deinstall_event(ev_tree,msg->msg,proc,procdata);
     return;
     }
/*  if (msg->msg==E_GROUP)
     {
     int pocet,i,addev;
     T_EVENT_POINT *pp;
     EVENT_MSG *tgm=&tg;

     shift_message(msg);
     addev = msg->msg;
     if (addev!=E_ADD || addev!=E_ADDEND) return;
     shift_message(msg);
     int pocet = msg->msg;
     va_list va_procdata;
     va_copy(tmp, msg->data);
     for (int i = 0; i < pocet; ++i) va_arg(va_procdata, EV_PROC);
     for (int i = 0; i < pocet; ++i) {
         EV_PROC proc = va_arg(msg->data, EV_PROC);
         void *procdata = va_arg(va_procdata, void *)
         if (i == 0) {
             pp=install_event(ev_tree,proc,procdata,((int32_t *)proc+1),addev==E_ADDEND);
         }
     }

     int32_t *p;void *proc;
     shift_msg(msg,tg);addev=tg.msg;
     shift_msg(tgm,tg);
     pocet=tg.msg;
     p=tg.data;proc=p+pocet*sizeof(int32_t);
     for(i=0;i<pocet;i++,p++) if (i!=0)
                                {
                                T_EVENT_POINT *q;
                                q=add_event(ev_tree,*p,proc,addev==E_ADDEND);
                                q->user_data=(void *)pp;
                                q->proc=PROC_GROUP;
                                }
        else
           pp=install_event(ev_tree,*p,proc,((int32_t *)proc+1),addev==E_ADDEND);
     }
*/

}

int send_message_to(int (*cb)(EVENT_MSG *, void *), void *ctx, int message, ...) {
    EVENT_MSG x;
    x.msg = message;
    va_start(x.data, message);
    int r = cb(&x, ctx);
    va_end(x.data);
    return r;
}

static void send_message_to_tree(EVENT_MSG *x) {
    if (x->msg==E_ADD || x->msg==E_INIT || x->msg==E_DONE) {
        tree_basics(&ev_tree,x);
    }
    else {
        enter_event(&ev_tree,x);
    }
}

void send_message(int message,...)
  {
    EVENT_MSG x;
    x.msg = message;
    va_start(x.data, message);
    send_message_to_tree(&x);
    va_end(x.data);
  }




void timer(EVENT_MSG *msg)
  {
  static uint32_t lasttime=0;
  if (msg->msg==E_WATCH)
     {
     uint32_t tm=get_game_tick_count()/TIMERSPEED;
     if (tm==lasttime) return;
     lasttime=tm;
     send_message(E_TIMER,tm);
     return;
     }
  }

void tasker(EVENT_MSG *msg,void **_)
  {


  switch (msg->msg)
     {
     case E_INIT:
/*           tasklist_sp=New(void *);
           tasklist_low=New(void *);
           tasklist_top=New(void *);
           task_info=New(char);
           taskcount=1;
           memset(task_info,0,taskcount);*/
           break;
     case E_WATCH:
     case E_IDLE:
     default:
           if (q_any_task()>=1)
              task_sleep();
           break;
     case E_DONE:
           {
/*           int i;
           memset(task_info,1,taskcount);
           do
              {
              for (i=1;i<taskcount;i++)
                 if (tasklist_sp[i]!=NULL) break;
              if (i!=taskcount) task_sleep();
              }
           while (i<taskcount);
           free(tasklist_sp);
           free(tasklist_low);
           free(task_info);*/
           }
           break;
     }
  }


/*void except_free_stack(void *ptr);
#pragma aux except_free_stack parm [eax]=\
     "mov  esp,eax"\
     "sub  esp,10h"\
     "popf"\
     "pop  ax"\
     "mov  ebp,esp"\
     "lss  esp,[ebp]"\
     "mov  ebp,esp"


void except_GPF()
#pragma aux except_GPF parm[]
  {
  raise_error(ERR_MEMREF);
  }

*/

void init_events()
  {
  send_message(E_ADD,E_WATCH,keyboard);
  send_message(E_ADD,E_WATCH,timer);
  send_message(E_ADD,E_WATCH,tasker);
  }

static char do_events_called=0;

void do_events()
  {
  do_events_called=1;
  if (!q_is_mastertask()) task_sleep();
  else
     {
     send_message(E_WATCH);
     send_message(E_IDLE);
     }
  }


void error_show(int error_number)
  {
  send_message(E_PRGERROR,&error_number);
  if (!error_number) abort();
  }


void escape()
  {

  exit_wait=0;
  do
     {
     send_message(E_WATCH);
     send_message(E_IDLE);
     if (do_events_called==0)  ShareCPU();
     else do_events_called=0;
     }
  while (!exit_wait);
  exit_wait=0;
  }

T_EVENT_ROOT *gate_basics(EVENT_MSG *msg, void **user_data)
  {
  T_EVENT_ROOT *p;

  memcpy(&p,user_data,4);
  shift_message(msg);
  if (msg->msg==E_ADD || msg->msg==E_INIT || msg->msg==E_DONE)
      tree_basics((T_EVENT_ROOT **)user_data,msg);
  return p;
  }
