/*
 * Copyright 2009 Funambol, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id$ */

#ifdef __MACH__
#include <sys/malloc.h> // mac os x
#else
#include <malloc.h> // linux, windows
#endif
//#include <malloc.h>

#include <cstring>
#include <cstdlib>
#include <errno.h>

#include <algorithm>

#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "CDataStorage.h"
#include "DataStorageDefs.h"
#include "Logger/Logger.h"

#include "common/Buffer.h"

#include "../DataScrambler.h"
#include "../memory/CMemoryDataStorage.h"

namespace NS_DM_Client
{
namespace NS_DataStorage
{
//------------------------------------------------------------------------------------------------------
IDataStorage* CreateDataStorage(const String& profile, const String& base_path)
{
    IDataStorage* res = (IDataStorage*)NULL;
    
#if defined(DATASTORAGE_MEMORY)
    res = new(std::nothrow) CMemoryDataStorage(profile);
#else
    res = new(std::nothrow) CDataStorage(profile);
#endif

    if (res != (IDataStorage*)NULL)
    {
        if (!res->Init(base_path))
        {
            res->Release();
            res = (IDataStorage*)NULL;
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't get IDataStorage instance");
    }
    return res;
 
}
//------------------------------------------------------------------------------------------------------
IDataStorage* createDataStorageForConfig(const String& profile)
{
    IDataStorage* res = new(std::nothrow) CDataStorage(profile);
    if (res != (IDataStorage*)NULL)
    {
        if (!res->Init(""))
        {
            res->Release();
            res = (IDataStorage*)NULL;
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't get IDataStorage instance");
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
// factory method for IConfigurationStorage instance creation
IConfigurationStorage* CreateConfigurationStorage()
{
    IConfigurationStorage* res = new(std::nothrow) CConfigurationStorage();
    if (res != (IConfigurationStorage*)NULL)
    {
        if (!res->Init())
        {
            res->Release();
            res = (IConfigurationStorage*)NULL;
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't get IConfigurationStorage instance");
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
CDataStorage::CDataStorage(const String& profile) : m_profile(profile), m_base_path("")
{
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::Init(const String& base_path)
{
    bool res = false;
    // - create predefined directories if not exist
    if (base_path.empty())
    {
        m_base_path = c_privateDataPath;
    }
    else
    {
        m_base_path = base_path;
    }

    res = CreatePath(m_base_path);
    if (res)
    {
        String subPath = m_base_path + c_pathSeparatorStr + m_profile;
        res = CreatePath(subPath);
        if (!res)
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                "%s%s", "can't create predefined directory : ", subPath.c_str());
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "can't create predefined directory : ", m_base_path.c_str());
    }

    return res;
}
//------------------------------------------------------------------------------------------------------
void CDataStorage::Release()
{
    delete this;
}
//------------------------------------------------------------------------------------------------------
IStreamHandler* CDataStorage::CreateStream()
{
    bool res = false;
    // - generate unique file-name and create file for stream storing
    FILE* fdata = NULL;
    String path;

    struct stat fileinfo;
    long ifile_suffix = 0;
    while (true)
    {
        path.append(m_base_path).append(c_pathSeparatorStr).append(m_profile)
            .append(c_pathSeparatorStr).append(c_streamFileTemplate).append(itoa(++ifile_suffix, 10));
        path += c_streamDataExt;

        memset(&fileinfo, 0, sizeof(struct stat));
        if ((stat(path.c_str(), &fileinfo) == -1) && (errno == ENOENT)) // check if file already exist
        { // if not exist try create for writing and get file handle
            fdata = fopen(path.c_str(), "w");
            if (fdata != NULL)
            {
                res = true;
                break;
            }
        }
        if ((ifile_suffix >= c_MaxStreamNumber))
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                "%s", "max numbmer of streams is exceeding. Can't create new stream: ");
            break;
        }
    }

    IStreamHandler* stream = (IStreamHandler*)NULL;
    if (res)
    {
        stream = new(std::nothrow) CStreamHandler(fdata, path); // CStreamHandler is new owner of fdata file handle.
        if(stream == NULL) LOG_WARNING_(NS_Logging::GetLogger(c_DataStorageLog), "new CStreamHandler");
    }

    if(fdata != NULL) fclose(fdata);

    return stream;
}
//------------------------------------------------------------------------------------------------------
// functions for daemon's private data
//------------------------------------------------------------------------------------------------------
bool CDataStorage::SavePrivateData(const String& key, const void* buffer, size_t size, bool profileSpecific)
{
    bool res = ((key.length() > 0)); /*&& (buffer) && (size > 0)*/
    if (res)
    {
        if (createPrivateDataPath(key.c_str(), profileSpecific))
        {
            String path(m_base_path);
            path.append(c_pathSeparatorStr);
            if (profileSpecific)
                path.append(m_profile).append(c_pathSeparatorStr);
            path.append(key).append(c_privateDataExt);

            if (!(res = savePrivateData(path.c_str(), buffer, size)))
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                    "%s%s", "save private data to file is failed. key: ", key.c_str());
            }
        }
        else
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                "%s", "can't create directories from key path");
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "key lenght, input buffer or buffer size = 0. key: ", key.c_str());
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::SavePrivateData(const String& key, const Buffer& buffer, bool profileSpecific)
{
    return SavePrivateData(key, buffer.GetPointer(), buffer.Size(), profileSpecific);
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::LoadPrivateData(const String& key, Buffer& buffer, bool profileSpecific)
{
    bool res = (key.length() > 0);
    if (res)
    {
        void* tmpbuff = NULL;
        size_t sizebuff = 0;

        String path(m_base_path);
        path.append(c_pathSeparatorStr);
        if (profileSpecific){
            path.append(m_profile).append(c_pathSeparatorStr);
        }
        path.append(key).append(c_privateDataExt);

       if((res = loadPrivateData(path.c_str(), tmpbuff, sizebuff)) == true)
        {
           if (sizebuff == 0)
            {
                buffer.Allocate(0);
            }
           else
            {
              buffer.Allocate(sizebuff, '\0');
              memcpy(buffer.GetPointer(), tmpbuff, sizebuff);
              free(tmpbuff);
            }
        }
    }
   else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "key lenght = 0. key: ", key.c_str());
    }
   return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::GetChildList(const String& key, StringArray& list, bool profileSpecific)
{
    bool res = (key.length() > 0);
    if (res)
    {
        String path(m_base_path);
        path.append(c_pathSeparatorStr);
        if (profileSpecific)
            path.append(m_profile).append(c_pathSeparatorStr);
        path.append(key);
        
        {   // lock
        NS_Common::Lock lock(m_sync);

        int err_before = errno;
        DIR* dir_handle = opendir(path.c_str());
        if (dir_handle != (DIR*)NULL)
        {
            struct dirent* entry_info;
            String entry_path = "";
            while ( (entry_info = readdir(dir_handle)) != (struct dirent*)NULL )
            {
                entry_path = path + (key[key.length()-1] == c_pathSeparator ? c_pathEmptyStr : c_pathSeparatorStr) + entry_info->d_name;
                if (isCorrectDataPath(entry_path.c_str(), entry_info->d_name))
                {
                    list.push_back(entry_info->d_name);/*key + (key[key.length()-1] == c_pathSeparator ? c_pathEmptyStr : c_pathSeparatorStr) + */
                }
            }
            closedir(dir_handle);
            res = true;
        }
        else
        {
            if (err_before == errno)
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                    "can't open dir. Dir name: %s, error number the same as before open", path.c_str());
            }
            else
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                    "can't open dir. Dir name: %s, errno: %d", path.c_str(), errno);
            }
        }

        } // unlock
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "key lenght = 0. key: ", key.c_str());
    }

    if (res)
    {
        //std::sort(list.begin(), list.end(), compare_cs);
        std::sort(list.begin(), list.end());
    }

    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::Exist(const String& key, bool profileSpecific)
{
    bool res = (key.length() > 0);
    if (res)
    {
        String path(m_base_path);
        path.append(c_pathSeparatorStr);
        if (profileSpecific)
            path.append(m_profile).append(c_pathSeparatorStr);
        path.append(key);
        
        struct stat st;
        res = (lstat(path.c_str(), &st) == 0);
        if (!res)
        {
            LOG_WARNING_(NS_Logging::GetLogger(c_DataStorageLog),
                "lstat failed. Path: %s, Error: %d, ErrorMessage: '%s'", path.c_str(), errno, strerror(errno));
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "key lenght = 0. key: ", key.c_str());
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::isCorrectDataPath(const char* path, const char* node)
{
    struct stat st;
    memset(&st, 0, sizeof(struct stat));
    bool res = (lstat(path, &st) == 0);
    if (res)
    {
        res = false;
        if (S_ISDIR(st.st_mode))
        {
            if(  !(
                ((strlen(node) == 1) && (node[0] == '.'))
                ||
                ((strlen(node) == 2) && (node[0] == '.') && (node[1] == '.'))
                )
               )
            {
                res = true;
            }
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "lstat failed");
    }

    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::RemovePrivateData(const String& key)
{
    bool res = (key.length() > 0);
    if (res)
    {
        String path = m_base_path + c_pathSeparatorStr + m_profile + c_pathSeparatorStr + key;
        if (!(res = DeleteFilesystemEntity(path)))
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                "%s%s", "can't remove private data. path: ", path.c_str());
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "key lenght = 0. key: ", key.c_str());
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::savePrivateData(const char* key, const void* buffer, size_t size)
{
    bool res = false;

    {   // lock
    NS_Common::Lock lock(m_sync);

    FILE* fin = fopen(key, "w");
    if (fin)
    {
        if ((buffer == 0) || (size <=0))
        {
            res = true;
        }
        else
        {
            res = encryptAndSave(fin, key, buffer, size);
        }
        fclose(fin);
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "fopen failed. key file: %s, error message: %s", key, strerror(errno));
    }

    } // unlock

    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::loadPrivateData(const char* key, void*& buffer, size_t& size)
{
    bool res = false;

    {   // lock
    NS_Common::Lock lock(m_sync);

    if (readBufferSize(key, size))
     {
        if (size == 0)
         {
            res = true;
         }
        else
         {
            if ((buffer = malloc(size)) != 0)
              {
                memset(buffer, '\0',size);
                FILE* fin = fopen(key, "r");
                if (fin)
                  {
                    if (!(res = (fread(buffer, 1, size, fin) == size)))
                       {
                        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                            "fread failed. key file: %s", key);
                       }
                    else
                       {
                        DecryptData(buffer, size);
                       }
                    fclose(fin);
                  }
                else
                  {
                    LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                        "fopen failed. key file: %s, error message: %s", key, strerror(errno));
                  }
              }
            else
              {
                LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                    "%s%s", "can't allocate buffer. key file: ", key);
              }
         }
     }
    else
     {
        LOG_WARNING_(NS_Logging::GetLogger(c_DataStorageLog),
            "%s%s", "failed to read buffer size. key file: ", key);
     }
    } // unlock

    return res;

}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::readBufferSize(const char* key, size_t& size)
{
    bool res = false;
    struct stat st;
    memset(&st, 0, sizeof(struct stat));
    if ((res = (stat(key, &st) == 0)))
    {
        size = st.st_size;
    }
    else
    {
        LOG_WARNING_(NS_Logging::GetLogger(c_DataStorageLog),
            "stat failed. path: %s", key);
    }

    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::isPrivateDataFileType(const char* path)
{
    bool res = false;
    struct stat st;
    if ((stat(path, &st) == 0) && (S_ISREG(st.st_mode)))
    {
        String strPath = path;
        size_t extpos = strPath.rfind(c_privateDataExt);
        res = (extpos != String::npos) && (extpos == (strPath.length() - strlen(c_privateDataExt)));
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::createPrivateDataPath(const char* key, bool profileSpecific)
{
    bool res = true;
    String Key = key;
    String FormedPath = m_base_path + (profileSpecific ? c_pathSeparatorStr + m_profile : "");

    size_t pos_beg = 0;
    size_t pos_end = 0;
    int status = -1;
    while ( (pos_end = Key.find(c_pathSeparatorStr, pos_beg)) != String::npos )
    {
        FormedPath += c_pathSeparatorStr;
        FormedPath += Key.substr(pos_beg, pos_end - pos_beg);

        status = mkdir(FormedPath.c_str(), c_fullAccessMode);
        if ( ! ((status == 0) || ((status == -1) && (errno == EEXIST)))  )
        {       
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                    "createPrivateDataPath: mkdir failed. errno: %d, errno message: %s", errno, strerror(errno));
            
            res = false;
            break;
        }
        pos_beg = pos_end + 1;
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
void CDataStorage::tryRemovePrivateDataPath(const char* key)
{
    size_t pos = strlen(key);
    size_t pos_last = 0;
    String Key = + key;
    String base_path = m_base_path + c_pathSeparatorStr + m_profile + c_pathSeparatorStr;
    String path;
    while (   (pos = Key.rfind(c_pathSeparatorStr, pos)) != String::npos )
    {
        path = base_path + Key.substr(0, pos);
        rmdir(path.c_str());
        pos_last = pos--;
    }
    if (pos_last > 0)
    {
        path = base_path + Key.substr(0, pos_last);
        unlink(path.c_str());
    }
}
//------------------------------------------------------------------------------------------------------
const char* CDataStorage::GetBasePath()
{
    return m_base_path.c_str();
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::GetPrivateDataSize(const String& key, size_t& size, bool profileSpecific)
{
    String path(m_base_path);
    path.append(c_pathSeparatorStr);
    if (profileSpecific)
        path.append(m_profile).append(c_pathSeparatorStr);
    path.append(key).append(c_privateDataExt);

    return readBufferSize(path.c_str(), size);
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::encryptAndSave(FILE* fin, const char* key, const void* buffer, size_t size)
{
    bool res = false;
#if defined(DATASTORAGE_SCRAMBLE)
    void* temp_buffer = malloc(size);
    if (temp_buffer != NULL)
    {
        memset(temp_buffer, 0, size);
        memcpy(temp_buffer, buffer, size);
        EncryptData(temp_buffer, size);
        if (!(res = (fwrite(temp_buffer, 1, size, fin) == size)))
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "fwrite failed. key file: %s", key);
        }
        free(temp_buffer);
    }
    else
    {
        res = false;
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
            "can't allocate memory for temporary buffer need for encryption: %s", key);
    }
#else
    if (!(res = (fwrite(buffer, 1, size, fin) == size)))
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "fwrite failed. key file: %s", key);
    }
#endif
    return res;
}
//------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------
CConfigurationStorage::CConfigurationStorage()
{
    m_DataStorage = createDataStorageForConfig("");
}
//------------------------------------------------------------------------------------------------------
bool CConfigurationStorage::Init()
{
    return (m_DataStorage !=0);
}
//------------------------------------------------------------------------------------------------------
void CConfigurationStorage::Release()
{
    m_DataStorage->Release();
    delete this;
}
//------------------------------------------------------------------------------------------------------
CConfigurationStorage::~CConfigurationStorage()
{
}
//------------------------------------------------------------------------------------------------------
bool CConfigurationStorage::SaveConfiguration(const void* buffer, size_t size)
{
    return m_DataStorage->SavePrivateData(c_confFile, buffer, size, false);
}
//------------------------------------------------------------------------------------------------------
bool CConfigurationStorage::SaveConfiguration(const Buffer& buffer)
{
    return m_DataStorage->SavePrivateData(c_confFile, buffer, false);
}
//------------------------------------------------------------------------------------------------------
bool CConfigurationStorage::LoadConfiguration(Buffer& buffer)
{
    return m_DataStorage->LoadPrivateData(c_confFile, buffer, false);
}
//------------------------------------------------------------------------------------------------------

const char* GetBasePath()
{
    return c_privateDataPath;
}
//------------------------------------------------------------------------------------------------------

}
}
