// ==========================================================================
// Author:  Yee Hsu
// Date:    6/11/2011
//
// Desc:    A Configuration Manager that uses Boost shared object library.
//      This API allows user to read/write into a shared resource
//      accessible by any external processes. It is mainly used for
//      configuration settings but may also be used as a means for IPC
//      (Inter-Process Communication). The API also allows a callback
//      to any process when a setting has been changed.
// ==========================================================================

#include "ConfigFile.h"
#include "ConfigManager.h"
#include "ConfigThread.h"

#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
//#include <boost/interprocess/sync/interprocess_condition.hpp> 
//#include <boost/interprocess/mapped_region.hpp>

using namespace boost::interprocess;
namespace bip = boost::interprocess;

namespace __ET__ 
{
    ConfigManager::ConfigManager()
        : m_bInitialized(false)
        , m_self_name("")
        , m_psm_map()
        , m_cb_context(NULL)
        , CallBackFunc(NULL)
        , m_ipc_mutex(NULL)
        , m_cb_config()
        , m_pthr(NULL)
        , m_ptime(NULL)
    {
        // Initialize in constructor
        Init();
    }

    ConfigManager::~ConfigManager()
    {
        // Shutdown in destructor
        Shutdown();
    }

    void ConfigManager::PurgeSharedMemory()
    {
        // delete all queues by looking at the list
        try
        {
            managed_shared_memory smp(open_only, SHARED_MEM_LIST);
            managed_shared_memory::const_named_iterator iter = smp.named_begin();

            while (iter != smp.named_end())
            {
                const managed_shared_memory::char_type *name = iter->name(); 
                if (name) { shared_memory_object::remove(name); }
                ++iter;
            }
        }
        catch (bip::interprocess_exception& ex){
            const char* ignore = ex.what();
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - PurgeSharedMemory(Queue) : %s\n", ex.what());
        }

        // delete all segments
        try
        {
            shared_memory_object::remove(SHARED_MEM_LIST); 
            shared_memory_object::remove(SHARED_MEM_DATA); 
            shared_memory_object::remove(SHARED_MEM_GLOB); 
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - PurgeSharedMemory(Segment) : %s\n", ex.what());
        }
    }

    ConfigManager& ConfigManager::Instance()
    {
        static ConfigManager _instance;
        return _instance;
    }

    bool ConfigManager::SetSharedMemory( const String& segment )
    {
        try
        {
            m_psm_map[segment] = new managed_shared_memory(open_or_create, segment.c_str(), SHARED_MEM_SIZE);
            ET_ASSERT(m_psm_map[segment]);
            return bool (m_psm_map[segment] != NULL);
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - SetSharedMemory : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::Init()
    {
        if (m_bInitialized) return true;
        
        if (!IsReady())
        {
            ConfigFile::Instance().Init();

            // create all our shared memory segments
            if (!SetSharedMemory(SHARED_MEM_GLOB))
                return false;

            if (!SetSharedMemory(SHARED_MEM_DATA))
                return false;

            if (!SetSharedMemory(SHARED_MEM_LIST))
                return false;

            if (!SetSharedMemory(GenerateOrGetSelfUUID()))
                return false;

            // create or find our shared mutex
            try
            {
                m_ipc_mutex = m_psm_map[SHARED_MEM_GLOB]->find_or_construct<interprocess_mutex>("_ipc_mutex")();

                ET_ASSERT(m_ipc_mutex);

                if (!m_ipc_mutex)
                    return false;
            }
            catch (std::exception& ex) 
            {
                ET_ERROR("Error - Init : %s\n", ex.what());
                return false;
            }

            // create timer thread to keep checking shared mutex
            if (!m_ptime)
            {
                m_ptime = m_tTimer.CreateThread(new ConfigManagerTimer(this));
                m_tTimer.StartAll();
            }

            // lets add ourself to the shared process list
            try
            {
                SharedMemScopedLock lock(*m_ipc_mutex); 
                m_psm_map[SHARED_MEM_LIST]->destroy<shared_string>(GenerateOrGetSelfUUID().c_str()); 
                m_psm_map[SHARED_MEM_LIST]->construct<shared_string>(GenerateOrGetSelfUUID().c_str())(GenerateOrGetSelfUUID().c_str(), m_psm_map[SHARED_MEM_LIST]->get_segment_manager());
            }
            catch (std::exception& ex) 
            {
                ET_ERROR("Error - Init : %s\n", ex.what());
                // return false;
            }
            m_bInitialized = true;
        }
        return true;
    }

    bool ConfigManager::IsReady() const
    {
        return m_bInitialized;
    }

    bool ConfigManager::RegisterCallback( ConfigManagerCallback callback /*= NULL*/, void* context /*= NULL*/ )
    {
        if (IsReady() && !CallBackFunc)
        {
            CallBackFunc=callback;
            m_cb_context=context;

            if (!m_pthr)
            {
                m_pthr = m_tWorker.CreateThread(new ConfigManagerWorker(this));
                m_tWorker.StartAll();
            }
            return true;
        }
        return false;
    }

    void ConfigManager::DelSharedMemory( const String& segment )
    {
        if (m_psm_map[segment])
        {
            delete m_psm_map[segment];
            m_psm_map[segment] = NULL;
        }
    }

    void ConfigManager::Shutdown()
    {
        if (IsReady())
        {
            m_tWorker.StopAll();
            m_tTimer.StopAll();

            try
            {
                SharedMemScopedLock lock(*m_ipc_mutex); 
                m_psm_map[SHARED_MEM_LIST]->destroy<shared_string>(GenerateOrGetSelfUUID().c_str());    // remove self from list
            }
            catch (std::exception& ex) 
            {
                ET_ERROR("Error - Shutdown : %s\n", ex.what());
            }

            try
            {
                shared_memory_object::remove(GenerateOrGetSelfUUID().c_str());          // remove self queue
                DelSharedMemory(GenerateOrGetSelfUUID().c_str());
                DelSharedMemory(SHARED_MEM_LIST);
                DelSharedMemory(SHARED_MEM_DATA);
                DelSharedMemory(SHARED_MEM_GLOB);
            }
            catch (std::exception& ex) 
            {
                ET_ERROR("Error - Shutdown : %s\n", ex.what());
            }

            ConfigFile::Instance().Shutdown();
            m_bInitialized = false;
        }
    }

    bool ConfigManager::HasSettings(const String& sKey)
    {
        return HasSettings(SHARED_MEM_DATA, sKey);
    }

    bool ConfigManager::GetSettings( KeyList& Keys )
    {
        return GetSettings(SHARED_MEM_DATA, Keys);
    }

    bool ConfigManager::GetSettings(const String& sKey, String& sValue)
    {
        if (GetSettings(SHARED_MEM_DATA, sKey, sValue))
            return true;
        else
            return ConfigFile::Instance().GetSettings(sKey, sValue);
    }

    bool ConfigManager::DelSettings(const String& sKey)
    {
        if (DelSettings(SHARED_MEM_DATA, sKey))
        {
            SetSettings(sKey, CONFIG_DEL);
            return true;
        }
        return false;
    }

    bool ConfigManager::SetSettings(const String& sKey, const String& sValue)
    {
        if (ConfigFile::Instance().HasSettings(sKey, sValue))
            return false;

        if (SetSettings(SHARED_MEM_DATA, sKey, sValue))
        {
            SetSettings(sKey, CONFIG_SET);
            return true;
        }
        return false;
    }

    bool ConfigManager::HasSettings(const String& segment, const String& sKey)
    {
        if (!IsReady())
            return false;

        try
        {
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            std::pair<shared_string*, size_t> sp = m_psm_map[segment]->find<shared_string>(sKey.c_str());

            if (sp.first)
                return true;
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - HasSettings : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::GetSettings(const String& segment, KeyList& Keys)
    {
        if (!IsReady())
            return false;

        try
        {
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            managed_shared_memory::const_named_iterator iter = m_psm_map[segment]->named_begin();
            Keys.clear();

            while (iter != m_psm_map[segment]->named_end())
            {
                const managed_shared_memory::char_type *name = iter->name();            
                //std::size_t name_len = iter->name_length();
                //const void *value = iter->value();
                Keys.push_back(name);
                ++iter;
            }
            return true;
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - GetSettings : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::GetSettings(const String& segment, MapList& objs)
    {
        if (!IsReady())
            return false;

        KeyList keys;

        if (GetSettings(segment, keys))
        {
            objs.clear();
            for (KeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter)
            {
                ConfigObject obj;
                obj.sKey = iter->c_str();

                if (GetSettings(segment, obj.sKey, obj.sValue))
                {
                    objs.push_back(obj);
                }
            }
            return true;
        }
        return false;
    }

    bool ConfigManager::GetSettings(const String& segment, SelfMemMap& objs)
    {
        if (!IsReady())
            return false;

        KeyList keys;

        if (GetSettings(segment, keys))
        {
            objs.clear();
            for (KeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter)
            {
                int nValue;

                if (GetSettings(segment, iter->c_str(), nValue))
                {
                    objs[iter->c_str()] = nValue;
                }
            }
            return true;
        }
        return false;
    }

    bool ConfigManager::DelSettings(const String& segment, const String& sKey)
    {
        if (!IsReady())
            return false;

        try
        {
            SharedMemScopedLock lock(*m_ipc_mutex); 
            m_psm_map[segment]->destroy<shared_string>(sKey.c_str());
            return ConfigFile::Instance().DelSettings(sKey);
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - DelSettings : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::DelSelfKeys( const String& sKey )
    {
        if (!IsReady())
            return false;

        try
        {
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            managed_shared_memory smp(open_only, GenerateOrGetSelfUUID().c_str());
            smp.destroy<int>(sKey.c_str()); 
            return true;
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - DelSelfKeys : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::SetSettings(const String& segment, const String& sKey, const String& sValue)
    {
        if (!IsReady())
            return false;

        try
        {
            SharedMemScopedLock lock(*m_ipc_mutex); 
            m_psm_map[segment]->destroy<shared_string>(sKey.c_str()); 

            if (m_psm_map[segment]->construct<shared_string>(sKey.c_str())(sValue.c_str(), m_psm_map[segment]->get_segment_manager()))
            {
                ConfigFile::Instance().SetSettings(sKey, sValue);
                return true;
            }
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - SetSettings : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::SetSettings(const String& sKey, const int& nOpCode)
    {
        if (!IsReady())
            return false;

        KeyList keys;
        if (GetSettings(SHARED_MEM_LIST, keys))
        {
            for (KeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter)
            {
                String other_uuid = iter->c_str();

                if (other_uuid != GenerateOrGetSelfUUID())
                {
                    SetSettings(other_uuid, sKey, nOpCode);
                }
            }
        }
        return true;
    }

    bool ConfigManager::SetSettings(const String& segment, const String& sKey, const int& nOpCode)
    {
        if (!IsReady())
            return false;

        try
        {
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            managed_shared_memory smp(open_only, segment.c_str());
            smp.destroy<int>(sKey.c_str()); 
            smp.construct<int>(sKey.c_str())(nOpCode);
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - SetSettings : %s\n", ex.what());
            return false;
        }
        return true;
    }

    bool ConfigManager::GetSettings(const String& segment, const String& sKey, String& sValue)
    {
        if (!IsReady())
            return false;

        try
        {
            sValue = "";
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            std::pair<shared_string*, size_t> sp = m_psm_map[segment]->find<shared_string>(sKey.c_str());

            if (sp.first)
            {
                sValue = sp.first->c_str();
                return true;
            }
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - GetSettings : %s\n", ex.what());
        }
        return false;
    }

    bool ConfigManager::GetSettings(const String& segment, const String& sKey, int &nValue)
    {
        if (!IsReady())
            return false;

        try
        {
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            std::pair<int*, size_t> sp = m_psm_map[segment]->find<int>(sKey.c_str());

            if (sp.first)
            {
                nValue = *(sp.first);
                return true;
            }
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - GetSettings : %s\n", ex.what());
        }
        return false;
    }

    long ConfigManager::NumSettings( const String& segment )
    {        
        if (!IsReady())
            return false;

        try
        {
            //SharedMemScopedLock lock(*m_ipc_mutex); 
            return m_psm_map[segment]->get_num_named_objects();
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - NumSettings : %s\n", ex.what());
        }
        return 0;
    }

    void ConfigManager::NotifyCallback( const String& sKey, const int& nValue )
    {
        if (!IsReady())
            return;

        if (!CallBackFunc)
            return;

        try
        {
            m_cb_config.sKey    = sKey;
            m_cb_config.sValue  = "";
            m_cb_config.nOpCode = nValue;

            if (m_cb_config.nOpCode == CONFIG_SET)
            {
                GetSettings(sKey, m_cb_config.sValue);
            }
            CallBackFunc(m_cb_config, m_cb_context);
        }
        catch (std::exception& ex) 
        {
            ET_ERROR("Error - NotifyCallback : %s\n", ex.what());
        }
    }

    __ET__::String ConfigManager::GenerateOrGetSelfUUID()
    {
        if (m_self_name.empty())
        {
            boost::uuids::uuid uuid = boost::uuids::random_generator()();
            const std::string suuid = boost::lexical_cast<std::string>(uuid);
            m_self_name = "__ET___APP_" + suuid;
        }
        return m_self_name;
    } 

    void ConfigManager::UnlockInfiniteBlockedSharedMutex()
    {      
        if (m_ipc_mutex)
        {
            try
            {
                boost::system_time timeout=boost::get_system_time() +
                boost::posix_time::milliseconds(10 * 1000);

                if (!m_ipc_mutex->timed_lock(timeout))
                {
                    ET_TRACE("Aquiring Shared Mutex Failed: Unlocking!");
                }
            }
            catch (std::exception& ex)
            {
                ET_ERROR("Error - ConfigManager:UnlockInfiniteBlockedSharedMutex : %s\n", ex.what());
            }
            m_ipc_mutex->unlock();
        }
    }
}