
#include <types.h>
#include <core.h>
#include <spinlock.h>
#include <link.h>
#include <mm.h>
#include <slab.h>
#include <pe.h>

#pragma pack(push,4)
typedef struct
{
  char     app_name[16];
  addr_t   fpu_state;                       /*      +16       */
  count_t  ev_count;                        /*      +20       */
  addr_t   fpu_handler;                     /*      +24       */
  addr_t   sse_handler;                     /*      +28       */
  addr_t   pl0_stack;                       /*      +32       */

  addr_t   heap_base;                       /*      +36       */
  addr_t   heap_top;                        /*      +40       */
  addr_t   cursor;                          /*      +44       */
  addr_t   fd_ev;                           /*      +48       */
  addr_t   bk_ev;                           /*      +52       */
  addr_t   fd_obj;                          /*      +56       */
  addr_t   bk_obj;                          /*      +60       */
  addr_t   saved_esp;                       /*      +64       */
  addr_t   io_map[2];                       /*      +68       */

  u32_t    dbg_state;                       /*      +76       */
  char    *cur_dir;                         /*      +80       */
  count_t  wait_timeout;                    /*      +84       */
  addr_t   saved_esp0;                      /*      +88       */

  link_t   dll_list;                        /*      +92       */

  u32_t    reserved0[7];                    /*      +100   db 28 dup(?)  */

  addr_t   wnd_shape;                       /*      +128      */
  u32_t    wnd_shape_scale;                 /*      +132      */
  u32_t    reserved1;                       /*      +136      */
  size_t   mem_size;                        /*      +140      */
}appdata_t;
#pragma pack(pop)


extern appdata_t *current_slot;

bool link_pe(addr_t img_base);

int __stdcall strncmp(const char *s1, const char *s2, size_t n);

extern int __stdcall mnt_exec(void *raw, size_t raw_size, char *path,
              char *cmdline, u32_t flags) asm ("mnt_exec");

dll_t core_dll;

slab_cache_t *dll_slab;

static char* strupr(char *str )
{
    char *p;
    unsigned char c;

    p = str;
    while( (c = *p) )
    {
        if( c >= 'a' && c <= 'z' )
            *p = c - 'a' + 'A';
        ++p;
    }

    return( str );
}

void * memcpy(void * _dest, const void *_src, size_t _n)
{
int d0, d1, d2;
 __asm__ __volatile__(
	"rep ; movsl\n\t"
	"testb $2,%b4\n\t"
	"je 1f\n\t"
	"movsw\n"
	"1:\ttestb $1,%b4\n\t"
	"je 2f\n\t"
	"movsb\n"
	"2:"
	: "=&c" (d0), "=&D" (d1), "=&S" (d2)
	:"0" (_n/4), "q" (_n),"1" ((long)_dest),"2" ((long)_src)
	: "memory");
 return (_dest);
}

size_t strlen(const char *str)
{
int d0;
register int __res;
__asm__ __volatile__(
	"repne\n\t"
	"scasb\n\t"
	"notl %0\n\t"
	"decl %0"
	:"=c" (__res), "=&D" (d0) :"1" (str),"a" (0), "0" (0xffffffff));
return __res;
}

void init_core_dll()
{
    PIMAGE_DOS_HEADER        dos;
    PIMAGE_NT_HEADERS32      nt;
    PIMAGE_EXPORT_DIRECTORY  exp;

    dos =  (PIMAGE_DOS_HEADER)LOAD_BASE;
    nt  =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);
    exp =  MakePtr(PIMAGE_EXPORT_DIRECTORY,LOAD_BASE,
                   nt->OptionalHeader.DataDirectory[0].VirtualAddress);

    list_initialize(&core_dll.link);

    core_dll.img_base = LOAD_BASE;
    core_dll.img_size = nt->OptionalHeader.SizeOfImage;
    core_dll.img_md   = NULL;

    core_dll.img_hdr  = nt;
    core_dll.img_sec  = MakePtr(PIMAGE_SECTION_HEADER,nt, sizeof(IMAGE_NT_HEADERS32));
    core_dll.img_exp  = MakePtr(PIMAGE_EXPORT_DIRECTORY,LOAD_BASE,
                        nt->OptionalHeader.DataDirectory[0].VirtualAddress);
    core_dll.img_name = strupr(MakePtr(char*, LOAD_BASE, exp->Name));

    dll_slab = slab_cache_create(sizeof(dll_t), 16,NULL,NULL,SLAB_CACHE_MAGDEFERRED);

    DBG("%s base %x size %x sections %d exports %x\n",
        core_dll.img_name, core_dll.img_base,
        core_dll.img_size, nt->FileHeader.NumberOfSections,
        core_dll.img_exp );
};


dll_t * find_dll(link_t *list, const char *name)
{
    dll_t* dll = (dll_t*)list;

    do
    {
        if( !strncmp(name,dll->img_name,16))
            return dll;

        dll = (dll_t*)dll->link.next;

    }while(&dll->link !=  list);

    return NULL;
};


typedef struct
{
  char         srv_name[16];  //        ASCIIZ string
  u32_t        magic;         // +0x10  'SRV '
  size_t       size;          // +0x14  size of structure SRV
  void        *fd;            // +0x18  next SRV descriptor
  void        *bk;            // +0x1C  prev SRV descriptor
  addr_t       base;          // +0x20  service base address
  addr_t       entry;         // +0x24  service START function
  void        *srv_proc;      // +0x28  main service handler
}srv_t;

typedef srv_t* __stdcall  drv_entry_t(int);

srv_t* __fastcall load_pe_driver(const char *path)
{
    PIMAGE_DOS_HEADER     dos;
    PIMAGE_NT_HEADERS32   nt;

    drv_entry_t   *drv_entry;
    addr_t        *img_base ;
    srv_t         *srv;

    img_base = load_image(path);

    if( ! img_base )
        return 0;

    if( link_image( img_base ) )
    {
        dos = (PIMAGE_DOS_HEADER)img_base;
        nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);

        drv_entry = MakePtr(drv_entry_t*, img_base,
                            nt->OptionalHeader.AddressOfEntryPoint);

        srv = drv_entry(1);

        if(srv != NULL)
            srv->entry = nt->OptionalHeader.AddressOfEntryPoint + img_base;

        return srv;
    }
    else
    {
        mem_free( img_base );
        return NULL;
    }
}

typedef struct
{
    int a_type;
    union
    {
        long  a_val;
        void *a_ptr;
        void  (*a_fcn)( ) ;
    }a_un;
}auxv_t;

#define AUX_COUNT       0

typedef struct
{
    int     argc;       /*  always 2                                */
    char   *path;       /*  argv[0]   program path                  */
    char   *cmdline;    /*  argv[1]   command  line. May be null    */
    u32_t   sep1;       /*  separator.  must be zero                */
    char   *env;        /*  single environment string               */
    u32_t   sep2;       /*  separator.  must be zero                */
    auxv_t  aux[1];     /*  aux. AT_NULL for now                    */
}exec_stack_t;


addr_t __fastcall pe_app_space(size_t size);

int __stdcall pe_app_param(char *path, void *raw, addr_t ex_pg_dir,
                          exec_stack_t *ex_stack) asm ("pe_app_param");

int sys_exec(char *path, char *cmdline, u32_t flags)
{
    PIMAGE_DOS_HEADER     dos;
    PIMAGE_NT_HEADERS32   nt;

    size_t   img_size;
    count_t  img_pages;
    count_t  img_tabs;
    addr_t        ex_pg_dir;
    addr_t        ex_stack_page;
    addr_t        ex_pl0_stack;

    exec_stack_t *ex_stack;
    int           stack_size;
    char         *ex_path;
    char         *ex_cmdline = NULL;

    size_t        raw_size;
    u32_t        *raw;

    int pathsize = 0;
    int cmdsize  = 0;
    int envsize  = 0;

    u32_t tmp;

    DBG("\nexec %s cmd %s flags %x\n", path, cmdline, flags);

    if( ! path)
    {
        DBG("invalid path\n");
        return;
    };

    raw = load_file(path, &raw_size);

    if( ! raw )
        return -5;                                      /* FIXME */

    if( (raw[0] == 0x554E454D) &&
        ( ( raw[1] == 0x31305445) ||
          ( raw[1] == 0x30305445) ) )

    {
        DBG("leagacy Kolibri application\n");
        int tmp =  mnt_exec(raw, raw_size, path, cmdline, flags);
        DBG("pid %x\n",tmp);
        return tmp;
    }

    if( ! validate_pe(raw, raw_size, true) )
    {
        DBG("invalid executable file %s\n", path);
        mem_free(raw);
        return -31;
    }

    dos = (PIMAGE_DOS_HEADER)raw;
    nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);

    pathsize = strlen(path)+1;

    if( cmdline )
        cmdsize = strlen(cmdline)+1;

    stack_size = sizeof(exec_stack_t) + pathsize +
                 cmdsize + envsize + AUX_COUNT*sizeof(auxv_t);

    stack_size = (stack_size + 15) & ~15;               /* keep stack aligned */

    DBG("stacksize %d\n", stack_size);

    if( stack_size > 4096 )
    {
        DBG("command line too long\n");
        return -30;
    }

    ex_stack_page  = alloc_page();                   /* 2^0 = 1 page   */
    if( ! ex_stack_page )
    {
        mem_free(raw);
        return -30;                                    /* FIXME          */
    };

    dos = (PIMAGE_DOS_HEADER)raw;
    nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);

    img_size  =  nt->OptionalHeader.SizeOfImage;

    ex_pg_dir      = pe_app_space(img_size);

    if( !ex_pg_dir )
    {
        frame_free(ex_stack_page);
        mem_free(raw);
        return -30;                                    /* FIXME          */
    };

    __asm__ __volatile__ (
    "xorl %%eax, %%eax      \n\t"
    "rep stosl"
    :"=c"(tmp),"=D"(tmp)
    :"c"(1024),"D"(ex_stack_page + OS_BASE)
    :"eax","cc");

    ex_stack = (exec_stack_t*)(ex_stack_page + OS_BASE
                               + PAGE_SIZE - stack_size);
    ex_stack->argc = 2;

    ex_path = MakePtr(char*, ex_stack, sizeof(exec_stack_t)+AUX_COUNT*sizeof(auxv_t));

    memcpy(ex_path, path, pathsize);
    ex_stack->path = (char*)(((addr_t)ex_path & 0xFFF) + 0x7FFFF000);  /* top of stack */

    if( cmdline )
    {
        ex_cmdline = ex_path + pathsize;
        memcpy(ex_cmdline, cmdline, cmdsize);
        ex_stack->cmdline = ex_stack->path + pathsize;
    };

/*
    ex_stack.env = null
    ex_stack.aux[0] = AT_NULL
 */

    DBG("create stack at %x\n\tpath %x\n\tcmdline %x\n",
         ex_stack, ex_stack->path, ex_stack->cmdline);

    return pe_app_param(path, raw, ex_pg_dir, ex_stack);
};


typedef struct
{
    u32_t edi;
    u32_t esi;
    u32_t ebp;
    u32_t esp;
    u32_t ebx;
    u32_t edx;
    u32_t ecx;
    u32_t eax;
    u32_t eip;
    u32_t cs;
    u32_t eflags;
    u32_t pe_sp;
    u32_t pe_ss;
}thr_stack_t;

void sys_app_entry(addr_t raw, thr_stack_t *thr_stack, exec_stack_t *ex_stack)
{
    PIMAGE_DOS_HEADER     dos;
    PIMAGE_NT_HEADERS32   nt;

    size_t   img_size;
    count_t  img_pages;
    size_t   stack_size;
    addr_t   img_stack;
    addr_t  *pte;

    count_t  i;
    u32_t    tmp;

    __asm__ __volatile__ ("sti");

    dos = (PIMAGE_DOS_HEADER)raw;
    nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);

    img_size  =  nt->OptionalHeader.SizeOfImage;

    current_slot->mem_size  = img_size;

    list_initialize(&current_slot->dll_list);

    pte = (addr_t*)page_tabs;
    img_pages = img_size >> 12;

    stack_size = (nt->OptionalHeader.SizeOfStackReserve + 4095) & ~4095;
    img_stack = 0x7FFFF000 - stack_size;
    stack_size>>= 12;

    while (img_pages--)
        *pte++ = 2;

    pte = &((addr_t*)page_tabs)[img_stack>>12];

    while(stack_size--)
        *pte++ = 0x02;

    addr_t stack_page = ((addr_t)ex_stack-OS_BASE) & ~4095;

    *pte = stack_page | 7;

    create_image(0, raw, false);

    init_user_heap();

    if (! link_pe(0))
    {
        DBG("\nunresolved imports\nexit\n");
        __asm__ __volatile__ (
        "int $0x40"::"a"(-1));
    };


 //   __asm__ __volatile__ (
 //   "xchgw %bx, %bx");

    addr_t entry = nt->OptionalHeader.AddressOfEntryPoint +
                   nt->OptionalHeader.ImageBase;

    thr_stack->edi = 0;
    thr_stack->esi = 0;
    thr_stack->ebp = 0;
    thr_stack->ebx = 0;
    thr_stack->edx = 0;
    thr_stack->ecx = 0;
    thr_stack->eax = 0;
    thr_stack->eip = entry;
    thr_stack->cs  = sel_app_code;
    thr_stack->eflags = EFL_IOPL3 | EFL_IF;
    thr_stack->pe_sp = 0x7FFFF000 + ((u32_t)ex_stack & 0xFFF);
    thr_stack->pe_ss = sel_app_data;

};

void* __stdcall user_alloc(size_t size) asm("user_alloc");
void  __stdcall user_free(void *mem) asm("user_free");

dll_t* __fastcall load_dll(const char *path)
{
    PIMAGE_DOS_HEADER        dos;
    PIMAGE_NT_HEADERS32      nt;
    PIMAGE_EXPORT_DIRECTORY  exp;

    md_t    *img_md;

    size_t   img_size;
    addr_t   img_base;
    count_t  img_pages;

    size_t   raw_size = 0;
    void    *raw;

    DBG("\nload dll %s", path);

    raw = load_file(path, &raw_size);

    DBG("  raw = %x\n", raw);

    if( ! raw)
    {
        DBG("file not found: %s\n", path);
        return NULL;
    };

    if( ! validate_pe(raw, raw_size, false) )
    {
        DBG("invalid pe file %s\n", path);
        mem_free(raw);
        return NULL;
    }

    dos = (PIMAGE_DOS_HEADER)raw;
    nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);

    img_size  =  nt->OptionalHeader.SizeOfImage;

    img_base = (addr_t)user_alloc(img_size);
    if( !img_base)
    {
        mem_free(raw);
        return NULL;
    };

    dll_t *dll = (dll_t*)slab_alloc(dll_slab,0);         /* FIXME check */
    if( !dll)
    {
        mem_free(raw);
        user_free((void*)img_base);
        return NULL;
    };

    create_image(img_base, (addr_t)raw, false);

    mem_free(raw);

    dos = (PIMAGE_DOS_HEADER)img_base;
    nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);
    exp =  MakePtr(PIMAGE_EXPORT_DIRECTORY,img_base,
                   nt->OptionalHeader.DataDirectory[0].VirtualAddress);

    dll->img_base = img_base;
    dll->img_size = nt->OptionalHeader.SizeOfImage;
    dll->img_md   = NULL;

    dll->img_hdr  = nt;
    dll->img_sec  = MakePtr(PIMAGE_SECTION_HEADER,nt, sizeof(IMAGE_NT_HEADERS32));
    dll->img_exp  = MakePtr(PIMAGE_EXPORT_DIRECTORY,img_base,
                            nt->OptionalHeader.DataDirectory[0].VirtualAddress);
    dll->img_name = strupr(MakePtr(char*, img_base, exp->Name));

    list_insert(&current_slot->dll_list, &dll->link);

    return dll;
};

bool link_pe(addr_t img_base)
{
    PIMAGE_DOS_HEADER     dos;
    PIMAGE_NT_HEADERS32   nt;
    char path[128];

    int warn = 0;

/* assumed that image is valid */

    dos = (PIMAGE_DOS_HEADER)img_base;
    nt =  MakePtr( PIMAGE_NT_HEADERS32, dos, dos->e_lfanew);

    if(nt->OptionalHeader.DataDirectory[1].Size)
    {
        PIMAGE_IMPORT_DESCRIPTOR imp;

        imp = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, img_base,
                      nt->OptionalHeader.DataDirectory[1].VirtualAddress);

        while ( 1 )
        {
            PIMAGE_THUNK_DATA32     thunk;

            PIMAGE_DOS_HEADER       expdos;
            PIMAGE_NT_HEADERS32     expnt;
            PIMAGE_EXPORT_DIRECTORY exp;

            u32_t   *iat;
            char    *libname;
            addr_t  *functions;
            u16_t   *ordinals;
            char   **funcname;

            dll_t   *exp_dll;

            if ( (imp->TimeDateStamp==0 ) && (imp->Name==0) )
                break;

            libname=MakePtr(char*,imp->Name, img_base);

            DBG("import from %s\n",libname);

            exp_dll = find_dll(&core_dll.link, libname);
            if(exp_dll == NULL)
            {
            exp_dll = find_dll(&current_slot->dll_list, libname);
                if(exp_dll == NULL)
            {
                int len = strlen(libname)+1;

                memcpy(path, "/sys/lib/",9);
                memcpy(&path[9],libname,len);

                exp_dll = load_dll(path);
                if( !exp_dll)
                {
                    DBG("can't load %s\n", path);
                    return false;
                };
            }
            };
            DBG("find %s\n", exp_dll->img_name);

            exp = exp_dll->img_exp;

            functions = MakePtr(DWORD*,exp->AddressOfFunctions,exp_dll->img_base);
            ordinals = MakePtr(WORD*,  exp->AddressOfNameOrdinals,exp_dll->img_base);
            funcname = MakePtr(char**, exp->AddressOfNames,exp_dll->img_base);

            thunk = MakePtr(PIMAGE_THUNK_DATA32,
                            imp->Characteristics, img_base);
            iat= MakePtr(DWORD*,imp->FirstThunk, img_base);

            while ( 1 ) // Loop forever (or until we break out)
            {
                PIMAGE_IMPORT_BY_NAME ord;
                addr_t addr;
                *iat=0;

                if ( thunk->u1.AddressOfData == 0 )
                    break;

                if ( thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG )
                {
                   u16_t ordinal;
                   ordinal = thunk->u1.Ordinal & 0xFFFF;
                   *iat = functions[ordinal-exp->Base] + exp_dll->img_base;
                    break;
                }
                else
                {
                    ord = MakePtr(PIMAGE_IMPORT_BY_NAME,
                                  thunk->u1.AddressOfData, img_base);

                    DBG("import %s ", ord->Name);

                    if(strncmp(ord->Name,
                       MakePtr(char*,funcname[ord->Hint],exp_dll->img_base),32))
                    {
                        int ind;
                        char **names=funcname;

                        for(names = funcname,ind = 0;
                            ind < exp->NumberOfNames; names++,ind++)
                        {
                            if(!strncmp(ord->Name,MakePtr(char*,*names,exp_dll->img_base),32))
                            {
                                u16_t ordinal;
                                ordinal = ordinals[ind];
                                DBG("ordinal %d\t\tat %x\n", ordinal, functions[ordinal] + exp_dll->img_base);
                                *iat = functions[ordinal] + exp_dll->img_base;
                                break;
                            };
                        };
                        if(ind == exp->NumberOfNames)
                        {
                            DBG(" unresolved import %s\n",ord->Name);
                            warn=1;
                        };
                    }
                    else
                    {
                        DBG(" \tat %x\n", functions[ord->Hint] + exp_dll->img_base);
                        *iat = functions[ord->Hint] + exp_dll->img_base;
                    };
                };
                thunk++;            // Advance to next thunk
                iat++;
            }
            imp++;  // advance to next IMAGE_IMPORT_DESCRIPTOR
        };
    };

    if ( !warn )
        return true;
    else
        return false;
}

