//
// Copyright (C) 2006-2013 SIPez LLC.  All rights reserved.
//
// Copyright (C) 2004-2006 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
//
// Copyright (C) 2004-2006 Pingtel Corp.  All rights reserved.
// Licensed to SIPfoundry under a Contributor Agreement.
//
// $$
///////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES
// APPLICATION INCLUDES
#include <os/OsIntTypes.h>
#include <xmlparser/tinyxml.h>
#include <os/OsSysLog.h>
#include "net/Url.h"
#include "net/ProvisioningClass.h"
#include "net/ProvisioningAttrList.h"
#include "net/ProvisioningAgent.h"

// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STATIC VARIABLE INITIALIZATIONS

/* //////////////////////////// PUBLIC //////////////////////////////////// */

/* ============================ CREATORS ================================== */

////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::ProvisioningAgent
//
//  SYNOPSIS:    
//
//  DESCRIPTION: Default constructor
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAgent::ProvisioningAgent(const char* pServerClass, bool persistentStore)
: mServerClass(pServerClass)
{
   // If this Provisioning Agent is to use persistent configuration,
   // Attempt to open or create the corresponding xml document.
   if (persistentStore) {
      OsPath workingDirectory;

      // First try and find the .xml file, either in the SIPX_CONFDIR or the CWD.
      // The ServerClass name is used as the root file name.
      if (OsFileSystem::exists(SIPX_CONFDIR)) {
         workingDirectory = SIPX_CONFDIR;
         OsPath path(workingDirectory);
         path.getNativePath(workingDirectory);

      }
      else {
         OsPath path;
         OsFileSystem::getWorkingDirectory(path);
         path.getNativePath(workingDirectory);
      }

      // Build up the configuration file path
      mpConfigFile = new OsPath(workingDirectory + OsPathBase::separator + pServerClass + ".xml");

      mpXmlConfigDoc = new TiXmlDocument(mpConfigFile->data());

      if (!OsFileSystem::exists(*mpConfigFile)) {
         // xml configuration file does not exist, so create an initial one.
         UtlString blankXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
         blankXml += "<!-- This file is automatically generated - DO NOT EDIT -->\n\n";
         blankXml += "<" + mServerClass + ">\n";
         blankXml += "</" + mServerClass + ">\n";
         mpXmlConfigDoc->Parse(blankXml.data());
         mpXmlConfigDoc->SaveFile();

         OsSysLog::add(FAC_ACD, PRI_DEBUG,
                       "ProvisioningAgent::ProvisioningAgent - Creating initial configuration file: %s",
                       mpConfigFile->data());
      }
      else {
         // Now attempt to load the file and build the DOM
         mpXmlConfigDoc->LoadFile();

         if (mpXmlConfigDoc->Error()) {
            // There were errors parsing the existing config file.
            // Rename it and create a new blank one.
            OsPath badFile(workingDirectory + OsPathBase::separator + pServerClass + ".xml_INVALID");
            OsFileSystem::rename(*mpConfigFile, badFile);

            delete mpXmlConfigDoc;
            mpXmlConfigDoc = new TiXmlDocument(mpConfigFile->data());

            // Now build up a new blank configuration file.
            UtlString blankXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
            blankXml += "<!-- This file is automatically generated - DO NOT EDIT -->\n\n";
            blankXml += "<" + mServerClass + ">\n";
            blankXml += "</" + mServerClass + ">\n";
            mpXmlConfigDoc->Parse(blankXml.data());
            mpXmlConfigDoc->SaveFile();

            OsSysLog::add(FAC_ACD, PRI_DEBUG,
                          "ProvisioningAgent::ProvisioningAgent - Configuration file: %s corrupted, corrupted, creating blank file",
                          mpConfigFile->data());
         }
      }
   }
   else {
      // No configuration file or persistent storage
      mpConfigFile = NULL;
      mpXmlConfigDoc = NULL;
   }
}


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::~ProvisioningAgent
//
//  SYNOPSIS:    
//
//  DESCRIPTION: Destructor
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAgent::~ProvisioningAgent(){
   delete mpConfigFile;
   delete mpXmlConfigDoc;
}

/* ============================ MANIPULATORS ============================== */

////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::registerClass
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

OsStatus ProvisioningAgent::registerClass(ProvisioningClass* pProvisioningClass){
   // Search to see if this class has already registered.
   const UtlString* pClassName = pProvisioningClass->getClassName();
   if (mRegisteredClasses.contains(pClassName)) {
      // A class can only register once.
      return OS_FAILED;
   }
   else {
      // If not, add it to the list of Provisioning Classes.
      if (mRegisteredClasses.insertKeyAndValue((UtlString*)pClassName, pProvisioningClass) == NULL) {
         // The insert failed.  Probably due to a collision.
         return OS_FAILED;
      }
      else {
         // Pass the registered provisioning class the pointer to the Xml Config Doc.
         pProvisioningClass->setXmlConfigDoc(mpXmlConfigDoc);
      }
   }

   return OS_SUCCESS;
}


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::unregisterClass
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

OsStatus ProvisioningAgent::unregisterClass(ProvisioningClass* pProvisioningClass){
   // If this class has already registered, remove it and delete the key store.
   if (mRegisteredClasses.removeReference(pProvisioningClass->getClassName()) != NULL) {
      return OS_SUCCESS;
   }
   else {
      // Did not find Class in list.
      return OS_FAILED;
   }
}



////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::Create
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAttrList* ProvisioningAgent::Create(ProvisioningAttrList& rRequestAttributes){
   // Extract and lookup the <object-class>
   ProvisioningClass* pProvisioningClass = lookupProvisioningClass(rRequestAttributes);

   if (pProvisioningClass == NULL) {
      // Either the request was invalid or the Class isn't registered.
      return NULL;
   }
   else {
      // Call the target Provisioning Class.
      return pProvisioningClass->Create(rRequestAttributes);
   }
}


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::Delete
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAttrList* ProvisioningAgent::Delete(ProvisioningAttrList& rRequestAttributes){
   // Extract and lookup the <object-class>
   ProvisioningClass* pProvisioningClass = lookupProvisioningClass(rRequestAttributes);

   if (pProvisioningClass == NULL) {
      // Either the request was invalid or the Class isn't registered.
      return NULL;
   }
   else {
      // Call the target Provisioning Class.
      return pProvisioningClass->Delete(rRequestAttributes);
   }
}


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::Set
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAttrList* ProvisioningAgent::Set(ProvisioningAttrList& rRequestAttributes){
   // Extract and lookup the <object-class>
   ProvisioningClass* pProvisioningClass = lookupProvisioningClass(rRequestAttributes);

   if (pProvisioningClass == NULL) {
      // Either the request was invalid or the Class isn't registered.
      return NULL;
   }
   else {
      // Call the target Provisioning Class.
      return pProvisioningClass->Set(rRequestAttributes);
   }
}


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::Get
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAttrList* ProvisioningAgent::Get(ProvisioningAttrList& rRequestAttributes){
   // Extract and lookup the <object-class>
   ProvisioningClass* pProvisioningClass = lookupProvisioningClass(rRequestAttributes);

   if (pProvisioningClass == NULL) {
      // Either the request was invalid or the Class isn't registered.
      return NULL;
   }
   else {
      // Call the target Provisioning Class.
      return pProvisioningClass->Get(rRequestAttributes);
   }
}


////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::Action
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningAttrList* ProvisioningAgent::Action(ProvisioningAttrList& rRequestAttributes){
   // Extract and lookup the <object-class>
   ProvisioningClass* pProvisioningClass = lookupProvisioningClass(rRequestAttributes);

   if (pProvisioningClass == NULL) {
      // Either the request was invalid or the Class isn't registered.
      return NULL;
   }
   else {
      // Call the target Provisioning Class.
      return pProvisioningClass->Action(rRequestAttributes);
   }
}


/* ============================ ACCESSORS ================================= */

/* ============================ INQUIRY =================================== */

/* //////////////////////////// PROTECTED ///////////////////////////////// */

////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  NAME:        ProvisioningAgent::lookupProvisioningClass
//
//  SYNOPSIS:    
//
//  DESCRIPTION: 
//
//  RETURNS:     None.
//
//  ERRORS:      None.
//
//  CAVEATS:     None.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

ProvisioningClass* ProvisioningAgent::lookupProvisioningClass(ProvisioningAttrList& rRequestAttributes){
   const char*        pTargetClass;
   ProvisioningClass* pProvisioningClass;

   // Extract the target class value from the <object-class> parameter.
   if (rRequestAttributes.getAttribute("object-class", pTargetClass) == false) {
      // The <object-class> tag was not found. Must be a bad request.
      OsSysLog::add(FAC_ACD, PRI_ERR,
                    "ProvisioningAgent::lookupProvisioningClass - <object-class> parameter not found.");
      return NULL;
   }

   // Now that we have the target class, try and find a registered Provisioning Class.
   UtlString targetClassString(pTargetClass);
   pProvisioningClass = dynamic_cast<ProvisioningClass*>(mRegisteredClasses.findValue(&targetClassString));
   if (pProvisioningClass == NULL) {
      // The class doesn't appear to be registered.
      OsSysLog::add(FAC_ACD, PRI_ERR,
                    "ProvisioningAgent::lookupProvisioningClass - Provisioning Class: '%s' not registered.",
                    pTargetClass);
      return NULL;
   }

   // Return a pointer to the target Provisioning Class.
   return pProvisioningClass;
}


/* //////////////////////////// PRIVATE /////////////////////////////////// */

/* ============================ FUNCTIONS ================================= */

