/*
 * NAME
 *   AutoSelectAndKill.c
 * USAGE
 *   AutoSelectAndKill NewsGroup
 * DESCRIPTION
 *   Reads subjects from standard input.
 *
 *   When the environment variable SKIMAUTOSUBJECT is defined as either
 *   "CaseSensitive" or "CaseInsensitive", the subject are filtered as follows:
 *
 *       If a subject matches the AutoSelect pattern for this newsgroup, it is 
 *       selected for retrieval and appended to the Subjects file. Otherwise, 
 *       if a subject matches the Kill pattern, the subject is appended to the 
 *       Killed file for this newsgroup. Otherwise the subject is appended to 
 *       the Subjects file.
 *
 *   The current article number for this newsgroups is updated.
 * COPYRIGHT
 *   Skim - Off-line news reading package optimized for slow lines.
 *   Copyright (C) 1995  Rene W.J. Pijlman
 *
 *   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.
 * VERSION
 *   Skim version 0.6.
 */

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

#include "Skim.h"
#include "MemAlloc.h"
#include "VarBuf.h"

FILE_ID("/home/rene/sys/CVS_MasterSourceRepository/skim/AutoSelectAndKill.c,v 1.17 1995/08/15 22:50:13 rpijlman Exp");

#define USAGE "Usage: AutoSelectAndKill NewsGroup"

#define ARGC_NEWSGROUP        1
#define NUMBER_OF_ARGUMENTS   2

#define REGEX_MAPSIZE  256


static void DestroyPattern( struct re_pattern_buffer * Pattern )
{
    if ( Pattern != NULL )
    {
	if ( Pattern->fastmap != NULL )
	{
	    MemFree( Pattern->fastmap );
	}

	MemFree( Pattern );
    }
}


static struct re_pattern_buffer * CompilePatternInFile(
    char * PatternDirectory,
    char * FileName,
    char * TranslationTable )
{
    int ExitStatus;
    FILE * PatternFile;
    const char * ErrorMessage;
    struct re_pattern_buffer * Pattern;
    VarBuf M4Command = VBCreate();
    VarBuf RegularExpression = VBCreate();
    VarBuf SubExpression = VBCreate();

    Boolean FirstSubExpression = True;

    Pattern = MemAlloc( sizeof(struct re_pattern_buffer) );
    Pattern->fastmap = NULL;

    VBPrintf( M4Command, "m4 -I %s/%s %s/%s/%s",
              SkimDir(), PatternDirectory,
              SkimDir(), PatternDirectory, FileName );

    PatternFile = popen( VBAsString(M4Command), "r" );
    if ( PatternFile == NULL )
    {
        fprintf( stderr, "\nSkim: error executing %s.\n",
                 (char *)VBAsString(M4Command) );
        exit( EXIT_FAILURE );
    }

    while ( VBReadLine( SubExpression, PatternFile, WITHOUT_NEWLINE ) )
    {
        /* Ignore empty lines and comment. */
        if ( VBSize( SubExpression ) > 0 &&
             *VBAsString( SubExpression ) != '#' )
        {
            if ( !FirstSubExpression )
            {
                VBAppendCharacter( RegularExpression, '|' );
            }

            VBAppendVB( RegularExpression, SubExpression );

            FirstSubExpression = False;
        }

        VBReset( SubExpression );
    }

    if ( ( ExitStatus = pclose( PatternFile ) ) != EXIT_SUCCESS )
    {
        fprintf( stderr, "\nSkim: Error closing %s.\n Exit status %d.\n",
                 (char *)VBAddress(M4Command), ExitStatus );
        exit( EXIT_FAILURE );
    }

    if ( VBSize(RegularExpression) > 0 )
    {
	re_syntax_options = RE_SYNTAX_EGREP;

	Pattern->translate = TranslationTable;
	Pattern->fastmap = MemAlloc( REGEX_MAPSIZE  );
	Pattern->buffer = NULL;
	Pattern->allocated = 0;

	ErrorMessage = re_compile_pattern (VBAddress(RegularExpression),
					   VBSize(RegularExpression),
					   Pattern );
	if ( ErrorMessage != NULL )
	{
	    fprintf( stderr, 
	             "Error compiling the %s pattern of group %s:\n%s\n",
                     PatternDirectory, FileName, ErrorMessage );
	    exit( EXIT_FAILURE );
	}
    }
    else
    {
        DestroyPattern( Pattern );
        Pattern = NULL;
    }

    VBDestroy( M4Command );
    VBDestroy( SubExpression );
    VBDestroy( RegularExpression );

    return Pattern;
}


static Boolean MatchesPattern(
    char * String,
    int Length,
    struct re_pattern_buffer * PatternToMatchWith )
{
     int Status;

     Status = re_search( PatternToMatchWith, String, Length, 0, Length, 0 );
     if ( Status == -2 )
     {
         fprintf( stderr, "Internal error in re_match()\n" );
         exit( EXIT_FAILURE );
     }

     return ( Status != -1 );
}


int main( int argc, char ** argv )
{
    VarBuf NumberSpaceAndSubject = VBCreate();
    char * SkimAutoSubject = NULL;
    struct re_pattern_buffer * AutoSelect = NULL;
    struct re_pattern_buffer * Kill = NULL;
    unsigned int CurrentArticleNumber;
    unsigned int HighestArticleNumber = 0;
    size_t NumberOfCharactersSkipped;
    VarBuf FullPathSubjectsFile = VBCreate();
    VarBuf FullPathKilledFile = VBCreate();
    VarBuf FullPathCurrentArticleNumberFile = VBCreate();
    char * CaseInsensitive = NULL;
    FILE * CurrentArticleNumberFile = NULL;
    FILE * SubjectsFile = NULL;
    FILE * KilledFile = NULL;
    Boolean Initialized = False;
    char * Subject = NULL;
    int Length;
    int i;

    if ( argc != NUMBER_OF_ARGUMENTS )
    {
        fprintf( stderr, "%s\n", USAGE );
        exit( EXIT_FAILURE );
    }

    while( VBReadLine( NumberSpaceAndSubject, stdin, WITHOUT_NEWLINE ) )
    {
        /* Skip number and space(s). */
        for ( Subject = VBAsString(NumberSpaceAndSubject), 
                  NumberOfCharactersSkipped = 0;
              isdigit(*Subject) || isblank(*Subject);
              Subject++ )
        {
            NumberOfCharactersSkipped++;
        }

        assert( NumberOfCharactersSkipped <= VBSize( NumberSpaceAndSubject ) );

        Length = VBSize( NumberSpaceAndSubject ) - NumberOfCharactersSkipped;

	if ( !Initialized )
	{
	    /*
	     * Automatic selection and killing of subjects must be enabled by the 
	     * user. 
	     */
	    if ( ( SkimAutoSubject = getenv("SKIMAUTOSUBJECT") ) != NULL )
	    {
		if ( !strcmp( SkimAutoSubject, "CaseInsensitive" ) )
		{
		    CaseInsensitive = MemAlloc( REGEX_MAPSIZE );

		    /*
		     * Initialize a translation table for the GNU regex functions. 
		     * This makes pattern matching case insensitive.
		     */
		    for (i = 0; i < REGEX_MAPSIZE; i++)
		      CaseInsensitive[i] = i;
		    for (i = 'a'; i <= 'z'; i++)
		      CaseInsensitive[i] = i - ('a' - 'A');
		}
		else if ( !strcmp( SkimAutoSubject, "CaseSensitive" ) )
		{
		    CaseInsensitive = NULL;
		}
		else
		{
		    fprintf( stderr, "Invalid value in $SKIMAUTOSUBJECT: %s\n",
			     SkimAutoSubject );
		    exit( EXIT_FAILURE );
		}

		AutoSelect = CompilePatternInFile( "Patterns/Subject/AutoSelect",
						   argv[ ARGC_NEWSGROUP ],
						   CaseInsensitive );
		Kill = CompilePatternInFile( "Patterns/Subject/Kill",
					     argv[ ARGC_NEWSGROUP ],
					     CaseInsensitive );
	    }

	    Initialized = True;
	}

        if ( AutoSelect != NULL &&
             MatchesPattern( Subject, Length, AutoSelect ) )
        {
            if ( SubjectsFile == NULL )
            {
		VBPrintf( FullPathSubjectsFile, "%s/%s/%s",
			 SkimDir(), "Subjects", argv[ ARGC_NEWSGROUP ] );

		SubjectsFile = fopen( VBAsString(FullPathSubjectsFile), "a" );
		if ( SubjectsFile == NULL )
		{
		    fprintf( stderr, "Cannot open %s\n", VBAsString(FullPathSubjectsFile));
		    exit( EXIT_FAILURE );
		}
            }

            /* Write subject prepended with space. */
            if ( fprintf( SubjectsFile, " %s\n", 
                          VBAsString(NumberSpaceAndSubject) ) == EOF )
	    {
		fprintf( stderr, "Error writing to %s\n",
			 VBAsString(FullPathSubjectsFile) );
		exit( EXIT_FAILURE );
	    }
        }
        else if ( Kill != NULL &&
                  MatchesPattern( Subject, Length, Kill ) )
        {
            if ( KilledFile == NULL )
            {
		VBPrintf( FullPathKilledFile, "%s/%s/%s",
			  SkimDir(), "Killed", argv[ ARGC_NEWSGROUP ] );
		KilledFile = fopen( VBAsString(FullPathKilledFile), "a" );
		if ( KilledFile == NULL )
		{
		    fprintf( stderr, "Cannot open %s\n", 
		             VBAsString(FullPathKilledFile) );
		    exit( EXIT_FAILURE );
		}
            }

            if ( fprintf( KilledFile, "%s\n",
                          VBAsString(NumberSpaceAndSubject) ) == EOF )
	    {
		fprintf( stderr, "Error writing to %s\n",
			 VBAsString(FullPathKilledFile) );
		exit( EXIT_FAILURE );
	    }
        }
        else
        {
            if ( SubjectsFile == NULL )
            {
		VBPrintf( FullPathSubjectsFile, "%s/%s/%s",
			 SkimDir(), "Subjects", argv[ ARGC_NEWSGROUP ] );

		SubjectsFile = fopen( VBAsString(FullPathSubjectsFile), "a" );
		if ( SubjectsFile == NULL )
		{
		    fprintf( stderr, "Cannot open %s\n", VBAsString(FullPathSubjectsFile));
		    exit( EXIT_FAILURE );
		}
            }

            if ( fprintf( SubjectsFile, "%s\n",
                          VBAsString(NumberSpaceAndSubject) ) == EOF )
	    {
		fprintf( stderr, "Error writing to %s\n",
			 VBAsString(FullPathSubjectsFile) );
		exit( EXIT_FAILURE );
	    }
        }

        if ( sscanf( VBAsString(NumberSpaceAndSubject), "%u %*s\n",
                     &CurrentArticleNumber ) != EOF )
        {
            if ( CurrentArticleNumber > HighestArticleNumber )
            {
                HighestArticleNumber = CurrentArticleNumber;
            }
        }
        else
        {
            fprintf( stderr, "Format error in subject line.\n" );
	    exit( EXIT_FAILURE );
        }

        VBReset( NumberSpaceAndSubject );
    }

    if ( SubjectsFile != NULL &&
         ( ferror(SubjectsFile) || fclose(SubjectsFile) == EOF ) )
    {
        fprintf( stderr, "Error writing to %s\n",
                 VBAsString(FullPathSubjectsFile) );
        exit( EXIT_FAILURE );
    }

    if ( KilledFile != NULL &&
             ( ferror(KilledFile) || fclose(KilledFile) == EOF ) )
    {
        fprintf( stderr, "Error writing to %s\n",
                 VBAsString(FullPathKilledFile) );
        exit( EXIT_FAILURE );
    }

    if ( HighestArticleNumber > 0 )
    {
        /* Update the current article number. */
	VBPrintf( FullPathCurrentArticleNumberFile, "%s/%s/%s",
		  SkimDir(), "CurrentArticleNumber", argv[ ARGC_NEWSGROUP ] );

	CurrentArticleNumberFile =
	    fopen( VBAsString(FullPathCurrentArticleNumberFile), "w" );
	if ( CurrentArticleNumberFile != NULL )
	{
	    if ( fprintf( CurrentArticleNumberFile, "%u\n",
			  HighestArticleNumber + 1 ) == EOF ||
		 fclose( CurrentArticleNumberFile ) == EOF )
	    {
		fprintf( stderr, "Error writing current article number to %s\n",
			 VBAsString(FullPathCurrentArticleNumberFile) );
		exit( EXIT_FAILURE );
	    }
	}
	else
	{
	    fprintf( stderr, "Error opening current article number file: %s\n",
		     VBAsString(FullPathCurrentArticleNumberFile) );
	    exit( EXIT_FAILURE );
	}
    }

    MemFree( CaseInsensitive );
    DestroyPattern( AutoSelect );
    DestroyPattern( Kill );
    VBDestroy (NumberSpaceAndSubject);
    VBDestroy (FullPathSubjectsFile);
    VBDestroy (FullPathKilledFile);
    VBDestroy (FullPathCurrentArticleNumberFile);

    return EXIT_SUCCESS;
}
