/*
    cookietool is (c) 1995-2001 by Wilhelm Noeker (wnoeker@t-online.de)

    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., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA

 */


/*========================================================================*\
 |  File: compress.c                                   Date: 22 Mar 2001  |
 *------------------------------------------------------------------------*
 |     Read cookies, remove duplicates, sort, and write back to file.     |
 |       These routines are common to both cookietool and cdbdiff.        |
 |                                                                        |
\*========================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "cookio.h"
#include "compress.h"



struct cookie
    {
    UBYTE *text;
    UBYTE *sorthook;
    long size;
    long number;
    };

struct cookie *clist = NULL;
long listsize = 0;              /* will be adjusted dynamically */
long listed = 0;


/*
 * Assign ascending numbers to all cookies in the list and reset each sort
 * hook to the start of its body text.
 */
void rebuild_listinfo()
    {
    long l;

    for( l = 0; l < listed; l++ )
        {
        clist[ l ].number = l;
        clist[ l ].sorthook = clist[ l ].text;
        }
    }



/*
 * Build cookie list from file.
 * The list may or may not be empty before this call.
 */
void read_cookies( FILE *fp, int fmt )
    {
    long lines, cbuflen, offset, ignored = 0;
    char *cptr;

    offset = listed;            /* may or may not be 0 */
    printf( "Reading cookies..." );
    fflush( stdout );
    while( (cptr = read_cookie( fp, fmt, &cbuflen, &lines, NULL )) != NULL )
        {
        if( lines > 0 )
            {                   /* store the cookie */
            if( listed == listsize )
                {               /* we start with listsize==0, clist==NULL ! */
                listsize = 3 * listsize / 2 + 1000;
                clist = realloc( clist, listsize * sizeof( struct cookie ) );
                if( !clist )
                    {
                    printf( "\nList reallocation failed\n" );
                    exit( 20 );
                    }
                }
            clist[ listed ].text = malloc( cbuflen + 1 );       /* mind the '\0'! */
            if( clist[ listed ].text != NULL )
                {
                clist[ listed ].size = cbuflen;
                strcpy( clist[ listed ].text, cptr );
                }
            else
                {
                printf( "\nOut of memory\n" );
                exit( 20 );
                }
            listed++;
            }
        else
            ignored++;          /* or ignore it */
        }
    rebuild_listinfo();
    printf( " done. (%ld read, %ld empty)\n", listed-offset, ignored );
    }


/*
 * Write cookies to file, optionally skipping some at the start of the
 * list.
 */
void write_cookies( FILE *fp, int fmt, long offset )
    {
    long l;

    printf( "Writing cookies..." );
    fflush( stdout );
    for( l = offset; l < listed; l++ )
        if( !write_cookie( clist[ l ].text, fp, fmt ) )
            {
            printf( "\nFile error, aborted !!!\n" );
            exit( 20 );
            }
    printf( " done. (%ld written)\n", listed-offset );
    }



/*
 * Cookie comparison, for sorting.
 */
int cookie_cmp( struct cookie *a, struct cookie *b, int mode )
    {
    int c = 0;

    switch( mode )
        {
        case SORT_BODY:         /* by name */
            c = str_cmp( a->sorthook, b->sorthook );
            break;
        case SORT_REVERSE:      /* descending, by name */
            c = str_cmp( b->sorthook, a->sorthook );
            break;
        case SORT_SIZE:         /* by size */
            c = a->size - b->size;
            break;
        }
    if( c == 0 )                /* when in doubt, the number decides */
        c = a->number - b->number;
    return c;
    }



/*
 * sift(): does the main work for my_heapsort()
 */
void sift( struct cookie v[], long i, long m, int mode )
    {
    long j;
    struct cookie temp;

    while( (j = 2 * (i + 1) - 1) <= m )
        {
        if( j < m && cookie_cmp( &v[ j ], &v[ j + 1 ], mode ) < 0 )
            j++;
        if( cookie_cmp( &v[ i ], &v[ j ], mode ) < 0 )
            {
            temp = v[ i ];
            v[ i ] = v[ j ];
            v[ j ] = temp;
            i = j;
            }
        else
            i = m;              /* done */
        }
    }


/*
 * Note the side effect: Will print three "."s to stdout: one on entry,
 * one when the sort is halfway through, and another one when it's all done.
 */
void my_heapsort( struct cookie v[], long n, int mode )
    {
    long i;
    struct cookie temp;

    putchar( '.' ); fflush( stdout );
    if( n < 2 )                 /* no sorting necessary */
        return;
    for( i = n/2 - 1; i >= 0; i-- )
        sift( v, i, n - 1, mode );
    putchar( '.' ); fflush( stdout );
    for( i = n - 1; i >= 1; i-- )
        {
        temp = v[ 0 ];
        v[ 0 ] = v[ i ];
        v[ i ] = temp;
        sift( v, 0, i - 1, mode );
        }
    putchar( '.' ); fflush( stdout );
    }



/*
 * Adjust sorthooks for the final sort, according to the desired mode.
 */
void set_hooks( int mode, UBYTE *hooktarget )
    {
    long l;
    int hot;
    UBYTE *s;

    printf( "Adjusting sort hooks..." );
    fflush( stdout );
    for( l = 0; l < listed; l++ )
        {
        s = clist[ l ].text;
        switch( mode )
            {
            case SORT_LASTLINE: /* start of last line */
                hot = 1;
                while( *s )
                    {
                    if( *s == '\n' )
                        hot = 1;
                    else if( hot )
                        {
                        clist[ l ].sorthook = s;
                        hot = 0;
                        }
                    s++;
                    }
                break;
            case SORT_LASTWORD: /* start of last word */
                hot = 1;
                while( *s )
                    {
                    if( isspace( *s ) )
                        hot = 1;
                    else if( hot )
                        {
                        clist[ l ].sorthook = s;
                        hot = 0;
                        }
                    s++;
                    }
                break;
            case SORT_HOOKTARGET:
                while( s )      /* at last occurence of <hooktarget> */
                    {
                    clist[ l ].sorthook = s++;
                    s = strstr( s, hooktarget );
                    }
                break;
            default:
            }
      }
    printf( " done.\n" );
    }



/*
 * Delete cookies and (optionally) log them to a file. For values of delmode
 * and sortmode, see "compress.h".
 * Note that the routine expects the sorthooks to point at the body texts
 * on entry, but may modify and not restore them itself. There are many
 * reasons why this does *not* hurt with the current implementations of both
 * cookietool and cdbdiff, but it might be a pitfall in the future.
 */
void one_cookie( int delmode, int sortmode, UBYTE *hooktarget, FILE *fp, int fmt )
    {
    long i, j, dbl = 0, abr = 0;
    int cmp;

    if( delmode != DUPDEL_NONE )
        {
        printf( "Removing double entries" );
        if( delmode == DUPDEL_ABBREVS )
            printf( " + 'abbreviations'" );
        /* sort descending by string */
        my_heapsort( clist, listed, SORT_REVERSE );
        for( i = listed - 1; i > 0; i = j )
            {
            for( j = i - 1; j >= 0
              && ( (cmp = str_cmp( clist[ j ].text, clist[ i ].text )) == 0
              || (delmode == DUPDEL_ABBREVS && cmp == STR_LONGER) ); j-- )
                {
                if( fp )
                    if( !write_cookie( clist[ i ].text, fp, fmt ) )
                        {
                        printf( "\nFile error, aborted !!!\n" );
                        exit( 20 );
                        }
                free( clist[ i ].text );
                clist[ i-- ] = clist[ --listed ];
                if( cmp == 0 )
                    dbl++;
                else
                    abr++;
                }
            }
        printf( " done. (%ld ", dbl );
        if( delmode == DUPDEL_ABBREVS )
            printf( "+ %ld ", abr );
        printf( "found)\n" );
        }
    if( sortmode == SORT_RESTORE )
        {
        printf( "Restoring order" );
        my_heapsort( clist, listed, SORT_RESTORE );
        }
    else
        {
        if( sortmode > SORT_BODY )
            set_hooks( sortmode, hooktarget );
        printf( "Sorting" );
        if( sortmode == SORT_SIZE )
            my_heapsort( clist, listed, SORT_SIZE );
        else
            my_heapsort( clist, listed, SORT_BODY );
        }
    printf( " done.\n" );
    }

