/***********************************************************************
 * 
 *  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;

// Synchronization mutex
static
HANDLE            g_configIfMutex = NULL;


//++=======================================================================
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:
//  Notes: Function assumes that the caller has made sure that the destination
//         string buffer has enough space to receive the resulting string.
//
// 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);
}


//++=======================================================================
static
int SSCS_CALL
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, "-AddReference- Start\n", 0);

   // Increment the reference count on the object
   PlatAcquireMutex(g_configIfMutex);
   pConfigIfInstance->refCount ++;
   refCount = pConfigIfInstance->refCount;
   PlatReleaseMutex(g_configIfMutex);

   DbgTrace(2, "-AddReference- End, refCount = %0X\n", refCount);

   return refCount;
}


//++=======================================================================
static
void SSCS_CALL
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, "-ReleaseReference- Start\n", 0);

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

   // 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, "-ReleaseReference- End\n", 0);
}


//++=======================================================================
static
char* SSCS_CALL
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, "-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, "-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, "-GetEntryValue- Buffer allocation failure\n", 0);
   }

   DbgTrace(2, "-GetEntryValue- End, pValue = %0X\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);

   PlatAcquireMutex(g_configIfMutex);

   // 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") + 1);
      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 = AddReference;
               pConfigIfInstance->configIf.releaseReference = ReleaseReference;
               pConfigIfInstance->configIf.getEntryValue = 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)
                     {
                        #define MAX_LINE_LEN 1024
                        char  *pLine = (char*) malloc(MAX_LINE_LEN);
                        if (pLine)
                        {
                           while (fgets(pLine, MAX_LINE_LEN, pConfigFile) != NULL)
                           {
                              int   lineLength;

                              RemoveWhiteSpaceFromTheEnd(pLine);

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

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

                                 // 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);
                                 }
                              }
                           }

                           // Free the buffer allocated for holding line strings
                           free(pLine);
                        }
                        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);
         }

         // Free the buffer allocated for the file path
         free(pFilePath);
      }
      else
      {
         DbgTrace(0, "-GetConfigInterface- Buffer allocation error\n", 0);
      }
   }

   PlatReleaseMutex(g_configIfMutex);

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

   return retStatus;
}


//++=======================================================================
CasaStatus
ConfigIfInit(void)
//
// Arguments:  
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Initializes the configuration interface complex.
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus;

   DbgTrace(1, "-ConfigIfInit- Start\n", 0);

   // Allocate mutex
   if ((g_configIfMutex = PlatAllocMutex()) != NULL)
      retStatus = CASA_STATUS_SUCCESS;
   else
      retStatus = CasaStatusBuild(CASA_SEVERITY_INFORMATIONAL,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INSUFFICIENT_RESOURCES);

   DbgTrace(1, "-ConfigIfInit- End, retStatus = %0X\n", retStatus);

   return retStatus;
}


//++=======================================================================
void
ConfigIfUninit(void)
//
// Arguments:  
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Uninitializes the configuration interface complex.
//
// L2
//=======================================================================--
{
   DbgTrace(1, "-ConfigIfUninit- Start\n", 0);

   // Free mutex if necessary
   if (g_configIfMutex)
   {
      PlatDestroyMutex(g_configIfMutex);
      g_configIfMutex = NULL;
   }

   DbgTrace(1, "-ConfigIfUninit- End\n", 0);
}


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