/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $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 = 0;
	
#if defined(DATASTORAGE_MEMORY)
	res = new CMemoryDataStorage(profile);
#else
	res = new CDataStorage(profile);
#endif	

	if (res)
    {
        if (!res->Init(base_path))
        {
            res->Release();
            res = 0;
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't get IDataStorage instance");
    }
    return res;
 
}
//------------------------------------------------------------------------------------------------------
IDataStorage* createDataStorageForConfig(const String& profile)
{
	IDataStorage* res = new CDataStorage(profile);
	if (res)
	{
		if (!res->Init(""))
		{
			res->Release();
			res = 0;
		}
	}
	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 CConfigurationStorage();
    if (res)
    {
        if (!res->Init())
        {
            res->Release();
            res = 0;
        }
    }
    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_pathSeparator + 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 = 0;
    String path;

    struct stat fileinfo;
    long ifile_suffix = 0;
    while (true)
    {
        path = m_base_path + c_pathSeparator + m_profile +
            c_pathSeparator + c_streamFileTemplate + itoa(++ifile_suffix, 10);
        path += c_streamDataExt;
        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(), "wb");
            if (fdata)
            {
                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 = 0;
    if (res)
    {
        stream = new CStreamHandler(fdata, path); // CStreamHandler is new owner of fdata file handle.
    }
    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 + c_pathSeparator +
            (profileSpecific ? m_profile + c_pathSeparator : "") + key + 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 = 0;
        size_t sizebuff = 0;

        String path = m_base_path + c_pathSeparator +
            (profileSpecific ? m_profile + c_pathSeparator : "") + key + c_privateDataExt;

        if((res = loadPrivateData(path.c_str(), tmpbuff, sizebuff)) == true)
        {
            if (sizebuff == 0)
            {
                buffer.Allocate(0);
            }
            else
            {
                if ((res = buffer.Allocate(sizebuff)))
                {
                    memcpy(buffer.GetPointer(), tmpbuff, sizebuff);
                }
                else
                {
                    LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                        "%s%s", "can't allocate buffer. key: ", key.c_str());
                }
                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 + c_pathSeparator +
            (profileSpecific ? m_profile + c_pathSeparator : "") + key;

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

        int err_before = errno;
        DIR* dir_handle = opendir(path.c_str());
        if (dir_handle)
        {
            struct dirent* entry_info;
            String entry_path;
            while ( (entry_info = readdir(dir_handle)) != 0 )
            {
                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(/*key + (key[key.length()-1] == c_pathSeparator ? c_pathEmptyStr : c_pathSeparatorStr) + */entry_info->d_name);
                }
            }
            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)
    {
        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 + c_pathSeparator +
            (profileSpecific ? m_profile + c_pathSeparator : "") + key;

        struct stat st;
        res = (lstat(path.c_str(), &st) == 0);
        if (!res)
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                "lstat failed. Path: %s, Error: %d", path.c_str(), 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;
    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_pathSeparator + m_profile + c_pathSeparator + 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, "wb");
    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),
            "%s%s", "fopen failed. key file: ", key);
    }

    } // 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)
            {
                FILE* fin = fopen(key, "rb");
                if (fin)
                {
                    if (!(res = (fread(buffer, 1, size, fin) == size)))
                    {
                        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                            "%s%s", "fread failed. key file: ", key);
                    }
                    else
                    {
                        DecryptData(buffer, size);
                    }
                    fclose(fin);
                }
                else
                {
                    LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                        "%s%s", "fopen failed. key file: ", key);
                }
            }
            else
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog),
                    "%s%s", "can't allocate buffer. key file: ", key);
            }
        }
    }
    else
    {
        LOG_ERROR_(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;
    if ((res = (stat(key, &st) == 0)))
    {
        size = st.st_size;
    }
    else
    {
        LOG_ERROR_(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.find_last_of(c_privateDataExt);
        res = (extpos != String::npos) && (extpos == (strPath.length() - 1));
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::createPrivateDataPath(const char* key, bool profileSpecific)
{
    bool res = true;
    String Key = key;
    String FormedPath = m_base_path + (profileSpecific ? c_pathSeparator + m_profile : "");

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

        status = mkdir(FormedPath.c_str(), /*c_accessMode*/c_fullAccessMode);
        if ( ! ((status == 0) || ((status == -1) && (errno == EEXIST)))  )
        {
            // error logging
            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_pathSeparator + m_profile + c_pathSeparator;
    String path;
    while (   (pos = Key.rfind(c_pathSeparator, 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 + c_pathSeparator +
        (profileSpecific ? m_profile + c_pathSeparator : "") + key + 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)
    {
        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;
}
//------------------------------------------------------------------------------------------------------

}
}
