/*
 *  Copyright © 2006-2013 SplinterGU (Fenix/Bennugd)
 *  Copyright © 2002-2006 Fenix Team (Fenix)
 *  Copyright © 1999-2002 José Luis Cebrián Pagüe (Fenix)
 *
 *  This file is part of Bennu - Game Development
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *     1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *
 *     2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *
 *     3. This notice may not be removed or altered from any source
 *     distribution.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#else
#include <direct.h>
#endif
#include "bgdrtm.h"
#include "dcb.h"
#include "dirs.h"
#include "files.h"
#include "xstrings.h"

#define SYSPROCS_ONLY_DECLARE
#include "sysprocs.h"

/* --------------------------------------------------------------------------- */

void * globaldata = 0 ;
void * localdata  = 0 ;
int    local_size = 0 ;
int  * localstr   = 0 ;
int    local_strings = 0 ;

PROCDEF * procs = NULL ;
PROCDEF * mainproc = NULL ;
int procdef_count = 0 ;

/* ---------------------------------------------------------------------- */

DCB_SYSPROC_CODE2 * sysproc_code_ref = NULL ;

/* ---------------------------------------------------------------------- */

void sysprocs_fixup( void )
{
    SYSPROC * proc = sysprocs ;
    DCB_SYSPROC_CODE2 * s = NULL ;
    int n;

    while ( proc->func )
    {
        proc->code = -1;

        s = sysproc_code_ref ;
        for ( n = 0; n < dcb.data.NSysProcsCodes; n++, s++ )
        {
            if (
                proc->type == s->Type && proc->params == s->Params &&
                s->Id == getid( proc->name ) && !strcmp( (const char *)s->ParamTypes, proc->paramtypes ) )
            {
                proc->code = s->Code ;
                break ;
            }
        }
        proc++ ;
    }
}

/* ---------------------------------------------------------------------- */

PROCDEF * procdef_get( int n )
{
    if ( n >= 0 && n < procdef_count )
        return &procs[n] ;

    return NULL ;
}

/* ---------------------------------------------------------------------- */

PROCDEF * procdef_get_by_name( char * name )
{
    int n;
    for ( n = 0 ; n < procdef_count; n++ )
        if ( strcmp( procs[n].name, name ) == 0 )
            return &procs[n];

    return NULL ;
}

/* ---------------------------------------------------------------------- */

DCB_HEADER dcb ;

/* ---------------------------------------------------------------------- */

static char * trim( char * ptr )
{
    char * ostr = ptr;
    char * bptr = ptr;

    while ( *ptr == ' ' || *ptr == '\n' || *ptr == '\r' || *ptr == '\t' ) ptr++;

    while ( *ptr ) *bptr++ = *ptr++;

    while ( bptr > ostr && ( bptr[-1] == ' ' || bptr[-1] == '\n' || bptr[-1] == '\r' || bptr[-1] == '\t' ) ) bptr--;

    *bptr = 0;

    return ( ostr );
}

/* ---------------------------------------------------------------------- */

static int load_file( const char * filename, int n )
{
    char line[2048] ;
    int allocated = 16 ;
    int count = 0 ;
    char ** lines ;
    file * fp ;

    fp = file_open( filename, "r0" ) ;
    if ( !fp )
    {
        dcb.sourcefiles[n] = 0 ;
        dcb.sourcelines[n] = 0 ;
        dcb.sourcecount[n] = 0 ;
        return 0 ;
    }

    lines = ( char ** ) calloc( 16, sizeof( char* ) ) ;

    while ( !file_eof( fp ) )
    {
        file_qgets( fp, line, 2048 ) ;
        trim( line );
        if ( allocated == count )
        {
            allocated += 16 ;
            lines = realloc( lines, sizeof( char* ) * allocated ) ;
        }
        lines[count++] = strdup( line ) ;
    }
    file_close( fp ) ;

    dcb.sourcefiles[n] = ( uint8_t * ) strdup( filename );
    dcb.sourcelines[n] = ( uint8_t ** ) lines ;
    dcb.sourcecount[n] = count ;
    return 1 ;
}

/* ---------------------------------------------------------------------- */

int dcb_load( const char * filename )
{
    file * fp ;

    /* check for existence of the DCB FILE */
    if ( !file_exists( filename ) ) return 0 ;

    fp = file_open( filename, "rb0" ) ;
    if ( !fp )
    {
        fprintf( stderr, "ERROR: Runtime error - Could not open file (%s)\n", filename ) ;
        exit( 1 );
    }

    return dcb_load_from( fp, filename, 0 );
}

/* ---------------------------------------------------------------------- */

DCB_VAR * read_and_arrange_varspace( file * fp, int count )
{
    int n, n1;
    DCB_VAR * vars = ( DCB_VAR * ) calloc( count, sizeof( DCB_VAR ) ) ;

    for ( n = 0; n < count; n++ )
    {
        file_read( fp, &vars[n], sizeof( DCB_VAR ) ) ;
        ARRANGE_DWORD( &vars[n].ID );
        ARRANGE_DWORD( &vars[n].Offset );
        for ( n1 = 0; n1 < MAX_TYPECHUNKS; n1++ ) ARRANGE_DWORD( &vars[n].Type.Count[n1] );
        ARRANGE_DWORD( &vars[n].Type.Members );
    }

    return vars;
}

/* ---------------------------------------------------------------------- */

int dcb_load_from( file * fp, char * filename, int offset )
{
    unsigned int n ;
    uint32_t size;

    /* Lee el contenido del fichero */

    file_seek( fp, offset, SEEK_SET );
    file_read( fp, &dcb, sizeof( DCB_HEADER_DATA ) ) ;

    ARRANGE_DWORD( &dcb.data.Version );
    ARRANGE_DWORD( &dcb.data.NProcs );
    ARRANGE_DWORD( &dcb.data.NFiles );
    ARRANGE_DWORD( &dcb.data.NID );
    ARRANGE_DWORD( &dcb.data.NStrings );
    ARRANGE_DWORD( &dcb.data.NLocVars );
    ARRANGE_DWORD( &dcb.data.NLocStrings );
    ARRANGE_DWORD( &dcb.data.NGloVars );

    ARRANGE_DWORD( &dcb.data.SGlobal );
    ARRANGE_DWORD( &dcb.data.SLocal );
    ARRANGE_DWORD( &dcb.data.SText );

    ARRANGE_DWORD( &dcb.data.NImports );

    ARRANGE_DWORD( &dcb.data.NSourceFiles );

    ARRANGE_DWORD( &dcb.data.NSysProcsCodes );

    ARRANGE_DWORD( &dcb.data.OProcsTab );
    ARRANGE_DWORD( &dcb.data.OID );
    ARRANGE_DWORD( &dcb.data.OStrings );
    ARRANGE_DWORD( &dcb.data.OText );
    ARRANGE_DWORD( &dcb.data.OGlobal );
    ARRANGE_DWORD( &dcb.data.OGloVars );
    ARRANGE_DWORD( &dcb.data.OLocal );
    ARRANGE_DWORD( &dcb.data.OLocVars );
    ARRANGE_DWORD( &dcb.data.OLocStrings );
    ARRANGE_DWORD( &dcb.data.OVarSpaces );
    ARRANGE_DWORD( &dcb.data.OFilesTab );
    ARRANGE_DWORD( &dcb.data.OImports );

    ARRANGE_DWORD( &dcb.data.OSourceFiles );
    ARRANGE_DWORD( &dcb.data.OSysProcsCodes );

    if ( memcmp( dcb.data.Header, DCB_MAGIC, sizeof( DCB_MAGIC ) - 1 ) != 0 || dcb.data.Version < 0x0700 ) return 0 ;

    globaldata = calloc( dcb.data.SGlobal + 4, 1 ) ;
    localdata  = calloc( dcb.data.SLocal + 4, 1 ) ;
    localstr   = ( int * ) calloc( dcb.data.NLocStrings + 4, sizeof( int ) ) ;
    dcb.proc   = ( DCB_PROC * ) calloc(( 1 + dcb.data.NProcs ), sizeof( DCB_PROC ) ) ;
    procs      = ( PROCDEF * ) calloc(( 1 + dcb.data.NProcs ), sizeof( PROCDEF ) ) ;

    procdef_count = dcb.data.NProcs ;
    local_size    = dcb.data.SLocal ;
    local_strings = dcb.data.NLocStrings ;

    /* Recupera las zonas de datos globales */

    file_seek( fp, offset + dcb.data.OGlobal, SEEK_SET ) ;
    file_read( fp, globaldata, dcb.data.SGlobal ) ;         /* **** */

    file_seek( fp, offset + dcb.data.OLocal, SEEK_SET ) ;
    file_read( fp, localdata, dcb.data.SLocal ) ;           /* **** */

    if ( dcb.data.NLocStrings )
    {
        file_seek( fp, offset + dcb.data.OLocStrings, SEEK_SET ) ;
        file_readUint32A( fp, (uint32_t *)localstr, dcb.data.NLocStrings ) ;
    }

    file_seek( fp, offset + dcb.data.OProcsTab, SEEK_SET ) ;
    for ( n = 0 ; n < dcb.data.NProcs ; n++ )
    {
        file_read( fp, &dcb.proc[n], sizeof( DCB_PROC_DATA ) ) ;

        ARRANGE_DWORD( &dcb.proc[n].data.ID );
        ARRANGE_DWORD( &dcb.proc[n].data.Flags );
        ARRANGE_DWORD( &dcb.proc[n].data.NParams );

        ARRANGE_DWORD( &dcb.proc[n].data.NPriVars );
        ARRANGE_DWORD( &dcb.proc[n].data.NPriStrings );

        ARRANGE_DWORD( &dcb.proc[n].data.NPubVars );
        ARRANGE_DWORD( &dcb.proc[n].data.NPubStrings );

        ARRANGE_DWORD( &dcb.proc[n].data.NSentences );

        ARRANGE_DWORD( &dcb.proc[n].data.SPrivate );
        ARRANGE_DWORD( &dcb.proc[n].data.SPublic );

        ARRANGE_DWORD( &dcb.proc[n].data.SCode );

        ARRANGE_DWORD( &dcb.proc[n].data.OExitCode );
        ARRANGE_DWORD( &dcb.proc[n].data.OErrorCode );

        ARRANGE_DWORD( &dcb.proc[n].data.OSentences );

        ARRANGE_DWORD( &dcb.proc[n].data.OPriVars );
        ARRANGE_DWORD( &dcb.proc[n].data.OPriStrings );
        ARRANGE_DWORD( &dcb.proc[n].data.OPrivate );

        ARRANGE_DWORD( &dcb.proc[n].data.OPubVars );
        ARRANGE_DWORD( &dcb.proc[n].data.OPubStrings );
        ARRANGE_DWORD( &dcb.proc[n].data.OPublic );

        ARRANGE_DWORD( &dcb.proc[n].data.OCode );
    }

    /* Recupera las cadenas */

    dcb.data.OStrings += offset;
    dcb.data.OText += offset;

    string_load( fp, dcb.data.OStrings, dcb.data.OText, dcb.data.NStrings, dcb.data.SText );

    /* Recupera los ficheros incluídos */

    if ( dcb.data.NFiles )
    {
        DCB_FILE dcbfile;
        char fname[__MAX_PATH];

        xfile_init( dcb.data.NFiles );
        file_seek( fp, offset + dcb.data.OFilesTab, SEEK_SET ) ;
        for ( n = 0 ; n < dcb.data.NFiles; n++ )
        {
            file_read( fp, &dcbfile, sizeof( DCB_FILE ) ) ;

            ARRANGE_DWORD( &dcbfile.SName );
            ARRANGE_DWORD( &dcbfile.SFile );
            ARRANGE_DWORD( &dcbfile.OFile );

            file_read( fp, &fname, dcbfile.SName ) ;
            file_add_xfile( fp, filename, offset + dcbfile.OFile, fname, dcbfile.SFile ) ;
        }
    }

    /* Recupera los imports */

    if ( dcb.data.NImports )
    {
        dcb.imports = ( uint32_t * )calloc( dcb.data.NImports, sizeof( uint32_t ) ) ;
        file_seek( fp, offset + dcb.data.OImports, SEEK_SET ) ;
        file_readUint32A( fp, dcb.imports, dcb.data.NImports ) ;
    }

    /* Recupera los datos de depurado */

    if ( dcb.data.NID )
    {
        dcb.id = ( DCB_ID * ) calloc( dcb.data.NID, sizeof( DCB_ID ) ) ;
        file_seek( fp, offset + dcb.data.OID, SEEK_SET ) ;

        for ( n = 0; n < dcb.data.NID; n++ )
        {
            file_read( fp, &dcb.id[n], sizeof( DCB_ID ) ) ;
            ARRANGE_DWORD( &dcb.id[n].Code );
        }
    }

    if ( dcb.data.NGloVars )
    {
        file_seek( fp, offset + dcb.data.OGloVars, SEEK_SET ) ;
        dcb.glovar = read_and_arrange_varspace( fp, dcb.data.NGloVars );
    }

    if ( dcb.data.NLocVars )
    {
        file_seek( fp, offset + dcb.data.OLocVars, SEEK_SET ) ;
        dcb.locvar = read_and_arrange_varspace( fp, dcb.data.NLocVars );
    }

    if ( dcb.data.NVarSpaces )
    {
        dcb.varspace = ( DCB_VARSPACE * ) calloc( dcb.data.NVarSpaces, sizeof( DCB_VARSPACE ) ) ;
        dcb.varspace_vars = ( DCB_VAR ** ) calloc( dcb.data.NVarSpaces, sizeof( DCB_VAR * ) ) ;
        file_seek( fp, offset + dcb.data.OVarSpaces, SEEK_SET ) ;

        for ( n = 0; n < dcb.data.NVarSpaces; n++ )
        {
            file_read( fp, &dcb.varspace[n], sizeof( DCB_VARSPACE ) ) ;
            ARRANGE_DWORD( &dcb.varspace[n].NVars );
            ARRANGE_DWORD( &dcb.varspace[n].OVars );
        }

        for ( n = 0; n < dcb.data.NVarSpaces; n++ )
        {
            dcb.varspace_vars[n] = 0 ;
            if ( !dcb.varspace[n].NVars ) continue ;
            file_seek( fp, offset + dcb.varspace[n].OVars, SEEK_SET ) ;
            dcb.varspace_vars[n] = read_and_arrange_varspace( fp, dcb.varspace[n].NVars );
        }
    }

    if ( dcb.data.NSourceFiles )
    {
        char fname[__MAX_PATH] ;

        dcb.sourcecount = ( uint32_t * ) calloc( dcb.data.NSourceFiles, sizeof( uint32_t ) ) ;
        dcb.sourcelines = ( uint8_t *** ) calloc( dcb.data.NSourceFiles, sizeof( char ** ) ) ;
        dcb.sourcefiles = ( uint8_t ** ) calloc( dcb.data.NSourceFiles, sizeof( char * ) ) ;
        file_seek( fp, offset + dcb.data.OSourceFiles, SEEK_SET ) ;
        for ( n = 0; n < dcb.data.NSourceFiles; n++ )
        {
            file_readUint32( fp, &size ) ;
            file_read( fp, fname, size ) ;
            if ( !load_file( fname, n ) ) fprintf( stdout, "WARNING: Runtime warning - file not found (%s)\n", fname ) ;
        }
    }

    /* ZZZZZZZZZZZZZZZZZZZZZZZZZZ */

    /* Recupera los procesos */

    for ( n = 0 ; n < dcb.data.NProcs ; n++ )
    {
        procs[n].params             = dcb.proc[n].data.NParams ;
        procs[n].string_count       = dcb.proc[n].data.NPriStrings ;
        procs[n].pubstring_count    = dcb.proc[n].data.NPubStrings ;
        procs[n].private_size       = dcb.proc[n].data.SPrivate ;
        procs[n].public_size        = dcb.proc[n].data.SPublic ;
        procs[n].code_size          = dcb.proc[n].data.SCode ;
        procs[n].id                 = dcb.proc[n].data.ID ;
        procs[n].flags              = dcb.proc[n].data.Flags ;
        procs[n].type               = n ;
        procs[n].name               = getid_name( procs[n].id ) ;
        procs[n].breakpoint         = 0;

        if ( dcb.proc[n].data.SPrivate )
        {
            procs[n].pridata = ( int * )calloc( dcb.proc[n].data.SPrivate, sizeof( char ) ) ; /* El size ya esta calculado en bytes */
            file_seek( fp, offset + dcb.proc[n].data.OPrivate, SEEK_SET ) ;
            file_read( fp, procs[n].pridata, dcb.proc[n].data.SPrivate ) ;      /* *** */
        }

        if ( dcb.proc[n].data.SPublic )
        {
            procs[n].pubdata = ( int * )calloc( dcb.proc[n].data.SPublic, sizeof( char ) ) ; /* El size ya esta calculado en bytes */
            file_seek( fp, offset + dcb.proc[n].data.OPublic, SEEK_SET ) ;
            file_read( fp, procs[n].pubdata, dcb.proc[n].data.SPublic ) ;       /* *** */
        }

        if ( dcb.proc[n].data.SCode )
        {
            procs[n].code = ( int * ) calloc( dcb.proc[n].data.SCode, sizeof( char ) ) ; /* El size ya esta calculado en bytes */
            file_seek( fp, offset + dcb.proc[n].data.OCode, SEEK_SET ) ;
            file_readUint32A( fp, (uint32_t *)procs[n].code, dcb.proc[n].data.SCode / sizeof(uint32_t) ) ;

            if ( dcb.proc[n].data.OExitCode )
                procs[n].exitcode = dcb.proc[n].data.OExitCode ;
            else
                procs[n].exitcode = 0 ;

            if ( dcb.proc[n].data.OErrorCode )
                procs[n].errorcode = dcb.proc[n].data.OErrorCode ;
            else
                procs[n].errorcode = 0 ;
        }

        if ( dcb.proc[n].data.NPriStrings )
        {
            procs[n].strings = ( int * )calloc( dcb.proc[n].data.NPriStrings, sizeof( int ) ) ;
            file_seek( fp, offset + dcb.proc[n].data.OPriStrings, SEEK_SET ) ;
            file_readUint32A( fp, (uint32_t *)procs[n].strings, dcb.proc[n].data.NPriStrings ) ;
        }

        if ( dcb.proc[n].data.NPubStrings )
        {
            procs[n].pubstrings = ( int * )calloc( dcb.proc[n].data.NPubStrings, sizeof( int ) ) ;
            file_seek( fp, offset + dcb.proc[n].data.OPubStrings, SEEK_SET ) ;
            file_readUint32A( fp, (uint32_t *)procs[n].pubstrings, dcb.proc[n].data.NPubStrings ) ;
        }

        if ( dcb.proc[n].data.NPriVars )
        {
            file_seek( fp, offset + dcb.proc[n].data.OPriVars, SEEK_SET ) ;
            dcb.proc[n].privar = read_and_arrange_varspace( fp, dcb.proc[n].data.NPriVars );
        }

        if ( dcb.proc[n].data.NPubVars )
        {
            file_seek( fp, offset + dcb.proc[n].data.OPubVars, SEEK_SET ) ;
            dcb.proc[n].pubvar = read_and_arrange_varspace( fp, dcb.proc[n].data.NPubVars );
        }
    }

    /* Recupero tabla de fixup de sysprocs */

    sysproc_code_ref = calloc( dcb.data.NSysProcsCodes, sizeof( DCB_SYSPROC_CODE2 ) ) ;
    file_seek( fp, offset + dcb.data.OSysProcsCodes, SEEK_SET ) ;
    for ( n = 0; n < dcb.data.NSysProcsCodes; n++ )
    {
        DCB_SYSPROC_CODE sdcb;
        file_read( fp, &sdcb, sizeof( DCB_SYSPROC_CODE ) ) ;

        ARRANGE_DWORD( &sdcb.Id );
        ARRANGE_DWORD( &sdcb.Type );
        ARRANGE_DWORD( &sdcb.Params );
        ARRANGE_DWORD( &sdcb.Code );

        sysproc_code_ref[n].Id = sdcb.Id ;
        sysproc_code_ref[n].Type = sdcb.Type ;
        sysproc_code_ref[n].Params = sdcb.Params ;
        sysproc_code_ref[n].Code = sdcb.Code ;
        sysproc_code_ref[n].ParamTypes = ( uint8_t * ) calloc( sdcb.Params + 1, sizeof( char ) );
        if ( sdcb.Params ) file_read( fp, sysproc_code_ref[n].ParamTypes, sdcb.Params ) ;
    }

    sysprocs_fixup();

    mainproc = procdef_get_by_name( "MAIN" );

    return 1 ;
}

/* ---------------------------------------------------------------------- */

char * getid_name( unsigned int code )
{
    unsigned int n ;
    for ( n = 0 ; n < dcb.data.NID ; n++ )
        if ( dcb.id[n].Code == code )
            return (char *)dcb.id[n].Name ;
    return "(?)" ;
}

/* ---------------------------------------------------------------------- */

int getid( char * name )
{
    unsigned int n ;
    for ( n = 0 ; n < dcb.data.NID ; n++ )
        if ( strcmp( (const char *)dcb.id[n].Name, name ) == 0 )
            return dcb.id[n].Code ;
    return -1 ;
}

/* ---------------------------------------------------------------------- */
