/****************************************************************************
*
*  This code is Public Domain.
*
*  ========================================================================
*
* Description:
* function                directive
*--------------------------------------------------
* EchoDirective()         ECHO
* IncludeDirective()      INCLUDE
* IncludeLibDirective()   INCLUDELIB
* IncBinDirective()       INCBIN
* AliasDirective()        ALIAS
* NameDirective()         NAME
* RadixDirective()        .RADIX
* SegOrderDirective()     .DOSSEG, .SEQ, .ALPHA
*
****************************************************************************/

#include <ctype.h>

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "segment.h"
#include "input.h"
#include "tokenize.h"
#include "expreval.h"
#include "types.h"
#include "fastpass.h"
#include "listing.h"
#include "omf.h"
#include "macro.h"

#define  res(token, function) extern ret_code function( int, struct asm_tok[] );
#include "dirtype.h"
#undef res

/* table of function addresses for directives */
#define  res(token, function) function ,
ret_code (* const directive_tab[])( int, struct asm_tok[] ) = {
#include "dirtype.h"
};
#undef res

/* should never be called */
ret_code StubDir( int i, struct asm_tok tokenarray[] ){ return( ERROR ); }

/* handle ECHO directive.
 * displays text on the console
 */
ret_code EchoDirective( int i, struct asm_tok tokenarray[] )
/**********************************************************/
{
    if ( Parse_Pass == PASS_1 ) { /* display in pass 1 only */
        if ( Options.preprocessor_stdout == FALSE ) { /* don't print to stdout if -EP is on! */
            printf( "%s\n", tokenarray[i+1].tokpos );
        }
    }
#ifdef DEBUG_OUT
    else DebugMsg1(("EchoDirective - echo suppressed (pass > 1): %s\n", tokenarray[i+1].tokpos));
#endif
    return( NOT_ERROR );
}

/* INCLUDE directive.
 * If a full path is specified, the directory where the included file
 * is located becomes the "source" directory, that is, it is searched
 * FIRST if further INCLUDE directives are found inside the included file.
 */
ret_code IncludeDirective( int i, struct asm_tok tokenarray[] )
/*************************************************************/
{
    char *name;

    DebugMsg1(("IncludeDirective enter\n"));

    if ( ModuleInfo.list ) LstWriteSrcLine();

    i++; /* skip directive */
    /* v2.03: allow plain numbers as file name argument */
    //if ( tokenarray[i].token == T_FINAL || tokenarray[i].token == T_NUM ) {
    if ( tokenarray[i].token == T_FINAL ) {
        return( EmitError( EXPECTED_FILE_NAME ) );
    }

    /* if the filename is enclosed in <>, just use this literal */

    if ( tokenarray[i].token == T_STRING && tokenarray[i].string_delim == '<' ) {
        if ( tokenarray[i+1].token != T_FINAL ) {
            return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i+1].tokpos ) );
        }
        name = tokenarray[i].string_ptr;
    } else {
        char *p;
        /* if the filename isn't enclosed in <>, use anything that comes
         * after INCLUDE - and remove trailing white spaces.
         */
        name = tokenarray[i].tokpos;
        for ( p = tokenarray[Token_Count].tokpos - 1; p > name && isspace(*p); *p = NULLC, p-- );
    }
    if ( SearchFile( name, TRUE ) )
        ProcessFile( tokenarray );   /* v2.11: process the file synchronously */
    return( NOT_ERROR );
}

static char *IncludeLibrary( const char *name )
/*********************************************/
{
    struct qitem *q;

    /* old approach, <= 1.91: add lib name to global namespace */
    /* new approach, >= 1.92: check lib table, if entry is missing, add it */
    /* Masm doesn't map cases for the paths. So if there is
     * includelib <kernel32.lib>
     * includelib <KERNEL32.LIB>
     * then 2 defaultlib entries are added. If this is to be changed for
     * JWasm, activate the _stricmp() below.
     */
    for ( q = ModuleInfo.g.LibQueue.head; q ; q = q->next ) {
        //if ( _stricmp( dir->sym.name, name) == 0)
        if ( strcmp( q->value, name ) == 0 )
            return( q->value );
    }
    q = LclAlloc( sizeof( struct qitem ) + strlen( name ) );
    strcpy( q->value, name );
    QEnqueue( &ModuleInfo.g.LibQueue, q );
    return( q->value );
}

#if FASTMEM==0

/* release the lib queue ( created by INCLUDELIB directive ) */

void FreeLibQueue( void )
/***********************/
{
    struct qitem *curr;
    struct qitem *next;
    for( curr = ModuleInfo.g.LibQueue.head; curr; curr = next ) {
        next = curr->next;
        LclFree( curr );
    }
}
#endif

/* directive INCLUDELIB */

ret_code IncludeLibDirective( int i, struct asm_tok tokenarray[] )
/****************************************************************/
{
    char *name;
    //struct asym *sym;

    if ( Parse_Pass != PASS_1 ) /* do all work in pass 1 */
        return( NOT_ERROR );
    i++; /* skip the directive */
    /* v2.03: library name may be just a "number" */
    //if ( tokenarray[i].token == T_FINAL || tokenarray[i].token == T_NUM ) {
    if ( tokenarray[i].token == T_FINAL ) {
        /* v2.05: Masm doesn't complain if there's no name, so emit a warning only! */
        //EmitError( LIBRARY_NAME_MISSING );
        //return( ERROR );
        EmitWarn( 2, LIBRARY_NAME_MISSING );
    }

    if ( tokenarray[i].token == T_STRING && tokenarray[i].string_delim == '<' ) {
        if ( tokenarray[i+1].token != T_FINAL ) {
            return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i+1].tokpos ) );
        }
        /* v2.08: use GetLiteralValue() */
        //name = StringBufferEnd;
        //GetLiteralValue( name, tokenarray[i].string_ptr );
        name = tokenarray[i].string_ptr;
    } else {
        char *p;
        /* regard "everything" behind INCLUDELIB as the library name */
        name = tokenarray[i].tokpos;
        /* remove trailing white spaces */
        for ( p = tokenarray[Token_Count].tokpos - 1; p > name && isspace( *p ); *p = NULLC, p-- );
    }

    IncludeLibrary( name );
    return( NOT_ERROR );
}

#if INCBINSUPP

/* INCBIN directive */

ret_code IncBinDirective( int i, struct asm_tok tokenarray[] )
/************************************************************/
{
    FILE *file;
    //int size;
    uint_32 fileoffset = 0; /* fixme: should be uint_64 */
    uint_32 sizemax = -1;
    struct expr opndx;

    DebugMsg(("IncBinDirective enter\n"));

    i++; /* skip the directive */
    /* v2.03: file name may be just a "number" */
    //if ( tokenarray[i].token == T_FINAL || tokenarray[i].token == T_NUM ) {
    if ( tokenarray[i].token == T_FINAL ) {
        return( EmitError( EXPECTED_FILE_NAME ) );
    }

    if ( tokenarray[i].token == T_STRING ) {

        /* v2.08: use string buffer to avoid buffer overflow if string is > FILENAME_MAX */
        if ( tokenarray[i].string_delim == '"' || tokenarray[i].string_delim == '\'' ) {
            memcpy( StringBufferEnd, tokenarray[i].string_ptr+1, tokenarray[i].stringlen );
            StringBufferEnd[tokenarray[i].stringlen] = NULLC;
        } else if ( tokenarray[i].string_delim == '<' ) {
            /* v2.08: use GetLiteralValue() instead of strncpy() */
            //GetLiteralValue( StringBufferEnd, tokenarray[i].string_ptr );
            memcpy( StringBufferEnd, tokenarray[i].string_ptr, tokenarray[i].stringlen+1 );
        } else {
            return( EmitError( FILENAME_MUST_BE_ENCLOSED_IN_QUOTES_OR_BRACKETS ) );
        }
    } else {
        return( EmitError( FILENAME_MUST_BE_ENCLOSED_IN_QUOTES_OR_BRACKETS ) );
    }
    i++;
    if ( tokenarray[i].token == T_COMMA ) {
        i++;
        if ( EvalOperand( &i, tokenarray, Token_Count, &opndx, 0 ) == ERROR )
            return( ERROR );
        if ( opndx.kind == EXPR_CONST ) {
            fileoffset = opndx.value;
        } else if ( opndx.kind != EXPR_EMPTY ) {
            return( EmitError( CONSTANT_EXPECTED ) );
        }
        if ( tokenarray[i].token == T_COMMA ) {
            i++;
            if ( EvalOperand( &i, tokenarray, Token_Count, &opndx, 0 ) == ERROR )
                return( ERROR );
            if ( opndx.kind == EXPR_CONST ) {
                sizemax = opndx.value;
            } else if ( opndx.kind != EXPR_EMPTY ) {
                return( EmitError( CONSTANT_EXPECTED ) );
            }
        }
    }
    if ( tokenarray[i].token != T_FINAL ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos ) );
    }

    if( CurrSeg == NULL ) {
        return( EmitError( MUST_BE_IN_SEGMENT_BLOCK ) );
    }

    /* v2.04: tell assembler that data is emitted */
    if ( ModuleInfo.CommentDataInCode )
        omf_OutSelect( TRUE );

    DebugMsg1(("IncBinDirective: filename=%s, offset=%" I32_SPEC "u, size=%" I32_SPEC "u\n", StringBufferEnd, fileoffset, sizemax ));

    /* try to open the file */
    if ( file = SearchFile( StringBufferEnd, FALSE ) ) {
        /* transfer file content to the current segment. */
        if ( fileoffset )
            fseek( file, fileoffset, SEEK_SET );  /* fixme: use fseek64() */
        for( ; sizemax; sizemax-- ) {
            int ch = fgetc( file );
            if ( ( ch == EOF ) && feof( file ) )
                break;
            OutputByte( ch );
        }
        fclose( file );
    }

    return( NOT_ERROR );
}
#endif

/* Alias directive.
 * Masm syntax is:
 *   'ALIAS <alias_name> = <substitute_name>'
 * which looks somewhat strange if compared to other Masm directives.
 * (OW Wasm syntax is 'alias_name ALIAS substitute_name', which is
 * what one might have expected for Masm as well).
 *
 * <alias_name> is a global name and must be unique (that is, NOT be
 * defined elsewhere in the source!
 * <substitute_name> is the name which is defined in the source.
 * For COFF and ELF, this name MUST be defined somewhere as
 * external or public!
 */

ret_code AliasDirective( int i, struct asm_tok tokenarray[] )
/***********************************************************/
{
    //char *tmp;
    struct asym *sym;
    char *subst;

    i++; /* go past ALIAS */

    if ( tokenarray[i].token != T_STRING ||
        tokenarray[i].string_delim != '<' ) {
        DebugMsg(("AliasDirective: first argument is not a literal: %s\n", tokenarray[i].string_ptr ));
        return( EmitError( TEXT_ITEM_REQUIRED ) );
    }

    /* check syntax. note that '=' is T_DIRECTIVE && DRT_EQUALSGN */
    if ( tokenarray[i+1].token != T_DIRECTIVE ||
        //tokenarray[i+1].tokval != T_EQU ||
        tokenarray[i+1].dirtype != DRT_EQUALSGN ) {
        DebugMsg(("AliasDirective: syntax error: %s\n", tokenarray[i+1].string_ptr ));
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i+1].string_ptr ) );
    }

    if ( tokenarray[i+2].token != T_STRING ||
        tokenarray[i+2].string_delim != '<' )  {
        DebugMsg(("AliasDirective: second argument is not a literal: %s\n", tokenarray[i+2].string_ptr ));
        return( EmitError( TEXT_ITEM_REQUIRED ) );
    }
    subst = tokenarray[i+2].string_ptr;

    if ( tokenarray[i+3].token != T_FINAL ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i+3].string_ptr ) );
    }

    /* make sure <alias_name> isn't defined elsewhere */
    sym = SymSearch( tokenarray[i].string_ptr );
    if ( sym == NULL || sym->state == SYM_UNDEFINED ) {
        struct asym *sym2;
        /* v2.04b: adjusted to new field <substitute> */
        sym2 = SymSearch( subst );
        if ( sym2 == NULL ) {
            sym2 = SymCreate( subst );
            sym2->state = SYM_UNDEFINED;
            sym_add_table( &SymTables[TAB_UNDEF], (struct dsym *)sym2 );
        } else if ( sym2->state != SYM_UNDEFINED &&
                   sym2->state != SYM_INTERNAL &&
                   sym2->state != SYM_EXTERNAL ) {
            return( EmitErr( MUST_BE_PUBLIC_OR_EXTERNAL, subst ) );
        }
        if ( sym == NULL )
            sym = SymCreate( tokenarray[i].string_ptr );
        else
            sym_remove_table( &SymTables[TAB_UNDEF], (struct dsym *)sym );

        sym->state = SYM_ALIAS;
        sym->substitute = sym2;
        /* v2.10: copy language type of alias */
        sym->langtype = sym2->langtype;
        sym_add_table( &SymTables[TAB_ALIAS], (struct dsym *)sym ); /* add ALIAS */
        return( NOT_ERROR );
    }
    if ( sym->state != SYM_ALIAS || ( strcmp( sym->substitute->name, subst ) != 0 )) {
        DebugMsg(("AliasDirective: symbol redefinition\n"));
        return( EmitErr( SYMBOL_REDEFINITION, sym->name ) );
    }
#if COFF_SUPPORT || ELF_SUPPORT
    /* for COFF+ELF, make sure <actual_name> is "global" (EXTERNAL or
     * public INTERNAL). For OMF, there's no check at all. */
    if ( Parse_Pass != PASS_1 ) {
        if ( Options.output_format == OFORMAT_COFF
#if ELF_SUPPORT
             || Options.output_format == OFORMAT_ELF
#endif
           ) {
            if ( sym->substitute->state == SYM_UNDEFINED ) {
                return( EmitErr( SYMBOL_NOT_DEFINED, subst ) );
            } else if ( sym->substitute->state != SYM_EXTERNAL &&
                       ( sym->substitute->state != SYM_INTERNAL || sym->substitute->ispublic == FALSE ) ) {
                return( EmitErr( MUST_BE_PUBLIC_OR_EXTERNAL, subst ) );
            }
        }
    }
#endif
    return( NOT_ERROR );
}

/* the NAME directive is ignored in Masm v6 */

ret_code NameDirective( int i, struct asm_tok tokenarray[] )
/**********************************************************/
{
    if( Parse_Pass != PASS_1 )
        return( NOT_ERROR );
    /* if a module name is set with -nm, ignore NAME directive! */
    /* v2.08: removed, since Options.names isn't touched at all */
    //if( Options.names[OPTN_MODULE_NAME] != NULL )
    //    return( NOT_ERROR );

    i++; /* skip directive */

    /* improper use of NAME is difficult to see since it is a nop
     therefore some syntax checks are implemented:
     - no 'name' structs, unions, records, typedefs!
     - no 'name' struct fields!
     - no 'name' segments!
     - no 'name:' label!
     */
    if ( CurrStruct != NULL ||
        ( tokenarray[i].token == T_DIRECTIVE &&
         ( tokenarray[i].tokval == T_SEGMENT ||
          tokenarray[i].tokval == T_STRUCT  ||
          tokenarray[i].tokval == T_STRUC   ||
          tokenarray[i].tokval == T_UNION   ||
          tokenarray[i].tokval == T_TYPEDEF ||
          tokenarray[i].tokval == T_RECORD)) ||
         tokenarray[i].token == T_COLON ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i-1].tokpos ) );
    }

    /* don't touch Option fields! if anything at all, ModuleInfo.name may be modified.
     * However, since the directive is ignored by Masm, nothing is done.
     */
//  strncpy( ModuleInfo.name, tokenarray[i].string_ptr, sizeof( ModuleInfo.name ) );
//  ModuleInfo.name[ sizeof( ModuleInfo.name ) - 1] = NULLC;
//  DebugMsg(("NameDirective: set name to >%s<\n", ModuleInfo.name ));
    DebugMsg(("NameDirective: ignored name >%s<\n", tokenarray[i].string_ptr ));
    return( NOT_ERROR );
}

/* .RADIX directive, value must be between 2 .. 16 */

ret_code RadixDirective( int i, struct asm_tok tokenarray[] )
/***********************************************************/
{
    uint_8          oldradix;
    struct expr     opndx;

    /* to get the .radix parameter, enforce radix 10 and retokenize! */
    oldradix = ModuleInfo.radix;
    ModuleInfo.radix = 10;
    i++; /* skip directive token */
    Tokenize( tokenarray[i].tokpos, i, tokenarray, TOK_RESCAN );
    ModuleInfo.radix = oldradix;
    /* v2.11: flag NOUNDEF added - no forward ref possible */
    if ( EvalOperand( &i, tokenarray, Token_Count, &opndx, EXPF_NOUNDEF ) == ERROR ) {
        return( ERROR );
    }

    if ( opndx.kind != EXPR_CONST ) {
        return( EmitError( CONSTANT_EXPECTED ) );
    }
    if ( tokenarray[i].token != T_FINAL ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos ) );
    }
    if ( opndx.value > 16 || opndx.value < 2 || opndx.hvalue != 0 ) {
        return( EmitError( INVALID_RADIX_TAG ) );
    }

    ModuleInfo.radix = opndx.value;
    DebugMsg(("RadixDirective: new radix=%u\n", ModuleInfo.radix ));

    return( NOT_ERROR );
}

/* DOSSEG, .DOSSEG, .ALPHA, .SEQ directives */

ret_code SegOrderDirective( int i, struct asm_tok tokenarray[] )
/**************************************************************/
{
    if ( tokenarray[i+1].token != T_FINAL ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i+1].tokpos ) );
    }
#if COFF_SUPPORT || ELF_SUPPORT || PE_SUPPORT
    if ( Options.output_format == OFORMAT_COFF
#if ELF_SUPPORT
        || Options.output_format == OFORMAT_ELF
#endif
#if PE_SUPPORT
        || ( Options.output_format == OFORMAT_BIN && ModuleInfo.sub_format == SFORMAT_PE )
#endif
       ) {
        if ( Parse_Pass == PASS_1 )
            EmitWarn( 2, NOT_SUPPORTED_WITH_CURR_FORMAT, _strupr( tokenarray[i].string_ptr ) );
    } else
#endif
        ModuleInfo.segorder = GetSflagsSp( tokenarray[i].tokval );

    return( NOT_ERROR );
}
