// ****************************************************************************
//  Project:        libguytools
// ****************************************************************************
//  Programmer:     Guy Voncken
//                  Police Grand-Ducale
//                  Service de Police Judiciaire
//                  Section Nouvelles Technologies
// ****************************************************************************
//  Module:         Error handling
// ****************************************************************************

// Copyright 2008, 2009, 2010, 2011, 2012 Guy Voncken
//
// This file is part of libguytools.
//
// libguytools 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.
//
// libguytools 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 libguytools. If not, see <http://www.gnu.org/licenses/>.

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>

#include "toolconstants.h"
#include "toolglobalid.h"
#include "toolerror.h"

/* ------------------------------ */
/*  Type & structure definitions  */
/* ------------------------------ */

typedef struct
{
   int          ErrorCode;
   const char *pMsg;
} t_ToolErrorEntry, *t_pToolErrorEntry;

typedef struct
{
   t_pToolErrorLogFn pLogFn;
   t_pToolErrorEntry pErrorCodeArr;
   int                ErrorCodeArrEntries;
   int                ErrorCodeArrLen;
} t_ToolErrorLocal;

/* ------------------------------ */
/*           Variables            */
/* ------------------------------ */

static t_ToolErrorLocal ToolErrorLocal;
static int              Initialised = FALSE;

APIRET ToolErrorLog (const char *pFileName, const char *pFunctionName, int LineNr, const char * pFormat, ...)
{
   va_list VaList;
   va_start(VaList, pFormat);

   if (ToolErrorLocal.pLogFn != NULL)
   {
      ToolErrorLocal.pLogFn (pFileName, pFunctionName, LineNr, pFormat, VaList);
   }
   else
   {
      printf ("\nError in %s, %s, %d:", pFileName, pFunctionName, LineNr);
      vprintf (pFormat, VaList);
   }
   va_end(VaList);

   return NO_ERROR;
}

/* ------------------------------ */
/*            Functions           */
/* ------------------------------ */

static APIRET ToolErrorFindEntry (int ErrorCode, t_pToolErrorEntry *ppEntry)
{
   t_pToolErrorEntry pEntry = NULL;
   int                i;

   for (i=0; i<ToolErrorLocal.ErrorCodeArrEntries; i++)
   {
      pEntry = ToolErrorLocal.pErrorCodeArr + i;
      if ((pEntry)->ErrorCode == ErrorCode)
         break;
   }

   if (i >= ToolErrorLocal.ErrorCodeArrEntries)
      return TOOL_ERROR_ENTRY_NOT_FOUND;

   if (ppEntry)
      *ppEntry = pEntry;

   return NO_ERROR;
}

APIRET ToolErrorRegisterError (int ErrorCode, const char *pMsg)
{
   t_pToolErrorEntry pEntry;

   if (Initialised == FALSE)
      return TOOL_ERROR_NOT_INITIALISED;

   APIRET rc = ToolErrorFindEntry (ErrorCode, NULL);
   if (rc == TOOL_ERROR_ENTRY_NOT_FOUND)
   {
      pEntry = &(ToolErrorLocal.pErrorCodeArr[ToolErrorLocal.ErrorCodeArrEntries++]);
      pEntry->ErrorCode = ErrorCode;
      pEntry->pMsg      = pMsg;
   }
   else
   {
      ToolErrorLog (__FFL__, "Duplicate ErrorCode %d", ErrorCode);
      return TOOL_ERROR_DUPLICATE_ENTRY;
   }
   return NO_ERROR;
}

APIRET ToolErrorGetMessage (int ErrorCode, const char **ppMsg)
{
   if (ErrorCode == NO_ERROR)
   {
      *ppMsg = "NO_ERROR";
   }
   else
   {
      t_pToolErrorEntry pEntry;

      APIRET rc = ToolErrorFindEntry (ErrorCode, &pEntry);
      if (rc == TOOL_ERROR_ENTRY_NOT_FOUND)
           *ppMsg = NULL;
      else *ppMsg = pEntry->pMsg;
   }
   return NO_ERROR;
}

const char *ToolErrorMessage (int ErrorCode)
{
   static char TmpStr [20];  // Attention: This is not multi-threading compatible!!
   const char *pMsg;

   APIRET rc = ToolErrorGetMessage (ErrorCode, &pMsg);
   if (rc != NO_ERROR)
   {
      snprintf(TmpStr, sizeof(TmpStr), "[%d]", ErrorCode);
      pMsg = TmpStr;
   }
   return pMsg;
}

APIRET ToolErrorCheck (char const *pFileName, char const *pFunctionName, int LineNr, int ErrorCode)
{
   const char *pMsg;

   if (ErrorCode == NO_ERROR)
   {
      ToolErrorLog (pFileName, pFunctionName, LineNr, "%s called with Rc=0", __FUNCTION__);
   }
   else
   {
      APIRET rc = ToolErrorGetMessage (ErrorCode, &pMsg);
      switch (rc)
      {
         case NO_ERROR:
            ToolErrorLog(pFileName, pFunctionName, LineNr, "Error %d: %s", ErrorCode, pMsg);
            break;
         case TOOL_ERROR_ENTRY_NOT_FOUND:
             ToolErrorLog(pFileName, pFunctionName, LineNr, "Error %d: -- unregistered error code --", ErrorCode);
             break;
         default:
             ToolErrorLog(__FFL__, "Unexpected internal error %ld...", rc);
             ToolErrorLog(__FFL__, "...while trying to log problem %d coming from %s/%s/%d", ErrorCode, pFileName, pFunctionName, LineNr);
             break;
      }
   }
   return NO_ERROR;
}

APIRET ToolErrorSetLogFn (t_pToolErrorLogFn pLogFn)
{
   ToolErrorLocal.pLogFn = pLogFn;
   return NO_ERROR;
}


const char *ToolErrorTranslateErrno (int Errno)
{
   const char *pTxt;

   switch (Errno)
   {
      case EPERM  : pTxt = "Operation not permitted";                           break;
      case ENOENT : pTxt = "No such file or directory";                         break;
      case ESRCH  : pTxt = "No such process";                                   break;
      case EINTR  : pTxt = "Interrupted system call";                           break;
      case EIO    : pTxt = "I/O error";                                         break;
      case ENXIO  : pTxt = "No such device or address";                         break;
      case E2BIG  : pTxt = "Argument list too long";                            break;
      case ENOEXEC: pTxt = "Exec format error";                                 break;
      case EBADF  : pTxt = "Bad file number";                                   break;
      case ECHILD : pTxt = "No child processes";                                break;
      case EAGAIN : pTxt = "Try again";                                         break;
      case ENOMEM : pTxt = "Out of memory";                                     break;
      case EACCES : pTxt = "Permission denied";                                 break;
      case EFAULT : pTxt = "Bad address";                                       break;
      case ENOTBLK: pTxt = "Block device required";                             break;
      case EBUSY  : pTxt = "Device or resource busy";                           break;
      case EEXIST : pTxt = "File exists";                                       break;
      case EXDEV  : pTxt = "Cross-device link";                                 break;
      case ENODEV : pTxt = "No such device";                                    break;
      case ENOTDIR: pTxt = "Not a directory";                                   break;
      case EISDIR : pTxt = "Is a directory";                                    break;
      case EINVAL : pTxt = "Invalid argument";                                  break;
      case ENFILE : pTxt = "File table overflow";                               break;
      case EMFILE : pTxt = "Too many open files";                               break;
      case ENOTTY : pTxt = "Not a typewriter";                                  break;
      case ETXTBSY: pTxt = "Text file busy";                                    break;
      case EFBIG  : pTxt = "File too large";                                    break;
      case ENOSPC : pTxt = "No space left on device";                           break;
      case ESPIPE : pTxt = "Illegal seek";                                      break;
      case EROFS  : pTxt = "Read-only file system";                             break;
      case EMLINK : pTxt = "Too many links";                                    break;
      case EPIPE  : pTxt = "Broken pipe";                                       break;
      case EDOM   : pTxt = "Math argument out of domain of func";               break;
      case ERANGE : pTxt = "Math result not representable";                     break;
      default     : pTxt = "toolerror.coo: None of the standard error codes";   break;
   }
   return pTxt;
}


/* --------------------------------------------------------------------------------- */
/*                             Module initialisation                                 */
/* --------------------------------------------------------------------------------- */

APIRET ToolErrorInit (int MaxErrors)
{

   if (Initialised == TRUE)
      return TOOL_ERROR_ALREADY_INITIALISED;

   ToolErrorLocal.pErrorCodeArr       = (t_pToolErrorEntry) malloc (MaxErrors * sizeof(t_ToolErrorEntry));
   ToolErrorLocal.ErrorCodeArrLen     = MaxErrors;
   ToolErrorLocal.ErrorCodeArrEntries = 0;

   Initialised = TRUE;

   return NO_ERROR;
}

APIRET ToolErrorDeInit ()
{
   if (Initialised == FALSE)
      return TOOL_ERROR_NOT_INITIALISED;

   free (ToolErrorLocal.pErrorCodeArr);
   ToolErrorLocal.pLogFn = NULL;
   Initialised = FALSE;

   return NO_ERROR;
}

