/*
 * ptmid.c: Creates Protracker MODule files from MIDI files.
 * (My first attempt at Hungarian Notation.. wince!)
 *
 * Author: Andrew Scott  (c)opyright 1994
 *
 * Date: 17/11/1993 ver 0.0
 *       8/1/1994   ver 0.1
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* #include <io.h> */  /* BJS */
#include "ptmid.h"

char bDrumch = 9,
     szId[5] = "M.K.";
int  fNocopy = 0,
     fQuiet = 0,
     fExtend = 0;
int  wPatmax = 64,
     wPtchan = 4,
     wQuantval = 16;
SI   *rgpsiDrum[128],
     **rgppsiIns[129],
     *psiTree = NULL;
Sz   szTitle = "Converted by PTMID!",
     szQuant = NULL;

/*
 * Init: Does all those initialization things (which aren't too involved).
 */

static void Init()
{
    int i;

    rgppsiIns[128] = NULL; /** Make sure sample-info arrays are clear **/
    for (i = 128; i--; i>=0) {
        rgpsiDrum[i] = NULL;
        rgppsiIns[i] = NULL;
    }
}

/*
 * BuildFn: Builds a full filename given a string which is the old filename,
 * and a default extension to use if one is not present in the string. After
 * building the new filename, any extension in the old name is removed.
 */
void BuildFn(Fn fnNew, Sz fnOld, Sz fnExt)
{
    Sz fnT = fnNew;
    int fExt = 0;

    while (*fnOld) {
        if ('.' == (*(fnT++) = *fnOld)) { /** Copy a char, test for extension **/
            fExt = 1;
            *fnOld = 0; /** yes.. note extension exists and remove it **/
        }
        fnOld++;
    }
    if (!fExt) { /** If no extension found **/
        *(fnT++) = '.';
        while ((*(fnT++) = *(fnExt++))); /** copy the default one - fnExt **/
    } else
        *fnT = 0;
}

/*
 * SzReadPfile: Reads the next string from the textfile given and returns it.
 * If file is at end, returns NULL.
 */
Sz SzReadPfile(FILE *pfileText)
{
    int ch, cch = 0, cchMac = 80;
    Sz szT, szStart;

    if (feof(pfileText))
        return NULL;
    szStart = szT = (Sz) malloc(80); /** Set aside 80 characters for line **/
    while ((ch = getc(pfileText)) != EOF && ch != '\n') {
        *szT = ch;
        if (++cch == cchMac) { /** If that's not enough **/
            cchMac += 40;
            szStart = (Sz) realloc(szStart, cchMac); /** increase in steps of 40 **/
            szT = szStart + cch;
        } else
            szT++;
    }
    *szT = 0;
    return (Sz) realloc(szStart, cch + 1);
}

/*
 * PsiAddsample: Given a sample's filename, will look it up in the tree
 * and return a pointer to it if it exists, else will create it and return
 * a pointer to the newly created entry.
 */
SI *PsiAddsample(Sz fnSample)
{
    SI *psiT;

    if (NULL == psiTree) { /** If nothing in tree **/
        psiT = psiTree = (SI *) malloc(sizeof(SI)); /** create root node **/
        psiT->fnSample = strdup(fnSample);
        psiT->pitch = -1;
        psiT->sample = -1;
        psiT->psiL = psiT->psiR = NULL;
    } else { /** Else **/
        SI *psiOld;
        int cmp;

        psiT = psiTree;
        while (psiT != NULL) { /** find spot for sample in tree **/
            psiOld = psiT;
            cmp = strcmp(psiT->fnSample, fnSample);
            if (!cmp)
                break;
            else if (0 > cmp)
                psiT = psiT->psiL;
            else
                psiT = psiT->psiR;
        }
        if (NULL == psiT) {
            psiT = (SI *) malloc(sizeof(SI)); /** and create entry **/
            if (0 > cmp)
                psiOld->psiL = psiT;
            else
                psiOld->psiR = psiT;
            psiT->fnSample = strdup(fnSample);
            psiT->pitch = -1;
            psiT->sample = -1;
            psiT->psiL = psiT->psiR = NULL;
        }
    }
    return psiT;
}

/*
 * PsiPrunePsi: Returns the given sample tree, but without any redundant
 * samples. Any redundant samples are freed. If no samples remain, NULL
 * is returned.
 */
SI *PsiPrunePsi(SI *psi)
{
    if (NULL == psi)
        return NULL;
    psi->psiL = PsiPrunePsi(psi->psiL); /** Prune left and right branches **/
    psi->psiR = PsiPrunePsi(psi->psiR);
    if (-1 == psi->pitch) { /** If root of tree redundant, need to remove it **/
        SI *psiT;

        if (NULL == psi->psiL) { /** If no left branch **/
            psiT = psi->psiR;
            free(psi); /** replace root **/
            psi = psiT; /** with right branch **/
        } else if (NULL == psi->psiR) { /** If no right branch **/
            psiT = psi->psiL;
            free(psi); /** replace root **/
            psi = psiT; /** with left branch **/
        } else if (NULL == psi->psiL->psiR) { /** If left branch has no right **/
            psiT = psi->psiL;
            psiT->psiR = psi->psiR; /** put right branch on right of left **/
            free(psi); /** and replace root **/
            psi = psiT; /** with left branch **/
        } else { /** Else.. there's 2 full branches - yuck! **/
            SI *psiOld;

            psiT = psi->psiL;
            while (NULL != psiT->psiR) { /** Find rightmost entry on left branch **/
                psiOld = psiT;
                psiT = psiT->psiR;
            }
            psiOld->psiR = psiT->psiL;
            psiT->psiL = psi->psiL;
            psiT->psiR = psi->psiR;
            free(psi); /** remove root **/
            psi = psiT; /** and replace it with that entry **/
        }
    }
    return psi;
}

/*
 * ReadconfigFn: Given the filename of the configuration file, it interprets
 * each line and sets up options and sample-tables.
 */
void ReadconfigFn(Sz fnConfig)
{
    FILE *pfileConfig;
    Sz szLine, szTok;
    int csz = 0, fError = 0;

    /* BJS on s:ptmid.cfg */
    if(NULL==(pfileConfig=fopen("s:ptmid.cfg","rt"))) {
        if (NULL == (pfileConfig = fopen(fnConfig, "rt"))) {
            fprintf(stderr, "ptmid: Cannot find config file: %s\n", fnConfig);
            exit(1);
        }
    }

    while ((szLine = SzReadPfile(pfileConfig)) != NULL) { /** With every line.. **/
        csz++;
        if ('#' != szLine[0] && NULL != (szTok = strtok(szLine, " \t")))
            if ('0' <= szTok[0] && '9' >= szTok[0] || !strcmp(szTok, "def")) {
                int irgppsi, cpsi; /** If an instrument definition **/
                SI **ppsi;

                if ('d' == szTok[0])
                    irgppsi = 128;
                else
                    irgppsi = atoi(szTok); /** decode instrument **/
                if (irgppsi < 129)
                    while (NULL != (szTok = strtok(NULL, " \t"))) { /*** With every sample.. ***/
                        if (NULL == rgppsiIns[irgppsi]) /*** Ensure allocated ***/
                            rgppsiIns[irgppsi] = ppsi = (SI **) malloc(sizeof(SI *) * 2);
                        else {
                            ppsi = rgppsiIns[irgppsi];
                            for (cpsi = 2; NULL != *ppsi; cpsi++, ppsi++);
                            rgppsiIns[irgppsi] = ppsi = (SI **) realloc(rgppsiIns[irgppsi],
                                sizeof(SI *) * cpsi);
                            ppsi += cpsi - 2;
                        }
                        ppsi[0] = PsiAddsample(szTok); /*** Put sample in array ***/
                        ppsi[1] = NULL;
                    }
                else
                    fError = 1;

            } else if ('d' == szTok[0] && '0' <= szTok[1] && '9' >= szTok[1]) {
                int irgpsi; /** If a percussion definition **/

                if ((irgpsi = atoi(szTok + 1)) < 128 && /** decode instrument **/
                 (szTok = strtok(NULL, " \t")) != NULL) {
                    if (NULL != rgpsiDrum[irgpsi])
                        free(rgpsiDrum[irgpsi]); /** and free up if previously used **/
                    rgpsiDrum[irgpsi] = PsiAddsample(szTok); /** Put sample in array **/
                } else
                    fError = 1;

            } else if (!strcmp(szTok, "sample")) { /** If sample info **/
                Sz fnSample;
                SI *psi;
                int irgb;
                static int rgbPitch[7] = {45, 47, 36, 38, 40, 41, 43};

                if ((fnSample = strtok(NULL, " \t")) == NULL) /** Get name **/
                    fError = 1;
                else if ((szTok = strtok(NULL, " \t")) == NULL)
                    fError = 1;
                else if ((irgb = toupper(szTok[0]) - 'A') < 0 || 6 < irgb) /** Get pitch **/
                    fError = 1;
                else {
                    psi = PsiAddsample(fnSample); /** Make sure sample allocated **/
                    if ('#' == szTok[1])
                        szTok++;
                    psi->pitch = rgbPitch[irgb] + 12 * atoi(szTok + 1) +
                        ('#' == szTok[0] ? 1 : 0);
                    if ((szTok = strtok(NULL, " \t")) == NULL)
                        psi->wLppos = 0;
                    else
                        psi->wLppos = atoi(szTok); /** Get loop start **/
                    if ((szTok = strtok(NULL, " \t")) == NULL)
                        psi->wLplen = 0;
                    else
                        psi->wLplen = atoi(szTok); /** Get loop length **/
                }

            } else if (!strcmp(szTok, "formid")) /** If module-id **/
                if ((szTok = strtok(NULL, " \t")) == NULL)
                    fError = 1;
                else
                    strncpy(szId, szTok, 4); /** store it **/

            else if (!strcmp(szTok, "patmax")) /** If max. patterns **/
                if ((szTok = strtok(NULL, " \t")) == NULL)
                    fError = 1;
                else {
                    int wT;

                    if ((wT = atoi(szTok)) != 0 && 128 >= wT)
                        wPatmax = wT; /** store them **/
                    else
                        fError = 1;
                }

            else if (!strcmp(szTok, "ptchan")) /** If max. channels **/
                if ((szTok = strtok(NULL, " \t")) == NULL)
                    fError = 1;
                else {
                    int wT;

                    if ((wT = atoi(szTok)) != 0 && MAXPTCHAN >= wT)
                        wPtchan = wT; /** store them **/
                    else
                        fError = 1;
                }

            else if (!strcmp(szTok, "drumch")) /** If MIDI percussion channel **/
                if ((szTok = strtok(NULL, " \t")) == NULL)
                    fError = 1;
                else {
                    int bT;

                    if ((bT = atoi(szTok)) != 0)
                        bDrumch = bT - 1; /** store it **/
                }

            else if (!strcmp(szTok, "fract") && NULL == szQuant) { /** If quantize frac. **/
                int wT;

                if ((szQuant = strtok(NULL, " \t")) == NULL ||
                 !(wT = ValidquantSz(strlwr(szQuant)))) /** decode **/
                    fError = 1;
                else
                    wQuantval = wT; /** and store it **/

            } else if (!strcmp(szTok, "extend")) /** If extend-notes flag **/
                fExtend = 1; /** toggle **/
            else if (!strcmp(szTok, "nocopy")) /** If no-copyright flag **/
                fNocopy = 1; /** toggle **/
            else
                fError = 1;

            if (fError) { /** If an error at any point, reveal line **/
                fprintf(stderr, "ptmid: Error in config file: line %d\n", csz);
                exit(1); /** and quit **/
            }
        free(szLine);
    }
    if (NULL == rgppsiIns[128]) {
        fprintf(stderr, "ptmid: No default instrument defined in config file\n");
        exit(1);
    }
    if ((psiTree = PsiPrunePsi(psiTree)) == NULL) {
        fprintf(stderr, "ptmid: No sample definitions found in config file\n");
        exit(1);
    }
}

/*
 * main: Parses arguments to program and opens appropriate MOD and MID files.
 */
int main(int argc, char **argv)
{
    int cNames = 0;
    Sz fnDef, fnConfig = DEFCONFIG;
    Fn fnIn, fnOut;
    FILE *pfileMod;
    Tune *ptuneMusic;

    Init();
    for (argv++; NULL != *argv; argv++) /** Run through all parameters **/
        if ('-' == **argv) /** If parameter is a switch **/
            switch ((*argv)[1]) { /** check what sort **/
                case 'c':
                    if ((*argv)[2])
                        fnConfig = *argv + 2; /** c: set config file **/
                    break;
                case 'd':
                    bDrumch = atoi(*argv + 2) - 1; /** d: set drum channel **/
                    break;
                case 'f': {
                    int wT; /** f: set quantize fraction **/

                    if ((wT = ValidquantSz(strlwr(*argv + 2))))
                        wQuantval = wT;
                    else
                        fprintf(stderr, "ptmid: Invalid quantize fraction - using default\n");
                    break;
                }
                case 'q':
                    fQuiet = !fQuiet; /** q: toggle quiet mode **/
                    break;
            }
        else { /** Else must be a filename **/
            if (0 == cNames)
                BuildFn(fnIn, fnDef = *argv, "mid");
            else if (1 == cNames)
                BuildFn(fnOut, *argv, "mod");
            cNames++;
        }

    if (1 > cNames || 2 < cNames) { /** If no filenames - error **/
        printf("Use: ptmid [-cFile] [-dChannel] [-fFrac] [-q] infile[.mid] [outfile[.mod]]\n");
        printf("Amiga V1.0 conversion by Brian Sullivan\n");
        printf("  version " PTVER "\n");
        printf("  Creates Protracker MOD files from General MIDI files\n");
        exit(1);
    } else if (1 == cNames) /** If one filename **/
        BuildFn(fnOut, fnDef, "mod"); /** build other one **/
    if (!fQuiet)
        printf("ptmid ver " PTVER ": Converting '%s' to '%s'\n", fnIn, fnOut);

    ReadconfigFn(fnConfig);
    if (access(fnIn, 1)) {
        fprintf(stderr, "ptmid: Cannot access file: %s\n", fnIn);
        exit(1);
    }
    if ((ptuneMusic = PtuneLoadFn(fnIn)) == NULL) {
        fprintf(stderr, "ptmid: Not a legal MIDI file: %s\n", fnIn);
        exit(1);
    }

    if (!fQuiet)
        printf("Analyzing..\n");
    ResolvePtune(ptuneMusic);

    if (!fQuiet)
        printf("Writing..\n");
    if ((pfileMod = fopen(fnOut, "wb")) == NULL) {
        fprintf(stderr, "ptmid: Cannot create file: %s\n", fnOut);
        exit(1);
    }
    SavePtunePfile(ptuneMusic, pfileMod);
    fclose(pfileMod);

    if (!fQuiet)
        printf("Done.\n");
}