/*--------------------------------------------------------------------------
                        C O M M A N D L O G 
                        
  A much requested facility for writing only user and manager level commands
  in a transcript file. This is it.
  
  Mark Koennecke, June 1998

  Extended to support Heinz Heers autolog-file
  Mark Koennecke, April-May 1999  
  
  Added a tail facility
  Mark Koennecke, October 1999 
--------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <tcl.h>
#include "sics.h"
#include "ifile.h"
#include "sicsvar.h"
#include "scaldate.h"
#include "network.h"
#include "circular.h"


/* in conman.c */
   int TelnetWrite(mkChannel *pSock, char *pText);
   

/*-------------------- the command log file pointer ---------------------*/
   static FILE *fd = NULL;
   static FILE *fauto = NULL;
   static char pFile[256];
/*-------------------- the tail buffer ---------------------------------*/
   static pCircular pTail = NULL;
#define MAXTAIL 1000
/*----------------------------------------------------------------------*/
   void WriteToCommandLog(char *prompt,char *pText)
   {
      int iNL = 0, iPos;
      char *pPtr = NULL;

      /* figure out if we have to do a newline with pText as well */      
      pPtr = strrchr(pText,'\n');
      if(pPtr != NULL)
      {
        iPos = pPtr - pText;
        if(iPos >= (strlen(pText) - 2) )
	{
           iNL = 1;
        }
      }

      /* supress status messages */
      if(strstr(pText,"status =") != NULL)
      {
         return;
      }
      
      /* suppress TRANSACTIONFINISHED as well in order to make the WWW
         commandlog work
      */
      if(strstr(pText,"TRANSACTIONFINISHED") != NULL)
      {
         return;
      }

      /* create tail buffer as needed */
      if(!pTail)
      {
         pTail = createCircular(MAXTAIL,free);
      }

      /* user file */
      if(fd != NULL)
      {
         if(iNL)
	 {
           fprintf(fd,"%s %s",prompt, pText);
         }
         else
	 {
           fprintf(fd,"%s %s\n",prompt, pText);
         }
      }
      /* automatic file */
      if(fauto != NULL)
      {
         if(iNL)
	 {
            fprintf(fauto,"%s %s",prompt, pText);
         }
         else
	 {
            fprintf(fauto,"%s %s\n",prompt, pText);
         }
      }
      /* tail buffer */
      if(pTail != NULL)
      {
         if(iNL)
	 {
            pPtr = strrchr(pText,'\n');
            *pPtr = ' ';     
         }
         setCircular(pTail,strdup(pText));
         nextCircular(pTail);
      }
   }
/*------------------------------------------------------------------------*/
   static void PrintTail(int iNum, SConnection *pCon)
   {
       char *pPtr = NULL;
       int i;

       if(pTail == NULL)
       {
          SCWrite(pCon,"Nothing to print",eError);
          return;
       }

       /* step back */
       for(i = 0; i < iNum; i++)
       {
          previousCircular(pTail);
       }

       /* now step ahead and print. I have to use a trick here: I do not
          want the tail stuff to show up in log files. Thus I write it 
          directly to the connection socket.
       */
       for(i = 0; i < iNum; i++)
       {
          pPtr = (char *)getCircular(pTail);
          if(pCon->pSock)
	  {
             TelnetWrite(pCon->pSock, pPtr);
          }
          nextCircular(pTail);           
       }
   }
/*------------------------------------------------------------------------*/
  void CLFormatTime(char *pBuffer, int iBufLen)
  {
     time_t iDate;
     struct tm *psTime;

     /* make time string */
     iDate = time(NULL);
     psTime = localtime(&iDate);
     memset(pBuffer,0,iBufLen);         
     strftime(pBuffer,iBufLen,"%Y-%m-%d@%H-%M-%S",psTime);
  } 
/*----------------------------------------------------------------------
  Build an automatically generated log file name and open it.
*/
   static void AutoLog(void)
   {
      char pBueffel[1024];
      char pTime[80];
      pSicsVariable pInst = NULL;
      char *pPtr = NULL;
      SConnection *pIntern = NULL;

      if(fauto)
      {
        fclose(fauto);
        fauto = NULL;
      }

      /* find path */
      pPtr = IFindOption(pSICSOptions,"LogFileDir");
      if(!pPtr)
      {
         pPtr = strdup("~/log");
         printf("WARNING: Required SICS option LogFileDir not found");         
      }      

      /* get time */
      CLFormatTime(pTime,79);
      
      /* build file name */
      sprintf(pBueffel,"%s/auto%s.log",pPtr,pTime);
      
      /* open file */
      fauto = fopen(pBueffel,"w");
      if(!fauto)
      {
         ServerWriteGlobal("ERROR: failed to open autolog file",eError);
      }      

      /* write the instrument name to it for identification */
      pInst = FindVariable(pServ->pSics,"instrument");
      if(pInst)
      {
        sprintf(pBueffel,"Logfile started at instument %s at %s",
                pInst->text,pTime);
        WriteToCommandLog("SYS>> ", pBueffel); 
      }

      /* if a file to execute is configured, execute it */
      pPtr = NULL;
      pPtr = IFindOption(pSICSOptions,"logstartfile");
      if(pPtr != NULL)
      {
        pIntern = SCCreateDummyConnection(pServ->pSics);
        if(!pIntern)
	{
          return;
        }
        SCnoSock(pIntern);
        SCSetRights(pIntern,usUser);
        sprintf(pBueffel,"fileeval %s",pPtr);
        InterpExecute(pServ->pSics,pIntern,pBueffel);
        SCDeleteConnection(pIntern);
      }
   }
/*----------------------------------------------------------------------
   AutoTask puts a time stamp into the auto log file any hour and
   creates a new log file any 24 hours 
*/
  static time_t tLogfile = 0;
  static time_t tStamp   = 0;
  static int iEnd        = 1;
  static int iAutoActive = 0;
  static int iIntervall  = 60;

  static int AutoTask(void *pData)
  {
     time_t tNow;
     char pTime[80];
     struct tm *sTime;
     long julian;
     unsigned yr, mo, dd;

     tNow = time(NULL);
     if(tNow > tLogfile)
     {
         AutoLog();
         sTime = localtime(&tNow);
         /* find next day, do so by converting to julian Date, add one
            and calculate back. The (stolen) julian calculations will
            take care of all the leaps and month and year etc.
	 */
         julian = ymd_to_scalar(sTime->tm_year+1900, sTime->tm_mon+1,
                                sTime->tm_mday);
         julian++;
         scalar_to_ymd(julian, &yr, &mo, &dd);
         sTime->tm_sec = 0;
         sTime->tm_min = 1;
         sTime->tm_hour = 0;
         sTime->tm_mday = dd;
         sTime->tm_mon = mo - 1;
         sTime->tm_year = yr - 1900;
         tLogfile = mktime(sTime);
         if(tLogfile < 0)
           tLogfile = tNow + 60*60*24;
      }
     if(tNow > tStamp)
     {
       CLFormatTime(pTime,79);
       WriteToCommandLog("TIMESTAMP>> ",pTime);
       sTime = localtime(&tNow);
       sTime->tm_sec = 0;
       sTime->tm_min += iIntervall;
       if(sTime->tm_min >= 60)
       {
          sTime->tm_min = 0;
          sTime->tm_hour++;
       }
       if(sTime->tm_hour >= 24)
           sTime->tm_hour = 0;
       tStamp = mktime(sTime);
       if((tStamp < 0) || ( (tStamp-tNow) < 100) )
       {
           tStamp = tNow + iIntervall*60;
       }
       if(fauto)
          fflush(fauto);
     }

     return iEnd;
  }   
/*----------- a command to configure the log  --------------------------*/   
   int  CommandLog(SConnection *pCon, SicsInterp *pSics, void *pData,
                   int argc, char *argv[])
   {
       char *pPtr = NULL;
       char pBueffel[1024];
       int iVal, iRet;
   
       if(argc == 1)
       {
         if(fd)
         {
           sprintf(pBueffel,"Command log ACTIVE at %s",pFile);
           SCWrite(pCon,pBueffel,eValue);
           return 1;
         }
         else
         {
           SCWrite(pCon,"Command logging DISABLED",eValue);
           return 1;
         }
       }

       /* handle tail */   
       strtolower(argv[1]);
       if(strcmp(argv[1],"tail") == 0)
       {
	 /* check for optional number of lines argument */
         iVal  = 20;
         if(argc >= 3)
	 {
            iRet = Tcl_GetInt(pSics->pTcl,argv[2],&iVal);
            if(iRet != TCL_OK)
               iVal = 20;
         }
         PrintTail(iVal,pCon);
         return 1;
       }
   
       /* check rights */
       if(!SCMatchRights(pCon,usMugger))
       {
          SCWrite(pCon,"ERROR: only managers may configure the logfile",
                  eError);
          SCWrite(pCon,"ERROR: Request refused",eError);
          return 0;        
       }
       
       /* check no of args */
       if(argc < 2)
       {
          SCWrite(pCon,
               "ERROR: Insufficient number or arguments to commandlog",
                eError);
          return 0;      
       }
       
       if(strcmp(argv[1],"new") == 0) /* new command */
       {
         if(argc < 3)
         {
            SCWrite(pCon,
               "ERROR: Insufficient number or arguments to commandlog new",
                eError);
             return 0;      
         }
         if(fd)
         {
            fclose(fd);
            fd = NULL;
         }
         /* make the filename */
         pPtr = IFindOption(pSICSOptions,"LogFileDir");
         if(!pPtr)
         {
            SCWrite(pCon,"WARNING: no log file directory specified",eWarning);
            sprintf(pBueffel,"%s",argv[2]);
              
         }
         else 
         {
            sprintf(pBueffel,"%s/%s",pPtr,argv[2]);
         }
         fd = fopen(pBueffel,"w");
         if(!fd)
         {
            sprintf(pBueffel,"ERROR: cannot open %s/%s for writing",pPtr,
                    argv[2]); 
            SCWrite(pCon,pBueffel,eError);
            return 0;
         }
         strcpy(pFile,argv[2]);
         SCSendOK(pCon);
         return 1;
       }
       else if(strcmp(argv[1],"auto") == 0)
       {
          if(iAutoActive)
	  {
             SCWrite(pCon,"ERROR: autologging is already active",eError);
             return 0;
          }
          TaskRegister(pServ->pTasker,
                       AutoTask,
                       NULL,
                       NULL,
                       NULL,
                       1);
          SCSendOK(pCon);
          iAutoActive = 1;
          return 1;
       }
       else if(strcmp(argv[1],"intervall") == 0)
       {
         if(argc > 2)
	 {
            iRet =  Tcl_GetInt(pSics->pTcl,argv[2],&iVal);
            if(iRet != TCL_OK)
	    {
               SCWrite(pCon,"ERROR: failed to convert new intervall to number",
                       eError);
               return 0;
            }           
            iIntervall = iVal;
            SCSendOK(pCon);
            return 1;
         }
         else
	 {
            sprintf(pBueffel,"autolog.intervall = %d", iIntervall);
            SCWrite(pCon,pBueffel,eValue);
            return 1;
         }
       }
       else if(strcmp(argv[1],"close") == 0) /* close command */
       {
         fclose(fd);
         fd = NULL;
         SCSendOK(pCon);
         return 1;
       }
       sprintf(pBueffel,"ERROR: subcommand %s to commandlog unknown",
              argv[1]);
       SCWrite(pCon,pBueffel,eError);
       return 0;
   }                
/*-------------------------------------------------------------------------*/
  void CommandLogClose(void *pData)
  {
     if(fd)
     {
       fclose(fd);
     }
     if(fauto)
        fclose(fauto);
     if(pData)
       KillDummy(pData);
     if(pTail)
       deleteCircular(pTail);
  }            



