#include <stdio.h>
#include <string.h>     // strchr, memset
#include <malloc.h>     // malloc
#include <stdlib.h>     // strtol
#if !defined(__WATCOMC__) && !defined(__QNX__) && !defined(WIN32)
#include <alloca.h>
#define  Alloc4hndl     alloca
#else
#define  Alloc4hndl     malloc
#endif

#include "rpnproc.h"



// Napoveda
//
#define  HELP_TEXT                                                          \
"Use:\n"                                                                    \
"# rpnproc [options] [[[[regT] regZ] regY] regX] [file1 [file2 [... fileN]]]\n"\
"\n"                                                                        \
" Option |              Description             |  Default\n"               \
"--------+--------------------------------------+-----------\n"             \
" -s<N>  | starting label                       | from begin\n"             \
" -t     | trace                                |\n"                        \
"--------+--------------------------------------+-----------\n"             \
" -l     | listing alowed                       | suppressed\n"             \
" -ll    | listing only (executing suppressed)  |\n"                        \
" -x     | numbers in HEX                       | signed dec\n"             \
" -o     | numbers in OCT                       | signed dec\n"             \
" -u     | numbers in UNSIGNED dec              | signed dec\n"             \
" -n<C>  | nonexistent label separator          | \'#\'\n"                  \
" -a<C>  | label/lineno separator               | \':\'\n"                  \
" -i<C>  | address/instruction separator        | \'\\t\'\n"                \
" -p<C>  | parameter/instruction separator      | \' \' (space)\n"          \
"\n"                                                                        \
"If no file is given, \"" INPUT_FILE_DFLT "\" is used.\n"                   \
"If file is \"-\", stdin is used.\n"                                        \
"If file begins \"%%\", argv[] is used.\n"                                  \
"\n"                                                                        \
"Examples:\n"                                                               \
"# rpnproc\n"                                                               \
"# rpnproc -ll listonly.rpn\n"                                              \
"# rpnproc -l 173 first.rpn -x second.rpn -12 -u third.rpn\n"               \
"# rpnproc -t 41 3 -\n"                                                     \
"# rpnproc -l -t 1 2 3 \"%%sto 0 %%* %%+ %%2 %%* %%rcl+ 0\"\n"              \
"\n"


// "Options" na "command-line"
//
#define  MULTICHR2(a, b)        ((a)  |  (b) << 8)
#define  OPTI2CHR(c)            MULTICHR2('-', (c))
#define  OPTI2ARG(v)            (*(v) + 2)
//
#define  OPTCHR_HELP            'h'
#define  OPTCHR_START           's'
#define  OPTCHR_TRACE           't'
//
#define  OPTCHR_LIST            'l'
#define  OPTCHR_HEX             'x'
#define  OPTCHR_OCT             'o'
#define  OPTCHR_UNS             'u'
#define  OPTCHR_DELIM_NONEXIST  'n'
#define  OPTCHR_DELIM_ADR_LBL   'a'
#define  OPTCHR_DELIM_INS_ADR   'i'
#define  OPTCHR_DELIM_PAR_INS   'p'
//
#define  OPTCHR_STDIN           '-'
#define  OPTCHR_JOB             '%'


// "Default" jmeno souboru
//
#define  INPUT_FILE_DFLT        "default.rpn"



// Promenne nastavene z "command-line"
//
struct optis
{
// pro vrstvu RPNPROC
    int             radix;      // cis.soustava vypisu hodnot v instrukcich
    unsigned long   delim;      // oddelovace
    int             lblidx;     // navesti (label) pro spusteni
    int             tracef;     // priznak TRACE
// pro tento program
    int             listf,      // vypis programu
                    execf;      // spusteni programu
// hodnoty stacku pred spustenim
    rpnprocvalue_t  stackt[RPNPROC_SIZE_STACK];
};


// Format vypisu hodnot
//
#define  VALUES_FORMAT          "0x%08lx %11ld"
#define  VALUES_ARGS(v)         (v), (v)



/*
 * CALLBACK A VYKONNA FUNKCE PRO "rpnproc_listjob()" A "rpnproc_listone()"
 */

static int cb_joblist(char *linep,
                      void *userdata)
{
    userdata = userdata;

    puts(linep);
    return(RPNPROC_CB_CONT);
}

static int job_list(int                (*fncp)(void *, rpnproclsjval_t *),
                    void               *argp,
                    struct optis const *optip)
{
    rpnproclsjval_t lsjvalv;

    lsjvalv.radix    = optip->radix;
    lsjvalv.delim    = optip->delim;
    lsjvalv.fncp     = cb_joblist;
    lsjvalv.userdata = NULL;
    return((*fncp)(argp, &lsjvalv));
}

#define  Joblist(f, p, o)                                                   \
        job_list((int (*)(void *, rpnproclsjval_t *))rpnproc_list##f, (p), (o))




/*
 * CALLBACK PRO FUNKCI "rpnproc_execute()"
 */

static int cb_execute(int             execb,
                      rpnprocexeval_t *exevalp,
                      void            *userdata)
{
    switch(execb)
    {
    case RPNPROC_EXECB_TRACE:
        printf("TRACE:   " VALUES_FORMAT ":  ", VALUES_ARGS(exevalp->stackx));
        Joblist(one, &exevalp->trcjobv, userdata);
        break;

    case RPNPROC_EXECB_PSE:
        printf("PAUSE:   " VALUES_FORMAT "\n", VALUES_ARGS(exevalp->stackx));
        break;

    default:
        printf("ERROR:   %d:  label=%d, line=%u\n", execb,
                exevalp->trcjobv.tracev.lblidx, exevalp->trcjobv.tracev.lineno);
    }
    return(RPNPROC_CB_CONT);
}



/*
 * VYKONANI PROGRAMU
 */

static int job_execute(rpnprocjobp_t      jobp,
                       unsigned           varnum,
                       unsigned           lblnum,
                       struct optis const *optip)
{
    rpnprocvalue_t  value;
    rpnprochndp_t   hndp;

    if(optip->listf)
    {
        puts("* * *  LIST  * * *");
        Joblist(job, jobp, optip);
    }
    if((hndp = Rpnprocinit(Alloc4hndl, varnum, lblnum)) == NULL)
        return(-1);
    if(optip->execf)
    {
        int     idx;

        if(optip->listf)
        {
#if     RPNPROC_SIZE_STACK != 4
#error  sortiment[], RPNPROC_SIZE_STACK != 4
#endif
            static char const   sortiment[] = {'X', 'Y', 'Z', 'T'};

            puts("* * *  EXEC  * * *");
            idx = RPNPROC_SIZE_STACK;
            while(--idx >= 0)
                printf("REG%c:    " VALUES_FORMAT "\n", sortiment[idx],
                                            VALUES_ARGS(optip->stackt[idx]));
        }
        rpnproc_setjob(hndp, jobp, cb_execute, (void *)optip);
        idx = RPNPROC_SIZE_STACK;
        while(--idx >= 0)
            rpnproc_enter(hndp, optip->stackt[idx]);
        value = rpnproc_execute(hndp, optip->lblidx, optip->tracef);
        printf("RESULT:  " VALUES_FORMAT "\n", VALUES_ARGS(value));
    }
    return(0);
}



/*
 * CALLBACK PRO FUNKCI "rpnproc_makejob()"
 */

struct makejobs
{
    rpnprocjobp_t   jobp;
    unsigned        lineno;
    void            *paramp;
    char            *(*fncp)(rpnprocmkjval_t *, struct makejobs *);
};

static char *cb_makejob_file(rpnprocmkjval_t *mvp,
                             struct makejobs *umjp)
{
    return(fgets(mvp->linet, sizeof(mvp->linet) - 1, umjp->paramp));
}

static char *cb_makejob_argv(rpnprocmkjval_t *mvp,
                             struct makejobs *umjp)
{
    int     length;
    char    *argp,
            *sep;

    if((argp = umjp->paramp) == NULL)
        return(NULL);
    length  =  (sep = strchr(++argp, OPTCHR_JOB)) == NULL   ?   strlen(argp)  :
                                                                sep - argp;
    if(length >= sizeof(mvp->linet))
        return(NULL);
    umjp->paramp = sep;
    memcpy(mvp->linet, argp, length);
    mvp->linet[length] = '\0';
    return(mvp->linet);
}

static int cb_makejob(int             mkjcb,
                      rpnprocmkjval_t *mvp,
                      void            *userdata)
{
#define  umjp   ((struct makejobs *)userdata)

    switch(mkjcb)
    {
    case RPNPROC_MKJCB_MEMJOB:
        mvp->memjob.jobp = malloc(mvp->memjob.size = 512);
        if(umjp->jobp == NULL)
            umjp->jobp = mvp->memjob.jobp;
        return(RPNPROC_CB_CONT);

    case RPNPROC_MKJCB_LINE:
        if((*umjp->fncp)(mvp, umjp) == NULL)
            return(RPNPROC_CB_BREAK);
        ++umjp->lineno;
        return(RPNPROC_CB_CONT);

    default:
        return(RPNPROC_CB_BREAK);
    }

#undef  umjp
}



/*
 * NACTENI A VYKONANI PROGRAMU ZE SOUBORU
 */

static int job_from_file(char               *fnamep,
                         struct optis const *optip)
{
    int             test;
    unsigned        varnum,
                    lblnum;
    struct makejobs umjv;

    if(fnamep[0] == OPTCHR_JOB)
    {
        umjv.paramp = fnamep;
        umjv.fncp = cb_makejob_argv;
        fnamep = "<argv>";
    }
    else
    {
        if(fnamep[0] == OPTCHR_STDIN)
        {
            if(fnamep[1] != '\0')
                return(0);
            umjv.paramp = stdin;
            fnamep = "<stdin>";
        }
        else
            if((umjv.paramp = fopen(fnamep, "r")) == NULL)
                return(0);
        umjv.fncp = cb_makejob_file;
    }
    printf("FILE:    %s\n", fnamep);
    umjv.jobp = NULL;
    umjv.lineno = 0;
    if((test = rpnproc_makejob(&varnum, &lblnum,
                                cb_makejob, &umjv)) != RPNPROC_MAKEJOB_OK)
        printf("MAKEJOB: %d:  line=%d\n", test, umjv.lineno);
    else
        if(umjv.jobp != NULL)
            if(job_execute(umjv.jobp, varnum, lblnum, optip))
                printf("MEMORY:  varnum=%u: lblnum=%u\n", varnum, lblnum);
    if(umjv.fncp == cb_makejob_file)
        if(umjv.paramp != stdin)
            fclose(umjv.paramp);
    return(1);
}



/*
 * PREVOD: TEXT --> HODNOTA
 */

static int text2value(char           *texp,
                      rpnprocvalue_t *valp)
{
    rpnprocvalue_t  value;
    char            *endp;

    value = strtol(texp, &endp, 0);
    if(*endp != '\0')
        return(-1);
    *valp = value;
    return(0);
}

static int text2lblidx(char *texp)
{
    rpnprocvalue_t  value;

    if(text2value(texp, &value))
        return(RPNPROC_LBLIDX_INVAL);
    return(value);
}

static int text2regs(char           *texp,
                     rpnprocvalue_t stackt[])
{
    int             idx;
    rpnprocvalue_t  value;

    if(idx = text2value(texp, &value))
        return(idx);
    idx = RPNPROC_SIZE_STACK;
    while(--idx > 0)
        stackt[idx] = stackt[idx - 1];
    stackt[0] = value;
    return(0);
}



/*
 * HLAVNI PROGRAM
 */

int main(int argc, char *argv[])
{
#define  _RPNPROC_DELIM_SHIFT_shift     shift

    int             shift;
    struct optis    optiv;
    unsigned        donef;

    memset(&optiv, 0, sizeof(optiv));
    optiv.radix = RPNPROC_LIST_RADIX_SGN;
    optiv.delim = RPNPROC_LIST_DELIM_DEFLT;
    optiv.lblidx = RPNPROC_LBLIDX_INVAL;
//- optiv.tracef = 0;
//- optiv.listf = 0;
    optiv.execf = 1;
    donef = 0;
    while(--argc)
    {
        shift = _RPNPROC_DELIM_SHIFT_NLBL;
        switch(*(short *)*++argv)
        {
        case OPTI2CHR(OPTCHR_HELP):
            printf(HELP_TEXT);
            return(0);

        case OPTI2CHR(OPTCHR_START):
            if(*OPTI2ARG(argv))
                optiv.lblidx = text2lblidx(OPTI2ARG(argv));
            break;

        case OPTI2CHR(OPTCHR_TRACE):
            optiv.tracef = 1;
            break;

        case OPTI2CHR(OPTCHR_LIST):
            optiv.listf = 1;
            if(*OPTI2ARG(argv) == 'l')
                optiv.execf = 0;
            break;

        case OPTI2CHR(OPTCHR_HEX):
            optiv.radix = RPNPROC_LIST_RADIX_HEX;
            break;

        case OPTI2CHR(OPTCHR_OCT):
            optiv.radix = RPNPROC_LIST_RADIX_OCT;
            break;

        case OPTI2CHR(OPTCHR_UNS):
            optiv.radix = RPNPROC_LIST_RADIX_UNS;
            break;

        case OPTI2CHR(OPTCHR_DELIM_PAR_INS):
            shift += _RPNPROC_DELIM_SHIFT_PARAM - _RPNPROC_DELIM_SHIFT_INSTR;

        case OPTI2CHR(OPTCHR_DELIM_INS_ADR):
            shift += _RPNPROC_DELIM_SHIFT_INSTR - _RPNPROC_DELIM_SHIFT_ADDR;

        case OPTI2CHR(OPTCHR_DELIM_ADR_LBL):
            shift += _RPNPROC_DELIM_SHIFT_ADDR - _RPNPROC_DELIM_SHIFT_NLBL;

        case OPTI2CHR(OPTCHR_DELIM_NONEXIST):
            if(*OPTI2ARG(argv))
                optiv.delim  =  optiv.delim & ~_RPNPROC_DELIM_MAKE(shift, ~0)  |
                                    _RPNPROC_DELIM_MAKE(shift, *OPTI2ARG(argv));
            break;

        default:
            if(!text2regs(argv[0], optiv.stackt))
                break;
            donef |= job_from_file(argv[0], &optiv);
            continue;
        }
        donef = 0;
    }
    if(!donef)
        job_from_file(INPUT_FILE_DFLT, &optiv);
    return(0);

#undef  _RPNPROC_DELIM_SHIFT_shift
}
