/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/


//===[ Include files ]=====================================================

#include "internal.h"

//===[ Type definitions ]==================================================

//
// Config Key object
// 
typedef struct _ConfigKey
{
   LIST_ENTRY  listEntry;
   char        *pKeyName;
   int         keyNameLen;
   char        *pValue;
   int         valueLen;

} ConfigKey, *pConfigKey;

//
// Config Interface instance data
// 
typedef struct _ConfigIfInstance
{
   LIST_ENTRY     listEntry;
   int            refCount;
   char           *pConfigFolder;
   int            configFolderLen;
   char           *pConfigName;
   int            configNameLen;
   LIST_ENTRY     configKeyListHead;
   ConfigIf       configIf;

} ConfigIfInstance, *PConfigIfInstance;

//===[ Function prototypes ]===============================================

//===[ Global variables ]==================================================

// ConfigIf variables
static
LIST_ENTRY        g_configIfListHead = {&g_configIfListHead, &g_configIfListHead};

static
int               g_numConfigIfObjs = 0;


//++=======================================================================
static void
RemoveWhiteSpaceFromTheEnd(
   IN       const char     *pInString)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   char  *pLineEnd = (char*) pInString + strlen(pInString) - 1;


   DbgTrace(3, "-RemoveWhiteSpaceFromTheEnd- Start\n", 0);

   while (pLineEnd != pInString)
   {
      if (*pLineEnd == '\n'
          || *pLineEnd == ' '
          || *pLineEnd == '\t')
      {
         // Strike this character
         *pLineEnd = '\0';
         pLineEnd --;
      }
      else
      {
         // Found a non-white character
         break;
      }
   }

   DbgTrace(3, "-RemoveWhiteSpaceFromTheEnd- End\n", 0);
}


//++=======================================================================
static char*
SkipWhiteSpace(
   IN       const char     *pInString)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   char  *pOutString = (char*) pInString;

   DbgTrace(3, "-SkipWhiteSpace- Start\n", 0);

   while (*pOutString != '\0')
   {
      if (*pOutString == '\n'
          || *pOutString == ' '
          || *pOutString == '\t')
      {
         // Skip this character
         pOutString ++;
      }
      else
      {
         // Found a non-white character
         break;
      }
   }

   DbgTrace(3, "-SkipWhiteSpace- End\n", 0);

   return pOutString;
}


//++=======================================================================
static char*
SkipNonWhiteSpace(
   IN       const char     *pInString)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   char  *pOutString = (char*) pInString;

   DbgTrace(3, "-SkipNonWhiteSpace- Start\n", 0);

   while (*pOutString != '\0')
   {
      if (*pOutString == '\n'
          || *pOutString == ' '
          || *pOutString == '\t')
      {
         // Found a white character
         break;
      }
      else
      {
         // Skip this character
         pOutString ++;
      }
   }

   DbgTrace(3, "-SkipNonWhiteSpace- End\n", 0);

   return pOutString;
}


//++=======================================================================
static void
LowerCaseString(
   IN       char        *pDestString,
   IN       const char  *pSrcString)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   int   i;

   DbgTrace(3, "-LowerCaseString- Start\n", 0);

   // Copy the string as lower case
   for (i = 0; pSrcString[i] != '\0'; i++)
   {
      if (isalpha(pSrcString[i]))
         pDestString[i] = tolower(pSrcString[i]);
      else
         pDestString[i] = pSrcString[i];
   }

   // Null terminate the destination string
   pDestString[i] = '\0';

   DbgTrace(3, "-LowerCaseString- End\n", 0);
}


//++=======================================================================
int SSCS_CALL
ConfigIf_AddReference(
   IN       const void  *pIfInstance)
//
// Arguments:  
//    pIfInstance -
//       Pointer to interface object.
//   
// Returns:
//    Interface reference count.
//                           
// Description:
//    Increases interface reference count.
//
// L2
//=======================================================================--
{
   int               refCount;
   ConfigIfInstance  *pConfigIfInstance = CONTAINING_RECORD(pIfInstance, ConfigIfInstance, configIf);

   DbgTrace(2, "-ConfigIf_AddReference- Start\n", 0);

   // Increment the reference count on the object
   pConfigIfInstance->refCount ++;
   refCount = pConfigIfInstance->refCount;

   DbgTrace(2, "-ConfigIf_AddReference- End, refCount = %08X\n", refCount);

   return refCount;
}


//++=======================================================================
void SSCS_CALL
ConfigIf_ReleaseReference(
   IN       const void  *pIfInstance)
//
// Arguments:  
//    pIfInstance -
//       Pointer to interface object.
//   
// Returns:
//    Nothing.
//                           
// Description:
//    Decreases interface reference count. The interface is deallocated if
//    the reference count becomes zero.
//
// L2
//=======================================================================--
{
   bool              freeObj = false;
   ConfigIfInstance  *pConfigIfInstance = CONTAINING_RECORD(pIfInstance, ConfigIfInstance, configIf);

   DbgTrace(2, "-ConfigIf_ReleaseReference- Start\n", 0);

   // Decrement the reference count on the object and determine if it needs to
   // be released.
   pConfigIfInstance->refCount --;
   if (pConfigIfInstance->refCount == 0)
   {
      // The object needs to be released, forget about it.
      freeObj = true;
      g_numConfigIfObjs --;
      RemoveEntryList(&pConfigIfInstance->listEntry);
   }

   // Free object if necessary
   if (freeObj)
   {
      // Free all of the config key objects associated with this configuration
      // interface instance.
      while (!IsListEmpty(&pConfigIfInstance->configKeyListHead))
      {
         LIST_ENTRY  *pListEntry;
         ConfigKey   *pConfigKey;

         // Get reference to entry at the head of the list
         pListEntry = pConfigIfInstance->configKeyListHead.Flink;
         pConfigKey = CONTAINING_RECORD(pListEntry, ConfigKey, listEntry);

         // Free the buffers associated with the ConfigKey
         free(pConfigKey->pKeyName);
         free(pConfigKey->pValue);

         // Remove the entry from the list
         RemoveEntryList(&pConfigKey->listEntry);

         // Finish freeing the ConfigKey
         free(pConfigKey);
      }

      // Free the rest of the buffers associated with the interface instance data
      free(pConfigIfInstance->pConfigFolder);
      free(pConfigIfInstance->pConfigName);
      free(pConfigIfInstance);
   }

   DbgTrace(2, "-ConfigIf_ReleaseReference- End\n", 0);
}


//++=======================================================================
char* SSCS_CALL
ConfigIf_GetEntryValue(
   IN       const void  *pIfInstance,
   IN       const char  *pKeyName)
//
// Arguments:  
//    pIfInstance -
//       Pointer to interface object.
//   
//    pKeyName -
//       Pointer to NULL terminated string that contains the
//       name of the key whose value is being requested.
//               
// Returns:
//    Pointer to NULL terminated string with value being requested or NULL.
//                           
// Description:
//    Gets value associated with a key for the configuration object.
//
// L2
//=======================================================================--
{
   ConfigIfInstance  *pConfigIfInstance = CONTAINING_RECORD(pIfInstance, ConfigIfInstance, configIf);
   char              *pValue = NULL;
   LIST_ENTRY        *pListEntry;
   ConfigKey         *pConfigKey;
   int               keyNameLen = (int) strlen(pKeyName);
   char              *pKeyNameLowercase;

   DbgTrace(2, "-ConfigIf_GetEntryValue- Start\n", 0);

   // Allocate enough space to hold lower case version of the key name
   pKeyNameLowercase = (char*) malloc(keyNameLen + 1);
   if (pKeyNameLowercase)
   {
      // Lower case the key name
      LowerCaseString(pKeyNameLowercase, pKeyName);

      // Try to find matching ConfigKey
      pListEntry = pConfigIfInstance->configKeyListHead.Flink;
      while (pListEntry != &pConfigIfInstance->configKeyListHead)
      {
         // Get pointer to the current entry
         pConfigKey = CONTAINING_RECORD(pListEntry, ConfigKey, listEntry);

         // Check if we have a match
         if (pConfigKey->keyNameLen == keyNameLen
             && memcmp(pKeyNameLowercase, pConfigKey->pKeyName, keyNameLen) == 0)
         {
            // We found it, return its value.
            pValue = (char*) malloc(pConfigKey->valueLen + 1);
            if (pValue)
            {
               strcpy(pValue, pConfigKey->pValue);
            }
            else
            {
               DbgTrace(0, "-ConfigIf_GetEntryValue- Buffer allocation failure\n", 0);
            }
            break;
         }

         // Advance to the next entry
         pListEntry = pListEntry->Flink;
      }

      // Free the lower case version of the key name
      free(pKeyNameLowercase);
   }
   else
   {
      DbgTrace(0, "-ConfigIf_GetEntryValue- Buffer allocation failure\n", 0);
   }

   DbgTrace(2, "-ConfigIf_GetEntryValue- End, pValue = %08X\n", (unsigned int) pValue);

   return pValue;
}



//++=======================================================================
CasaStatus
GetConfigInterface(
   IN       const char  *pConfigFolder,
   IN       const char  *pConfigName,
   INOUT    ConfigIf    **ppConfigIf)
//
// Arguments:  
//    pConfigFolder -
//       Pointer to NULL terminated string that contains the name of
//       the folder containing the configuration file.
//               
//    pConfigName -
//       Pointer to NULL terminated string containing the name of the
//       configuration entry.
//
//    ppConfigIf -
//       Pointer to variable that will receive pointer to ConfigIf
//       instance.
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Get configuration interface to specified configuration entry.
//
// L2
//=======================================================================--
{
   int               configFolderLen = (int) strlen(pConfigFolder);
   int               configNameLen = (int)strlen(pConfigName);
   ConfigIfInstance  *pConfigIfInstance;
   LIST_ENTRY        *pListEntry;
   CasaStatus        retStatus = CasaStatusBuild(CASA_SEVERITY_INFORMATIONAL,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_OBJECT_NOT_FOUND);

   DbgTrace(2, "-GetConfigInterface- Start\n", 0);

   // Check if we already have an entry in our list for the configuration
   pListEntry = g_configIfListHead.Flink;
   while (pListEntry != &g_configIfListHead)
   {
      // Get pointer to the current entry
      pConfigIfInstance = CONTAINING_RECORD(pListEntry, ConfigIfInstance, listEntry);

      // Check if we have a match
      if (pConfigIfInstance->configFolderLen == configFolderLen
          && pConfigIfInstance->configNameLen == configNameLen
          && memcmp(pConfigFolder, pConfigIfInstance->pConfigFolder, configFolderLen) == 0
          && memcmp(pConfigName, pConfigIfInstance->pConfigName, configNameLen) == 0)
      {
         // We found it, return the ConfigIf associated with the instance data
         // after incrementing its reference count.
         pConfigIfInstance->refCount ++;
         *ppConfigIf = &pConfigIfInstance->configIf;

         // Success
         retStatus = CASA_STATUS_SUCCESS;
         break;
      }

      // Advance to the next entry
      pListEntry = pListEntry->Flink;
   }

   // Proceed to create interface instance data for the configuration if none was found
   if (retStatus != CASA_STATUS_SUCCESS)
   {
      char  *pFilePath;

      // Build a string containing the configuration file path
      pFilePath = (char*) malloc(configFolderLen + 1 + configNameLen + sizeof(".conf"));
      if (pFilePath)
      {
         FILE  *pConfigFile;

         strcpy(pFilePath, pConfigFolder);
         strcat(pFilePath, pathCharString);
         strcat(pFilePath, pConfigName);
         strcat(pFilePath, ".conf");

         // Open the configuration file for reading
         pConfigFile = fopen(pFilePath, "r");
         if (pConfigFile)
         {
            // Opened the file, create a ConfigIfInstance object for it.
            pConfigIfInstance = (ConfigIfInstance*) malloc(sizeof(*pConfigIfInstance));
            if (pConfigIfInstance)
            {
               // Initialize the list head within the instance data
               InitializeListHead(&pConfigIfInstance->configKeyListHead);

               // Initialize the ConfigIf within the instance data
               pConfigIfInstance->configIf.addReference = ConfigIf_AddReference;
               pConfigIfInstance->configIf.releaseReference = ConfigIf_ReleaseReference;
               pConfigIfInstance->configIf.getEntryValue = ConfigIf_GetEntryValue;

               // Save the ConfigFolder and ConfigName information within the instance data
               pConfigIfInstance->pConfigFolder = (char*) malloc(configFolderLen + 1);
               if (pConfigIfInstance->pConfigFolder)
               {
                  strcpy(pConfigIfInstance->pConfigFolder, pConfigFolder);
                  pConfigIfInstance->configFolderLen = configFolderLen;

                  pConfigIfInstance->pConfigName = (char*) malloc(configNameLen + 1);
                  if (pConfigIfInstance->pConfigName)
                  {
                     strcpy(pConfigIfInstance->pConfigName, pConfigName);
                     pConfigIfInstance->configNameLen = configNameLen;

                     // Add the instance data into our list and bump up its reference count
                     // since we did that.
                     InsertTailList(&g_configIfListHead, &pConfigIfInstance->listEntry);
                     pConfigIfInstance->refCount = 1;

                     // At this point we want to return success to the caller even if we
                     // experience a read error.
                     retStatus = CASA_STATUS_SUCCESS;

                     // Return the ConfigIf associated with the instance data after
                     // incrementing its reference count.
                     pConfigIfInstance->refCount ++;
                     *ppConfigIf = &pConfigIfInstance->configIf;

                     // Now update the instance data with the information present in the file
                     if (fseek(pConfigFile, 0, SEEK_SET) == 0)
                     {
                        char  line[512];

                        while (fgets(line, sizeof(line), pConfigFile) != NULL)
                        {
                           int   lineLength;

                           RemoveWhiteSpaceFromTheEnd(line);

                           lineLength = (int) strlen(line);
                           if (lineLength != 0)
                           {
                              char        *pKey;
                              char        *pKeyEnd;
                              char        *pValue;
                              ConfigKey   *pConfigKey;

                              // Attempt to find the key
                              pKey = SkipWhiteSpace(line);

                              // Make sure that we are not dealing with an empty line or a comment
                              if (*pKey == '\0' || *pKey == '#')
                                 continue;

                              // Go past the key
                              pKeyEnd = SkipNonWhiteSpace(pKey);

                              // Protect against a malformed line
                              if (*pKeyEnd == '\0')
                              {
                                 DbgTrace(0, "-GetConfigInterface- Key found without value\n", 0);
                                 continue;
                              }

                              // Attempt to find the value
                              pValue = SkipWhiteSpace(pKeyEnd);

                              // Protect against a malformed line
                              if (*pValue == '\0')
                              {
                                 DbgTrace(0, "-GetConfigInterface- Key found without value\n", 0);
                                 continue;
                              }

                              // Delineate the key
                              *pKeyEnd = '\0';

                              // Create a ConfigKey object for this key/value pair
                              pConfigKey = (ConfigKey*) malloc(sizeof(*pConfigKey));
                              if (pConfigKey)
                              {
                                 pConfigKey->keyNameLen = (int) strlen(pKey);
                                 pConfigKey->pKeyName = (char*) malloc(pConfigKey->keyNameLen + 1);
                                 if (pConfigKey->pKeyName)
                                 {
                                    // Save the key name in lower case
                                    LowerCaseString(pConfigKey->pKeyName, pKey);

                                    pConfigKey->valueLen = (int) strlen(pValue);
                                    pConfigKey->pValue = (char*) malloc(pConfigKey->valueLen + 1);
                                    if (pConfigKey->pValue)
                                    {
                                       strcpy(pConfigKey->pValue, pValue);

                                       // The entry is ready, now associate it with the instance data.
                                       InsertTailList(&pConfigIfInstance->configKeyListHead, &pConfigKey->listEntry);
                                    }
                                    else
                                    {
                                       DbgTrace(0, "-GetConfigInterface- Buffer allocation failure\n", 0);
                                       free(pConfigKey->pKeyName);
                                       free(pConfigKey);
                                    }
                                 }
                                 else
                                 {
                                    DbgTrace(0, "-GetConfigInterface- Buffer allocation failure\n", 0);
                                    free(pConfigKey);
                                 }
                              }
                              else
                              {
                                 DbgTrace(0, "-GetConfigInterface- Buffer allocation failure\n", 0);
                              }
                           }
                        }
                     }
                     else
                     {
                        DbgTrace(0, "-GetConfigInterface- File seek error, errno = %d\n", errno);
                     }
                  }
                  else
                  {
                     DbgTrace(0, "-GetConfigInterface- Buffer allocation failure\n", 0);

                     // Free the buffers associated with the instance data
                     free(pConfigIfInstance->pConfigFolder);
                     free(pConfigIfInstance);
                  }
               }
               else
               {
                  DbgTrace(0, "-GetConfigInterface- Buffer allocation failure\n", 0);

                  // Free the buffer allocated for the instance data
                  free(pConfigIfInstance);
               }
            }
            else
            {
               DbgTrace(0, "-GetConfigInterface- Buffer allocation failure\n", 0);
            }

            // Close the file
            fclose(pConfigFile);
         }
         else
         {
            DbgTrace(0, "-GetConfigInterface- Unable to open config file, errno = %d\n", errno);
            DbgTrace(0, "-GetConfigInterface- Config file unable to open = %s\n", pFilePath);
         }
      }
      else
      {
         DbgTrace(0, "-GetConfigInterface- Buffer allocation error\n", 0);
      }
   }

   DbgTrace(2, "-GetConfigInterface- End, retStatus = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
//++=======================================================================
//++=======================================================================