/*
 * This file is part of Commodore 64 emulator
 *      and Program Development System.
 *
 *   Copyright (C) 1993-1995,1996, Jouko Valta
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * Written by
 *   Jouko Valta <jopi@stekt.oulu.fi>
 *
 * Simplified and modified for use with ct-BASIC (derived from EhBASIC by Lee Davidson) 
 *   by C. Meyer, <cm@ctmagazin.de>, 03.12.2009
 *   runs on Win32, compile with "gcc ehbc.c -o EHBC.EXE"
 */

//#define DEBUG

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#include "types.h"


/* Limits */


#define NUM_COMM        120     /* Anzahl Tokens */
#define MAX_COMM        0xFE    /* common for all versions */

#define KW_NONE         0xFE    /* flag unused token */

#define CLARIF_LP       '#'     /* control code left delimiter */
#define CLARIF_RP       '#'     /* control code right delimiter */

#define CLARIF_LP_ST    "#"     /* control code left delimiter, "string version" */
#define CLARIF_RP_ST    "#"     /* control code right delimiter, "string version" */

/* ------------------------------------------------------------------------- */
/*
 * Two BASIC tokens which need some special care
 */
#define TOKEN_REM  (0x99 - 0x80)
#define TOKEN_DATA (0x83 - 0x80)


/*
 * EhBasic Keywords
Functions with (): 
SGN() INT() ABS() USR() FRE() POS() SQR() RND() LOG() EXP()
COS() SIN() TAN() ATN() PEEK() DEEK() SADD() LEN() STR$() VAL() ASC()      
UCASE$() LCASE$() CHR$() HEX$() BIN$()	BITTST() MAX() MIN()
VARPTR() LEFT$() RIGHT$() MID$()  
 */

const char *keyword[] = {
    /* EhBASIC Keywords */
    "END",   "FOR",  "NEXT", "DATA", "INPUT",  "DIM", "READ",
    "LET",  "DEC",  "GOTO", "RUN",  "IF", "RESTORE", "GOSUB", "COLOR",
    "CURSOR", "LABSET", "ELSE", "LABINP", "LABOUT", "LABREQ", "LABBUS",
    "MACRO", "RENUMBER", "RETURN", "REM",
    "PLOT",  "ON", "BANK", "INC",   
    "WAIT", "LOAD", "SAVE",  "DIR", "DEF", "POKE",
    "DOKE", "CALL", "DO", "LOOP", "PRINT", "FRAME", "LIST", "WINDOW", "NEW",
    "WIDTH", "GET", "SWAP", "BITSET", "BITCLR", 
    "TAB(", "TO", "FN",  "SPC(", "THEN",
    "NOT",   "STEP", "UNTIL", "WHILE", "OFF", "+",  "-", "*", "/", "^", "AND",
    "EOR", "OR",  ">>", "<<", ">",  "=", "<", "SGN(", "INT(", "ABS(",  "USR(",
    "FRE(",   "POS(",  "SQR(",  "RND(",  "LOG(",   "EXP(", "COS(",  "SIN(",
    "TAN(",   "ATN(",  "PEEK(", "DEEK(", "LABERR", "LEN(",  "STR$(",  "VAL(", "ASC(", 
    "UCASE$(", "LCASE$(", "CHR$(", "HEX$(", "BIN$(", "BITTST(", "MAX(", "MIN(", "PI", "SCANKEY",
    "VARPTR(",  "LEFT$(", "RIGHT$(", "MID$(", 
    "POSITION", "GRID",  "SIZE", "MOVE", "POINT", "LINE", "RECT",  "FILL",
    "CLEAR", "BORDER","CIRCLE","WAVE"
};


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

static void pet_2_asc (int ctrls);
static void _p_toascii(int c, int ctrls);
static int p_expand(int version, int addr, int ctrls);
static void p_tokenize(int version, unsigned int addr, int ctrls);
static unsigned char sstrcmp(unsigned char *line, const char **wordlist,
                             int token, int maxitems);

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

static FILE *source, *dest;
static int kwlen;


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

int main(int argc, char **argv)
{
    char *progname = NULL;
    int c = 0;

    long offset = 0;
    int txt2bas_mode = 0;         /* best defaults */
    int basic_start_addr = 0x0801, ctrls= 0;
    int filenum = 0;
    
	filenum = argc-1;
    progname = argv[0];
    fprintf(stdout,"\nc't-BASIC Converter Version 2.3\n");
    
    while (filenum !=0) {

	char *outfilename = malloc(strlen(argv[filenum])+1);
	char *infilename = malloc(strlen(argv[filenum])+1);

    /* Parse argument(s) */

    strcpy(infilename,argv[filenum]);
    strcpy(outfilename,argv[filenum]);

	// funktioniert
	if (strlen(infilename)>=4 && !stricmp(&infilename[strlen(infilename)-4],".bas")) {
	strcpy(&outfilename[strlen(outfilename)-4],".TXT");
    fprintf(stdout,"\nBASIC to TEXT, ");
    txt2bas_mode = 0;
    ctrls= 0;
	}
	
	if (strlen(infilename)>=4 && !stricmp(&infilename[strlen(infilename)-4],".txt")) {
	strcpy(&outfilename[strlen(outfilename)-4],".BAS");
    fprintf(stdout,"\nTEXT to BASIC, ");
    txt2bas_mode = 1;
    ctrls= 0;
	}
	
    fprintf(stdout,"converted to: %s\n\n", outfilename);

/*
 * Check parameters
 */
 
    if (txt2bas_mode) {
#ifdef DEBUG
        fprintf (stderr, "\nStart address %04x\n", basic_start_addr);
        fprintf(stderr, "Control code set: %s\n\n",
                (ctrls ? "enabled" : "disabled"));
#endif
    }

    int  plen = 0;

    if ((source = fopen(infilename, "rb")) == NULL) {
        fprintf(stderr,
                "Can't open input file %s\n", infilename);
        exit(1);   
    }
		
        if ((dest = fopen(outfilename, "wb")) == NULL) {
            fprintf(stderr,
                    "Can't open output file %s\n", outfilename);
            exit(1);
        }

    if (txt2bas_mode) {
        p_tokenize(0, basic_start_addr, ctrls);
    } else {

        /*
         * Use TEXT mode if the offset doesn't equal BASIC load addresses
         * and the first bytes to be read do not contain load address.
         */

             if (((offset & 255) !=1 &&
                ((c = getc(source)) != EOF && ungetc(c, source) != EOF &&
                c && c != 1)) ) {
            pet_2_asc(ctrls);
        }
        else {

            /* get load address */
            getc(source);
            basic_start_addr =0x801;	// damit Verlinkung stimmt +1


            if (p_expand(0, basic_start_addr, ctrls)) {
                fprintf(dest, "\r\n*** Machine language part skipped. ***\r\n");
            }
        }

    }
    fclose(source);
    fclose(dest);
	filenum--;
    } 

    return(0);
}

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

/* Conversion Routines */

static void pet_2_asc(int ctrls)
{
    int c;

    while ((c = getc(source)) != EOF) {
        _p_toascii(c, ctrls);           /* convert character */
    }      /* line */
}


static void _p_toascii(int c, int ctrls)
{
    switch (c) {
      case 0x00:                          /* 00 for SEQ */
      case 0x0a:
      case 0x0d:
        fputc ('\n', dest);
        break;
      default:
        if (isprint(c))
           fputc(c, dest);
        else if (ctrls) {
           fprintf(dest, CLARIF_LP_ST "$%02x" CLARIF_RP_ST, c & 0xff);
          }  /* ctrls */
    }  /* switch */
}


/*
 * This routine starts from the beginning of Basic, and not from the
 * load address included on program files. That way it can list from
 * RAM dump.
 */

static int p_expand(int version, int addr, int ctrls)
{
    static char line[4];
    unsigned int c;
    int quote, spnum, directory = 0;
    int sysflg = 0;

    /*
     * It seems to be common mistake not to terminate BASIC properly
     * before the machine language part, so we don't check for the
     * low byte of line link here.
     * Line link and line number are read separately to leave possible
     * next file on stdin intact.
     */

    while ((fread(line, 1, 2, source) == 2) && (/*line[0] ||*/ line[1]) &&
           fread(line + 2, 1, 2, source) == 2) {
        quote = 0;
        // Zeilennummer:
        fprintf(dest, "%5d ",
                (spnum = (line[2] & 0xff) + ((line[3] & 0xff) << 8)) );
        fprintf(stdout, "%5d ",
                (spnum = (line[2] & 0xff) + ((line[3] & 0xff) << 8)) );

        if (directory) {
            if (spnum >= 100)
                spnum = 0;
            else if (spnum >= 10)
                spnum = 1;
            else
                spnum = 2;
        }

        /* prevent list protection from terminating listing */
        while ((c = getc(source)) != EOF && !c);

        if (c == 0x12 && !line[2] && !line[3]) {  /* 00 00 12 22 */
            directory++;
        }

        do {
            if (c == 0x22)
                quote ^= c;


            /* basic 2.0, 7.0 & 10.0 and extensions */

            if (!quote && c > 0x7f) {
                if (c <= MAX_COMM) {
                    fprintf(dest, "%s", keyword[c & 0x7f]);
                    fprintf(stdout, "%s", keyword[c & 0x7f]);

                    if (c == 0x9E) {
                        ++sysflg;
                    }
                    continue;
                }
 
                    fprintf(dest, "%s", keyword[c & 0x7f]);
                    fprintf(stdout, "%s", keyword[c & 0x7f]);
            } /* quote */

            if (directory && spnum && c == 0x20) {
                spnum--;          /* eat spaces to adjust directory lines */
                continue;
            }

            _p_toascii((int)c, ctrls);  /* convert character */
           putc(c, stdout);

        } while ((c = getc(source)) != EOF && c);
        fprintf(dest, "\r\n");
        fprintf(stdout, " [CR]\r\n");

    }      /* line */

#ifdef DEBUG
    printf("\n c %02x  EOF %d  *line %d  sysflg %d\n",
           c, feof(source), *line, sysflg);
#endif

    return (!feof(source) && (*line | line[1]) && sysflg);
}

/*
03/09/2006 gpz:
- changed to output tokenized line into a second buffer instead of
  putting the tokens back in the input buffer. i wonder how this ever
  worked, control codes with a reasonable high repeatcount made
  previous versions of petcat segfault (or with luck, output trash)
- added error message when there is an unknown controlcode inside braces
*/
static void p_tokenize(int version, unsigned int addr, int ctrls)
{
    static char line[256];
    static char tokenizedline[256];
    unsigned char *p1, *p2, quote, c;
    unsigned char rem_data_mode, rem_data_endchar = '\0';
    int len = 0, match;
    unsigned int linum = 10;

    fprintf(dest, "%c", 0); // erste "0" bei Seitenanfang ($800)

    /* Copies from p2 to p1 */

    while((p2 = (unsigned char *)fgets(line, 255, source)) != NULL) {

	memset(tokenizedline, 0, 256);
	p1 = (unsigned char *)tokenizedline;
	

#ifndef GEMDOS
        if (sscanf(line, "%d%n", &linum, &len) == 1)
            p2 += len;
#else
        if (sscanf(line, "%d", &linum) == 1)
            while (isspace(*p2) || isdigit(*p2)) p2++;
#endif

#ifdef DEBUG
	fprintf(stderr,"line processed: %d\n",linum);
#endif


        quote = 0;
        rem_data_mode = 0;
        while (isspace(*p2)) p2++;

        while (*p2) {
            if (*p2 == 0x22) {
                quote ^= *p2;
            }
            if (*p2 == 0x0a || *p2 == 0x0d)
                break;

            match = 0;
            if (quote) {
                /*
                 * control code evaluation
                 * only strings that appear inside quotes are
                 * interpreted -- they should not be used elsewhere
                 */

                if (ctrls && (*p2 == CLARIF_LP)) {
                    unsigned char *p;
                    p = p2;

#ifdef DEBUG
	fprintf(stderr,"controlcode start: %c\n", *p2);
#endif

                    /* repetition count */
                    len = 1;
#ifndef GEMDOS
                    if (sscanf((char *)++p, "%d%n", &len, &kwlen) == 1) {
                        p += kwlen;
#else
                    if (sscanf(++p, "%d", &len) == 1) {
                        while (isspace(*p) || isdigit(*p)) p++;
#endif

#ifdef DEBUG
	fprintf(stderr,"controlcode repeat count: len:%d kwlen:%d\n", len,kwlen);
#endif

                    if (*p == ' ')
                            ++p;
                    }

                }
#ifdef DEBUG
/*	fprintf(stderr,"controlcode end\n"); */
#endif

            }
            else if (rem_data_mode) {
                /* if we have already encountered a REM or a DATA, simply copy the char */

                /* DO NOTHING! As we do not set "match", the if (!match) will be true,
                 * and this part will copy the char over to the new buffer */
            }
            else if (isalpha(*p2) || strchr("+-*/^>=<", *p2)) {


                /* Common Keywords
                 * Note:  ~ (pi) is tested later */


                if (!match) {
                    int max;

                    max = NUM_COMM;

                    if ((c = sstrcmp(p2, keyword, 0, max)) != KW_NONE) {
                        *p1++ = c | 0x80;	// Tokens haben Bit 7 gesetzt
                        p2 += kwlen;
                        match++;

                        /* Check if the keyword is a REM or a DATA */
                        switch (c) {
                        case TOKEN_DATA:
                            rem_data_mode = 1;
                            rem_data_endchar = ':';
                            break;

                        case TOKEN_REM:
                            rem_data_mode = 1;
                            rem_data_endchar = '\0';
                            break;
                        }                   
                    }
                }

             } /* !quote */

            if (!match) {
                *p1++ = *p2;

                /* check if the REM/DATA mode has to be stopped: */
                if (*p2 == rem_data_endchar)
                    rem_data_mode = 0;

                ++p2;
            } /* match */
        } /* while */

#ifdef DEBUG
	fprintf(stderr,"input line: %s\n", line);
#endif
	fprintf(stdout,"[%5d] %s [NULL]\n", linum,tokenizedline);

        *p1 = 0;
        if ((len = strlen(tokenizedline) ) > 0) {
            addr += len + 5;
            fprintf(dest, "%c%c%c%c%s%c", addr & 255, (addr>>8) & 255,
                   linum & 255, (linum>>8) & 255, tokenizedline, '\0');

            linum += 10; /* auto line numbering by default */
        }

#ifdef DEBUG
	fprintf(stderr,"output line end\n");
#endif

    } /* while */

    fprintf(dest, "%c%c", 0, 0);        /* program end marker */
}

static unsigned char sstrcmp(unsigned char *line, const char **wordlist,
                            int token, int maxitems)
{
    int j;
    const char *p, *q;

    kwlen = 1;
    /* search for keyword */
    for (; token < maxitems; token++) {
        for (p = wordlist[token], q = (char *)line, j = 0;
             *p && *q && *p == *q; p++, q++, j++);

        /*fprintf (stderr,
                 "compare %s %s - %d %d\n", wordlist[token], line, j, kwlen);*/

        /* found an exact or abbreviated keyword
         */
        if (j && (!*p || (*p && (*p ^ *q) == 0x20 && j++)) ) {
            kwlen = j;
            /*fprintf (stderr, "found %s %2x\n", wordlist[token], token);*/
            return token;
        }
    } /* for */

    return (KW_NONE);
}
