// (c) 1990-2007 Electronic Arts Inc.

const char *SPIT_history[] =
{
"------------------------------------------------------------------",
"                                                                  ",
"                             Uni v2.06                            ",
"                                                                  ",
"                     by FrANK G. Barchard, EAC                    ",
"                                                                  ",
"                    Main program - Nov 15, 2007                   ",
"                                                                  ",
"------------------------------------------------------------------",
"                                                                  ",
" Version Date   SE History:                                       ",
" ------- ----   -- --------                                       ",
"    1.00 971104 FB count duplicate lines and output # line        ",
"    1.01 971120 KO Works. No stdout.                              ",
"    1.02 000526 FB -nocount and %5d to make more consistant       ",
"    1.03 000527 FB -pc/-mac/-unix and -history                    ",
"    1.04 000918 FB -filename                                      ",
"    1.05 010929 FB fix last line if no cr/lf on it                ",
"    1.06 011006 FB -count2 to count things with 2 or more         ",
"    1.07 011031 FB -noblank                                       ",
"    1.08 011124 FB -less<n>                                       ",
"    1.09 030923 FB -count use %d\t to import into excel directly  ",
"    1.10 030924 FB -i case independent for dictionary duplicates  ",
"    1.11 040616 FB remove duplicates that arent sorted (4fifa)    ",
"    2.00 040629 FB rewrite with -count=                           ",
"    2.01 040713 FB renamed to uni. -last to find last occurance   ",
"    2.02 040717 FB -tolower, -i, -blank, -showcount, -less, -v    ",
"    2.03 040720 FB many to one                                    ",
"    2.04 041013 FB -first                                         ",
"    2.05 070710 FB -file only keep if file exists                 ",
"    2.06 070710 FB -file remove quotes                            ",
"                                                                  ",
"------------------------------------------------------------------",
"\0"
};

/****************************************************************/
/*  Spit Setup                                                  */
/****************************************************************/

#include    "spit.h"

const char TextVersion[]="2.06 (Samba) 2007.12.4";
#define TOOL_VERSION 206
#define TOOL_NAME "uni"

static int OPTcount;
static int OPTcounteq;
static int OPTshowcount;
static int OPTless;
static int OPTlast;
static int OPTnewlinetype;
static int OPTfilenamesort;
static int OPTblank;
static int OPTcaseindependent;
static int OPTtolower;
static int OPTfile;

static char *DUPnextline(const char *s)
{
    while (*s)
    {
        if (*s==10)
        {
            ++s;
            if (*s==13)
                ++s;
            break;
        }
        if (*s==13)
        {
            ++s;
            if (*s==10)
                ++s;
            break;
        }
        ++s;
    }
    return((char *)s);
}

static char *DUPgetline(char *buf,const char *s)
{
    char *d=buf;
    while (*s)
    {
        if (*s==10)
        {
            ++s;
            if (*s==13)
                ++s;
            break;
        }
        if (*s==13)
        {
            ++s;
            if (*s==10)
                ++s;
            break;
        }
        *d++ = *s++;
    }
    *d = 0;
    return(buf);
}

static void DupOutput(SPITMEM *outfile, const char *outLine,int countSame)
{
    char *d=outfile->ptr;
    int len=outfile->len;

    if (*outLine || OPTblank)
    {
        char thisLine[SPITMAXLINE];
        thisLine[0] = '\0';

        if (OPTshowcount)
        {
            sprintf(thisLine,"%5d ",countSame);
        }
        strcat(thisLine,outLine);
        if (OPTtolower)
            strlwr(thisLine);

        if (OPTverbose>=4)
            printf("%s\n",thisLine);
        strcpy(d+len,thisLine);
        len += strlen(thisLine);

        switch (OPTnewlinetype)
        {
            case 0: /* PC style cr/lf */
                d[len++] = 13;
                d[len++] = 10;
                break;

            case 1: /* Mac style cr */
                d[len++] = 13;
                break;

            default: /* Unix style lf */
                d[len++] = 10;
                break;
        }
        d[len] = '\0';
    }
    outfile->len = len;
}

static int DupCount(SPITMEM *infile, const char *str, int &lineFirst, int &lineLast)
{
    char *s1 = infile->ptr;
    int lineCount=0;
    int count=0;
    while (*s1)
    {
        char thisLine[SPITMAXLINE];
        DUPgetline(thisLine,s1);
        s1 = DUPnextline(s1);

        int diff;
        if (OPTcaseindependent)
        {
#if defined(_MSC_VER)
            diff = stricmp(thisLine,str);
#else
            diff = strcasecmp(thisLine,str);
#endif
        }
        else
        {
            diff = strcmp(thisLine,str);
        }

        if (!diff)
        {
            ++count;
            if (lineFirst<0)
                lineFirst = lineCount;
            lineLast = lineCount;
        }
        ++lineCount;
    }
    return count;
}

static int unifile(SPITMEM *infile, SPITMEM *outfile)
{
    char *s1 = infile->ptr;
    int lineCount=0;
    outfile->len = 0;
    while (*s1)
    {
        char thisLine[SPITMAXLINE];
        DUPgetline(thisLine,s1);
        s1 = DUPnextline(s1);
        int lineFirst=-1, lineLast=-1;
        int countSame = DupCount(infile,thisLine,lineFirst,lineLast);
        bool shouldOutput = true;

        if (OPTcount && countSame<OPTcount)
            shouldOutput = false;
        if (OPTcounteq && countSame!=OPTcounteq)
            shouldOutput = false;
        if (OPTless && countSame>=OPTless)
            shouldOutput = false;

        if (OPTfile && shouldOutput)
        {
            GSTREAM *g = gopen(thisLine);
            if (g)
                gclose(g);
            else
            {
                if (thisLine[0]=='"')
                {
                    char unquoteLine[SPITMAXLINE];
                    strcpy(unquoteLine,thisLine+1);
                    int l = strlen(unquoteLine);
                    if (unquoteLine[l-1]=='"')
                        unquoteLine[l-1]='\0';

                    GSTREAM *g = gopen(unquoteLine);
                    if (g)
                        gclose(g);
                    else
                        shouldOutput = false;
                }
                else
                    shouldOutput = false;
            }
        }

        if (OPTlast)
        {
            if (lineLast!=lineCount)
                shouldOutput = false;
        }
        else
        {
            if (lineFirst!=lineCount)
                shouldOutput = false;
        }
        if (shouldOutput)
            DupOutput(outfile,thisLine,countSame);

        if (OPTverbose>=5)
            printf("line %d found %d times, first %d, last %d, output %s: %s\n",lineCount,countSame,lineFirst,lineLast,shouldOutput?"yes":"no",thisLine);
        ++lineCount;
    }
    return lineCount;
}

/*
;
; ABSTRACT
;
; uni - count duplicate lines and remove extras
;
; SUMMARY
;
; uni -options [file.txt]
;     @file          response file, containing a list of options and files
;     =file          specify a output file ie source=dest
;     -b0            disable banner
;     -v0            no console output
;     -v1            less output
;     -v2            normal output (default)
;     -v             more output (aka -v3). Shows output file to console
;     -i             case independent
;     -w0            dont output file
;     -r             recurse subdirectories to find files
;     -r%ENV%        traverse environment variable ENV to find files
;     -count<n>      find counts >= specifed value and print the count
;     -count=<n>     find counts == specifed value
;     -less<n>       find counts < specifed value and print the count
;     -last          find last occurance of each entry
;     -first         find first occurance of each entry (default)
;     -file          only keep names where the file exists
;     -showcount     print a count
;     -tolower       make output lower case
;     -blank         count blank lines
;     -noblank       dont count blank lines (default)
;
; DESCRIPTION
;
; Uni removes duplicate lines from a text file.  The original order is maintained
; with the first occurance being output be default.  -last will output the last
; occurance instead.
;
; Unique file name lists are useful for building assets with optimized order for better layout.
;
; Uni takes a count of the line, and options (-count,-less) control if the line should be output based on that count
;
; RETURNS
;
; 0 if successful
; errorcount if there were errors (ie file not found)
;
; SEE ALSO
;
; txt, gsr, Spit
;
; SAMPLE OUTPUT
;
; >uni sorted.txt=unied.txt
;
; END ABSTRACT
;
*/


/****************************************************************/
/*  SPIT Routines                                               */
/****************************************************************/

void SPIT_reset(void)
{
    OPTcount = 0;
    OPTcounteq = 0;
    OPTless = 0;
    OPTlast = 0;
#if defined(__WATCOMC__) || defined(_MSC_VER)
    OPTnewlinetype = 0;   /* crlf */
#elif defined(applec) || defined(powerc) || defined (__powerc)
    OPTnewlinetype = 1;   /* cr */
#else
    OPTnewlinetype = 2;   /* lf */
#endif
    OPTfilenamesort = 1;
    OPTcaseindependent = 0;
    OPTblank = 0;
    OPTshowcount = 0;
    OPTtolower = 0;
    OPTfile = 0;
    return;
}

char *optionhelp[]=
{
    "    @file          response file, containing a list of options and files\n",
    "    =file          specify a output file ie source=dest\n"
    "    -b0            disable banner\n",
    "    -v0            no console output\n"
    "    -v1            less output\n"
    "    -v2            normal output (default)\n"
    "    -v             more output (aka -v3). Shows output file to console\n",
    "    -i             case independent\n",
    "    -w0            dont output file\n",
    "    -r             recurse subdirectories to find files\n",
    "    -r%ENV%        traverse environment variable ENV to find files\n",
    "    -count<n>      find counts >= specifed value and print the count\n",
    "    -count=<n>     find counts == specifed value\n",
    "    -less<n>       find counts < specifed value and print the count\n",
    "    -last          find last occurance of each entry\n",
    "    -first         find first occurance of each entry\n",
    "    -file          only keep names where the file exists\n",
    "    -showcount     print a count\n",
    "    -tolower       make output lower case\n",
    "    -blank         count blank lines\n",
    "    -noblank       dont count blank lines (default)\n",
    0,
};

void SPIT_usage(const char *prgname, const char *envname, const char *urlname, int utype)
{
    char **hlp;

    printf("uni v%s by FrANK G. Barchard\n",TextVersion);

    if (utype)
        printf("USAGE: %s -options [files.txt] =[outputfile.txt]\n", prgname);

    switch (utype)
    {
        case 0:
        case 1:
            break;

        case 2:
            puts("    -count                       -showcount");
            puts("    -blank                       -noblank");
            puts("    -v verbose                   -less");
            puts("    -i case independent          -tolower");
            puts("    -first                       -last");
            puts("    -file");
            puts("    @response file               =output file");
          printf("type '%s -h' for more help\n", prgname);
          printf("type '%s -help' for web page (%s)\n", prgname, urlname);
            break;

        case 3:
            puts("");
          printf("VERSION: %s v%s, Spit v%.2f\n",SPIT_MACHINE,TextVersion,(float)SPIT_VERSION/100.0f);
            puts("SUMMARY: Uni removes duplicate lines from a text file, keeping the UNIque ones");
          printf("         SPIT environment variable is %s\n",envname);
            puts("");

            hlp = optionhelp;
            while (*hlp)
            {
                printf("%s",*hlp);
                ++hlp;
            }
            break;

         case 4:
            puts("");
            {
                hlp = optionhelp;
                while (*hlp)
                {
                    if (SPIT_strstr(*hlp,envname))
                        printf("%s",*hlp);
                    ++hlp;
                }
                puts("");
            }
            break;

        default:
            break;
    }
}

char *SPIT_opt(const char *optptr)
{
    char *d=NULL;
    int i;
    char optbuf[SPITMAXLINE];

    for (i=0; (unsigned char)optptr[i]>(unsigned char)' ' && i<SPITMAXLINE; ++i)
        optbuf[i] = (char) tolower(optptr[i]);
    optbuf[i] = 0;

    if (!stricmp(optbuf,"pc") || !stricmp(optbuf,"ibm") || !stricmp(optbuf,"dos") || !stricmp(optbuf,"pc") || !stricmp(optbuf,"crlf"))
    {
        OPTnewlinetype = 0;
        d = (char *)optptr+i;
    }
    else if (!stricmp(optbuf,"mac") || !stricmp(optbuf,"mpw") || !stricmp(optbuf,"ppc") || !stricmp(optbuf,"cr"))
    {
        OPTnewlinetype = 1;
        d = (char *)optptr+i;
    }
    else if (!stricmp(optbuf,"sgi") || !stricmp(optbuf,"linux") || !stricmp(optbuf,"unix") || !stricmp(optbuf,"lf"))
    {
        OPTnewlinetype = 2;
        d = (char *)optptr+i;
    }

    if (!d)
    {
        switch (*optptr|(char)' ')
        {
            case 'b':
                if (!strnicmp(optptr,"blank",5))
                {
                    d = SPIT_getopt( &OPTblank,optptr+5, "count blank lines", 0, 1);
                }
                break;


            case 'c':
                if (!strnicmp(optptr,"count=",6))
                {
                    d = SPIT_getopt( &OPTcounteq,optptr+6, "count equals", -1, 0x7fffffff);
                }
                else if (!strnicmp(optptr,"count",5))
                {
                    d = SPIT_getopt( &OPTcount,optptr+5, "count", -1, 0x7fffffff);
                }
                break;


            case 'f':
                if (!strnicmp(optptr,"first",5))
                {
                    int OPTfirst = !OPTlast;
                    d = SPIT_getopt( &OPTfirst,optptr+5, "find first occurance", 0, 1);
                    OPTlast = !OPTfirst;
                }
                else if (!strnicmp(optptr,"file",4))
                {
                    d = SPIT_getopt( &OPTfile,optptr+4, "check if files exist", 0, 1);
                }
                break;

            case 'l':
                if (!strnicmp(optptr,"less",4))
                {
                    d = SPIT_getopt( &OPTless,optptr+4, "count less than specified count", -1, 0x7fffffff);
                }
                else if (!strnicmp(optptr,"last",4))
                {
                    d = SPIT_getopt( &OPTlast,optptr+4, "find last occurance", 0, 1);
                }
                break;

            case 'i':
                d = SPIT_getopt( &OPTcaseindependent,optptr+1, "case independent", 0, 1);
                break;

            case 'n':
                if (!strnicmp(optptr,"nocount",7)) // old option.. overloaded with count so now its called showcount
                {
                    OPTshowcount ^= 1;
                    d = SPIT_getopt( &OPTshowcount,optptr+7, "no show count", 0, 1);
                    OPTshowcount ^= 1;
                }
                else if (!strnicmp(optptr,"noblank",7))
                {
                    OPTblank ^= 1;
                    d = SPIT_getopt( &OPTblank,optptr+7, "no blank", 0, 1);
                    OPTblank ^= 1;
                }

            case 's':
                if (!strnicmp(optptr,"showcount",9))
                {
                    d = SPIT_getopt( &OPTshowcount,optptr+9, "show count", 0, 1);
                }
                break;

            case 't':
                if (!strnicmp(optptr,"tolower",7))
                {
                    d = SPIT_getopt( &OPTtolower,optptr+7, "convert to lower case", 0, 1);
                }
                break;



            default:
                break;
        }
    }
    return(d);
}

int main(int argc, char* argv[])
{
    int more;
    int i;
    int isopt;
    int errorCount=0;
    SPITMEM infile;
    SPITMEM outfile;
    int lineCount=0;
    int returnvalue=0;
    char statbuf[SPITMAXLINE];

    SPIT_init(SPIT_reset, SPIT_usage, SPIT_opt, SPIT_history);

    do
    {
        more = SPIT_parse(argc, argv, TOOL_NAME, TOOL_VERSION);

        if (!SPIThelpgiven)
        {
            SPITMEM infiles;
            SPIT_alloc(&infiles,1);
            infiles.ptr = '\0';
            infiles.len = 0;
            int outArg=0;

            for (i=0; i<SPITargnum; ++i)
            {
                isopt = 0;
                if (SPITsargv[i][0]=='-' || SPITsargv[i][0]=='+' || (SPITsargv[i][0]==OPTdot && SPITsargv[i][1]!='.' && SPITsargv[i][1]!='/' && SPITsargv[i][1]!='\\'))
                {
                    isopt = Zop(SPITsargv[i]+1)?1:0;
                }
                else if (SPITsargv[i][0]=='=')
                {
                    isopt = Zop(SPITsargv[i])?1:0;
                }

                if (!isopt)
                {
                    if (SPIT_loadfilev(&infile, SPITsargv[i], OPTverbose))
                    {
                        int newLen = infiles.len + infile.len;
                        SPITMEM tempfiles;
                        if (SPIT_alloc(&tempfiles,newLen+1))
                        {
                            memcpy(tempfiles.ptr,infiles.ptr,infiles.len);
                            memcpy(tempfiles.ptr+infiles.len, infile.ptr, infile.len);
                            tempfiles.ptr[infiles.len+infile.len] = '\0';
                            tempfiles.len = infiles.len+infile.len;
                            SPIT_free(&infiles);
                            infiles = tempfiles;
                        }
                        else
                        {
                            if (OPTverbose)
                                printf(TOOL_NAME " - ERROR out of memory for source file %s\n",SPITsargv[i]);
                            ++errorCount;
                        }
                        SPIT_free(&infile);
                        outArg = i;
                    }
                }
            }

            if (SPIT_alloc(&outfile, infiles.len*2+50000L))
            {
                if (OPTverbose==3)
                {   printf("Processing, ");
                    SPIT_flush();
                }
                else if (OPTverbose>=4)
                {   printf("\b:\n");
                    SPIT_flush();
                }

                lineCount = unifile(&infiles, &outfile);

                if (OPTwrite)
                    SPIT_savefile(&outfile, SPITdargv[outArg]);

                /* print some stats */

                if (OPTverbose>=3)
                {
                    SPIT_percentstr(statbuf, infiles.len, outfile.len);
                    printf("%ld->%ld (%s)\n", infiles.len, outfile.len, statbuf);
                    SPIT_flush();
                }

                SPIT_free(&outfile);
            }
            else
            {
                if (OPTverbose>=2)
                    printf(TOOL_NAME " - ERROR could not allocate dup buffer\n");
            }
            SPIT_free(&infiles);
        }
    } while (more>0);

    SPIT_restore();

    return errorCount;
}
