/*****************************************************************************
 * HPT --- FTN NetMail/EchoMail Tosser
 *****************************************************************************
 * Copyright (C) 1997-2000
 *
 * Matthias Tichy
 *
 * Fido:     2:2433/1245 2:2433/1247 2:2432/605.14
 * Internet: mtt@tichy.de
 *
 * Grimmestr. 12         Buchholzer Weg 4
 * 33098 Paderborn       40472 Duesseldorf
 * Germany               Germany
 *
 * Hash Dupe and other typeDupeBase (C) 2000 
 *
 * Alexander Vernigora
 *
 * Fido:     2:4625/69              
 * Internet: alexv@vsmu.vinnica.ua
 *
 * Yunosty 79, app.13 
 * 287100 Vinnitsa   
 * Ukraine
 *
 * This file is part of HPT.
 *
 * HPT 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, or (at your option) any
 * later version.
 *
 * HPT 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 HPT; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************
 * $Id: dupe.c,v 1.76 2003/02/04 08:16:29 d_sergienko Rel $
 */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>

#include <pkt.h>
#include <global.h>

#include <smapi/compiler.h>
#include <smapi/unused.h>
#include <dupe.h>

#include <smapi/msgapi.h>
#include <smapi/stamp.h>
#include <smapi/progprot.h>

#include <fidoconf/common.h>
#include <fidoconf/xstr.h>
#include <fidoconf/crc.h>


FILE *fDupe;

UINT32  DupeCountInHeader, maxTimeLifeDupesInArea;
s_dupeMemory *CommonDupes=NULL;

#define MSGIDPOS        7
#define HASHBUFSIZE     3*XMSG_TO_SIZE /* msgid len */ + AREANAMELEN
static char hashBuf [HASHBUFSIZE];

static time_t TimeStamp;

char *createDupeFileName(s_area *area) {
    char *name=NULL, *ptr, *retname=NULL;
    
    
    if (!area->DOSFile) {
        name = makeMsgbFileName(config, area->areaName);
    } else {
        if (area->fileName) xstrcat(&name, (ptr = strrchr(area->fileName,PATH_DELIM))
            ? ptr+1 : area->fileName);
        else xscatprintf(&name, "%X", strcrc32(area->areaName,0xFFFFFFFFUL) );
    }
    
    switch (config->typeDupeBase) {
    case hashDupes:
        xstrcat(&name,".dph");
        break;
    case hashDupesWmsgid:
        xstrcat(&name,".dpd");
        break;
    case textDupes:
        xstrcat(&name,".dpt");
        break;
    case commonDupeBase:
        break;
    }
    
    if (config->areasFileNameCase == eUpper)
        name = strUpper(name);
    else 
        name = strLower(name);
    
    xstrscat(&retname, config->dupeHistoryDir, name, NULL);
    nfree(name);
    
    return retname;
}


int compareEntries(char *p_e1, char *p_e2) {
    const s_textDupeEntry  *atxt,   *btxt;
    const s_hashDupeEntry  *ahash,  *bhash;
    const s_hashMDupeEntry *ahashM, *bhashM;
    int rc = 0;
    const void *e1 = (const void *)p_e1, *e2 = (const void *)p_e2;
    
    switch (config->typeDupeBase) {
    case hashDupes:
        ahash = e1; bhash = e2;
        if (ahash->CrcOfDupe > bhash->CrcOfDupe)
            rc =  1;  
        else if (ahash->CrcOfDupe < bhash->CrcOfDupe)
            rc = -1;
        else
            rc =  0;
        break;
        
    case hashDupesWmsgid:
        ahashM = e1; bhashM = e2;
        if (ahashM->CrcOfDupe == bhashM->CrcOfDupe)
            rc = strcmp(ahashM->msgid, bhashM->msgid);
        else if (ahashM->CrcOfDupe > bhashM->CrcOfDupe)
            rc = 1;  
        else
            rc = -1;
        break;
        
    case textDupes:
        atxt = e1; btxt = e2;
        rc = strcmp(atxt->msgid, btxt->msgid);
        break;
        
    case commonDupeBase:
        ahash = e1; bhash = e2;
        if (ahash->CrcOfDupe > bhash->CrcOfDupe)
            rc =  1;  
        else if (ahash->CrcOfDupe < bhash->CrcOfDupe)
            rc = -1;
        else
            rc =  0;
        break;
    }
    
    return rc;
}

int writeEntry(char *p_entry) {
   const s_textDupeEntry  *entxt;
   const s_hashDupeEntry  *enhash;
   const s_hashMDupeEntry *enhashM;
   UINT32 diff=0;
   time_t currtime;
   const void *entry = (const void *)p_entry;

   currtime = TimeStamp;

   switch (config->typeDupeBase) {
      case hashDupes:
           enhash = entry;
           if ( (diff = currtime - enhash->TimeStampOfDupe) < maxTimeLifeDupesInArea) {
              fwrite(enhash, sizeof(s_hashDupeEntry), 1, fDupe);
              DupeCountInHeader++;   
           }
   	   break;

      case hashDupesWmsgid: 
           enhashM = entry;
           if ( (diff = currtime - enhashM->TimeStampOfDupe) < maxTimeLifeDupesInArea) {
              fwrite(enhashM, sizeof(time_t)+sizeof(UINT32), 1, fDupe);
              if ((enhashM->msgid != NULL)&&(0 < strlen(enhashM->msgid))) {
                 fputc(strlen(enhashM->msgid), fDupe);
                 fputs(enhashM->msgid, fDupe);
              }
              else fputc(0, fDupe);
              DupeCountInHeader++;   
           }
  	   break;

      case textDupes:
           entxt = entry;
           if ( (diff = currtime - entxt->TimeStampOfDupe) < maxTimeLifeDupesInArea) {
              fwrite(entxt, sizeof(time_t), 1, fDupe);
              /* write zero length of this field. left for backward compatibility
              if ((entxt->msgid != NULL)&&(0 < strlen(entxt->from))) {
                 fputc(strlen(entxt->from), fDupe); 
                 fputs(entxt->from, fDupe);
              }
              else
              */ 
              fputc(0, fDupe);
              /* write zero length of this field. left for backward compatibility
              if ((entxt->msgid != NULL)&&(0 < strlen(entxt->to))) {
                 fputc(strlen(entxt->to), fDupe); 
                 fputs(entxt->to, fDupe);
              }
              else
              */
              fputc(0, fDupe);
              /* write zero length of this field. left for backward compatibility
              if ((entxt->msgid != NULL)&&(0 < strlen(entxt->subject))) {
                 fputc(strlen(entxt->subject), fDupe); 
                 fputs(entxt->subject, fDupe);
              }
              else
              */
              fputc(0, fDupe);
              if ((entxt->msgid != NULL)&&(0 < strlen(entxt->msgid))) {
                 fputc(strlen(entxt->msgid), fDupe);
                 fputs(entxt->msgid, fDupe);
              }
              else fputc(0, fDupe);
              DupeCountInHeader++;   
           }
 	   break;

      case commonDupeBase:
           enhash = entry;
           if ( (diff = currtime - enhash->TimeStampOfDupe) < maxTimeLifeDupesInArea) {
              fwrite(enhash, sizeof(s_hashDupeEntry), 1, fDupe);
              DupeCountInHeader++;   
           }
           break;
   }

   return 1;
}

int deleteEntry(char *entry) {
    if(entry)
    {
        s_textDupeEntry  *entxt;
        s_hashDupeEntry  *enhash;
        s_hashMDupeEntry *enhashM;
        
        switch (config->typeDupeBase) {
        case hashDupes:
            enhash = (s_hashDupeEntry *)entry;
            nfree(enhash);
            break;
            
        case hashDupesWmsgid:
            enhashM = (s_hashMDupeEntry *)entry;
            nfree(enhashM->msgid);
            nfree(enhashM);
            break;
            
        case textDupes:
            entxt = (s_textDupeEntry *)entry;
            nfree(entxt->msgid);
            nfree(entxt);
            break;
            
        case commonDupeBase:
            enhash = (s_hashDupeEntry *)entry;
            nfree(enhash);
            break;
        }
    }
    return 1;
}

void doReading(FILE *f, s_dupeMemory *mem)
{
    s_textDupeEntry  *entxt;
    s_hashDupeEntry  *enhash;
    s_hashMDupeEntry *enhashM;
    UCHAR   length;
    UINT32 i;
    time_t timedupe;
    
    /*  read Number Of Dupes from dupefile */
    fread(&DupeCountInHeader, sizeof(UINT32), 1, f);
    
    /*  process all dupes */
    for (i = 0; i < DupeCountInHeader; i++) {
        if (feof(f)) break;
        
        switch (config->typeDupeBase) {
        case hashDupes:
            enhash = (s_hashDupeEntry*) safe_malloc(sizeof(s_hashDupeEntry));
            fread(enhash, sizeof(s_hashDupeEntry), 1, f);
            tree_add(&(mem->avlTree), compareEntries, (char *) enhash, deleteEntry);
            break;
            
        case hashDupesWmsgid:
            enhashM = (s_hashMDupeEntry*) safe_malloc(sizeof(s_hashMDupeEntry));
            fread(enhashM, sizeof(time_t)+sizeof(UINT32), 1, f);
            if ((length = (UCHAR)getc(f)) > 0) {  /* no EOF check :-( */
                enhashM->msgid = safe_malloc(length+1);
                fread((UCHAR*)enhashM->msgid, length, 1, f);     
                enhashM->msgid[length]='\0';
            } else enhashM->msgid = NULL;
            tree_add(&(mem->avlTree), compareEntries, (char *) enhashM, deleteEntry);
            break;
            
        case textDupes:
                
                entxt = (s_textDupeEntry*) safe_malloc(sizeof(s_textDupeEntry));
                
                fread(&timedupe, sizeof(time_t), 1, f);     
                entxt->TimeStampOfDupe=timedupe;
                
                if ((length = (UCHAR)getc(f)) > 0) { /* no EOF check :-( */
                    fread(hashBuf, length, 1, f);     
                } 
                if ((length = (UCHAR) getc(f)) > 0) { /* no EOF check :-( */
                    fread(hashBuf, length, 1, f);     
                } 
                if ((length = (UCHAR)getc(f)) > 0) { /* no EOF check :-( */
                    fread(hashBuf, length, 1, f);     
                } 
                if ((length = (UCHAR)getc(f)) > 0) { /* no EOF check :-( */
                    entxt->msgid = safe_malloc(length+1);
                    fread((UCHAR*)entxt->msgid, length, 1, f);     
                    entxt->msgid[length]='\0';
                } else entxt->msgid = NULL;

                if(entxt->msgid)
                    tree_add(&(mem->avlTree), compareEntries, (char *) entxt, deleteEntry);
                break;

        case commonDupeBase:
            enhash = (s_hashDupeEntry*) safe_malloc(sizeof(s_hashDupeEntry));
            fread(enhash, sizeof(s_hashDupeEntry), 1, f);
            tree_add(&(mem->avlTree), compareEntries, (char *) enhash, deleteEntry);
            break;
        }
        
        
    }
}

s_dupeMemory *readDupeFile(s_area *area) {
   FILE *f;
   char *fileName=NULL;
   s_dupeMemory *dupeMemory;

   TimeStamp = time (NULL);

   dupeMemory = safe_malloc(sizeof(s_dupeMemory));
   tree_init(&(dupeMemory->avlTree),1);
   
   if (config->typeDupeBase!=commonDupeBase) {
       fileName = createDupeFileName(area);
       w_log(LL_DUPE, "Reading dupes of %s", area->areaName);
   }
   else {
       xstrscat(&fileName, config->dupeHistoryDir, "hpt_base.dpa", NULL);
       w_log(LL_DUPE, "Reading dupes from %s", fileName);
   }

   f = fopen(fileName, "rb");
   if (f != NULL) { w_log(LL_FILE,"dupe.c:readDupeFile(): opened %s (\"rb\" mode)",fileName);
       /*  readFile */
       doReading(f, dupeMemory);
       fclose(f);
   } else {
       if (fexist(fileName)) w_log(LL_ERR, "Error reading dupe base: %s", fileName);
       else if( errno != ENOENT)
         w_log(LL_ERR, "Dupe base '%s' read error: %s", fileName, strerror(errno) );
   }

   nfree(fileName);

   return dupeMemory;
}


int createDupeFile(s_area *area, char *name, s_dupeMemory DupeEntries) {
   FILE *f;

/*    w_log(LL_SRCLINE,"dupe.c:%u:createDupeFile() name='%s'", __LINE__, name); */

   f = fopen(name, "wb");
   if (f!= NULL) {
       w_log(LL_FILE,"dupe.c:createDupeFile(): opened %s (\"wb\" mode)",name);
       if (config->typeDupeBase!=commonDupeBase)
          maxTimeLifeDupesInArea=area->dupeHistory*86400;    
       else
          maxTimeLifeDupesInArea=config->areasMaxDupeAge*86400;    

       DupeCountInHeader = 0;
       fwrite(&DupeCountInHeader, sizeof(UINT32), 1, f);    
       fDupe = f;
       tree_trav(&(DupeEntries.avlTree), writeEntry);
       fDupe = NULL;

       /*  writeDupeFileHeader */
       if (DupeCountInHeader>0) {
          fseek(f, 0, SEEK_SET);
          fwrite(&DupeCountInHeader, sizeof(UINT32), 1, f);    
          fclose(f);
       /*  for 1 save commonDupeBase */
       if (config->typeDupeBase==commonDupeBase)
          freeDupeMemory(area);
       }
       else {
          fclose(f);
          remove (name);
       }
     
       return 0;
   } else return 1;
}


int writeToDupeFile(s_area *area) {
   char *fileName=NULL;
   s_dupeMemory *dupes;
   int  rc = 0;          

   if (area->dupeCheck == dcOff) return rc;
   
   if (config->typeDupeBase!=commonDupeBase) {
      dupes = area->dupes;
      fileName = createDupeFileName(area);
   }
   else {
      dupes = CommonDupes;
      xstrscat(&fileName, config->dupeHistoryDir, "hpt_base.dpa", NULL);
   }

   if (dupes != NULL) {
      if (tree_count(&(dupes->avlTree)) > 0) {
         rc = createDupeFile(area, fileName, *dupes);
      }
   }

   nfree(fileName);

   return rc;
}


void freeDupeMemory(s_area *area) {
   s_dupeMemory *dupes;

   if (area->dupeCheck == dcOff) return;
  
   if (config->typeDupeBase != commonDupeBase) 
      dupes = area -> dupes;
   else
      dupes = CommonDupes;

   if (dupes != NULL) {
      tree_mung(&(dupes -> avlTree), deleteEntry);
      if (config->typeDupeBase != commonDupeBase) {
         nfree(area -> dupes);
      }
      else {
         nfree(CommonDupes);
      }
   };
}


int dupeDetection(s_area *area, const s_message msg) {
    s_dupeMemory     *Dupes = area->dupes;
    s_textDupeEntry  *entxt;
    s_hashDupeEntry  *enhash;
    s_hashMDupeEntry *enhashM;
    char             *str;
    int nRet = 0;

    w_log(LL_FUNC,"dupe.c::dupeDetection() begin");
    
    if (area->dupeCheck == dcOff) return 1; /*  no dupeCheck return 1 "no dupe" */
    
    str = (char*)MsgGetCtrlToken((byte*)msg.text, (byte*)"MSGID:");

    if (str == NULL) 
    {   /* Kluge MSGID is not found so make it from crc of message body */
        if (msg.text) 
            xscatprintf (&str, "MSGID: %08lx",strcrc32(msg.text, 0xFFFFFFFFL));
        else 
            return 1; /*  without msg.text - message is empty, no dupeCheck */
    }
    /*   testif dupeDatabase is already read */
    if (config->typeDupeBase != commonDupeBase) {
        if (area->dupes == NULL) {
            Dupes = area->dupes = readDupeFile(area); /* read Dupes */
        }
    }
    else {
        if (CommonDupes == NULL) {
            CommonDupes = readDupeFile(area); /* read Dupes */
        }
    }
    
    memset(&hashBuf,0,HASHBUFSIZE);
    switch (config->typeDupeBase) {
    case hashDupes:
        enhash = safe_malloc(sizeof(s_hashDupeEntry));
        strnzcpy(hashBuf, area->areaName,   AREANAMELEN);
        /*
        strnzcpy(hashBuf, msg.fromUserName, XMSG_FROM_SIZE);
        strnzcat(hashBuf, msg.toUserName,   XMSG_TO_SIZE);
        strnzcat(hashBuf, msg.subjectLine,  XMSG_SUBJ_SIZE);
        */
        strnzcat(hashBuf, str+MSGIDPOS,     3*XMSG_TO_SIZE);
        enhash->CrcOfDupe       = strcrc32(hashBuf, 0xFFFFFFFFL);
        enhash->TimeStampOfDupe = TimeStamp;
        nRet = tree_add(&(Dupes->avlTree), compareEntries, (char *) enhash, deleteEntry);
        break;
        
    case hashDupesWmsgid:
        enhashM = safe_malloc(sizeof(s_hashMDupeEntry));
        strnzcpy(hashBuf, area->areaName,   AREANAMELEN);
        /*
        strnzcpy(hashBuf, msg.fromUserName, XMSG_FROM_SIZE);
        strnzcat(hashBuf, msg.toUserName,   XMSG_TO_SIZE);
        strnzcat(hashBuf, msg.subjectLine,  XMSG_SUBJ_SIZE);
        */
        strnzcat(hashBuf, str+MSGIDPOS,     3*XMSG_TO_SIZE);
        enhashM->msgid = safe_malloc(strlen(str)+1-MSGIDPOS); 
        strcpy(enhashM->msgid, str+MSGIDPOS);
        enhashM->CrcOfDupe       = strcrc32(hashBuf, 0xFFFFFFFFL);
        enhashM->TimeStampOfDupe = TimeStamp;
        nRet = tree_add(&(Dupes->avlTree), compareEntries, (char *) enhashM, deleteEntry);
        break;
        
    case textDupes: 
        entxt = safe_malloc(sizeof(s_textDupeEntry));
        entxt->TimeStampOfDupe = TimeStamp;
        
        entxt->msgid   = safe_malloc(strlen(str)+2-MSGIDPOS);
        strcpy(entxt->msgid, str+MSGIDPOS);
        nRet = tree_add(&(Dupes->avlTree), compareEntries, (char *) entxt, deleteEntry);
        break;
        
    case commonDupeBase:
        enhash = safe_malloc(sizeof(s_hashDupeEntry));
        strnzcpy(hashBuf, area->areaName,   AREANAMELEN);
        /*
        strnzcat(hashBuf, msg.fromUserName, XMSG_FROM_SIZE);
        strnzcat(hashBuf, msg.toUserName,   XMSG_TO_SIZE);
        strnzcat(hashBuf, msg.subjectLine,  XMSG_SUBJ_SIZE);
        */
        strnzcat(hashBuf, str+MSGIDPOS,     3*XMSG_TO_SIZE);
        enhash->CrcOfDupe       = strcrc32(hashBuf, 0xFFFFFFFFL);
        enhash->TimeStampOfDupe = TimeStamp;
        nRet = tree_add(&(CommonDupes->avlTree), compareEntries, (char *) enhash, deleteEntry);
        break;
    }
    nfree(str);
    w_log(LL_FUNC,"dupe.c::dupeDetection() end nRet = %d", nRet);
    return nRet;
}

