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

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

#include "../DataScrambler.h"

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

#include <base/fscapi.h>

#ifndef S_ISREG
	#define S_ISREG(x) (((x) & S_IFMT) == S_IFREG)
#endif

#pragma warning (disable:4996)

const char* const c_root_key = "./";

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())
    {
        if (!ExpandStringWithEnvironmentVars(c_privateDataPath, m_base_path))
		{
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't expand string with env.vars from string : %s", c_privateDataPath);
			return false;
		}
    }
    else
    {
	    if (!ExpandStringWithEnvironmentVars(base_path, m_base_path))
		{
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't expand string with env.vars from string : %s", c_privateDataPath);
			return false;
		}
    }

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

    return res;
}
//------------------------------------------------------------------------------------------------------
void CDataStorage::Release()
{
    delete this;
}
//------------------------------------------------------------------------------------------------------
IStreamHandler* CDataStorage::CreateStream()
{
    return 0;
}
//------------------------------------------------------------------------------------------------------
// 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));
    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), "save private data to file is failed. key: %s", key.c_str());
            }
        }
        else
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't create directories from key path");
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "key lenght, input buffer or buffer size = 0. key: %s", 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);
		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
            {
                if ((res = buffer.Allocate(sizebuff)))
                {
                    memcpy(buffer.GetPointer(), tmpbuff, sizebuff);
                }
                else
                {
                    LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't allocate buffer. key: %s", key.c_str());
                }
                free(tmpbuff);
            }
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "key lenght = 0. key: %s", 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 (key != c_root_key)
		{
			if (profileSpecific)
				path.append(m_profile).append(c_pathSeparatorStr);
			path.append(key);
		}
		else
		{
			if (profileSpecific)
				path.append(m_profile);
			else
				path.append("");
		}
		String base_path = path;

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

        DWORD err_before = GetLastError();
	path = path +"/*";
	BOOL fRet = FALSE;
	WIN32_FIND_DATAA fd = {};
	HANDLE hFind = FindFirstFileA(path.c_str(), &fd);
	if(hFind != INVALID_HANDLE_VALUE)
	{
		String entry_path;
		fRet = TRUE;
		do {
			if(!(strcmp( fd.cFileName, ".") && strcmp( fd.cFileName, "..")))
			{
				continue;
			}
               		entry_path = base_path + (((key[key.length()-1] == c_pathSeparator) && (key != c_root_key)) ? c_pathEmptyStr : c_pathSeparatorStr) + fd.cFileName;
                	if (isCorrectDataPath(entry_path.c_str(), fd.cFileName))
                	{
                    		list.push_back(fd.cFileName);
                	}
		}
		while(fRet && FindNextFileA( hFind, &fd));
		FindClose( hFind);
	}
        else
        {
            if (err_before == GetLastError())
            {
                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), "key lenght = 0. key: %s", 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);
		path.append(c_pathSeparatorStr);
		if (key != c_root_key)
		{
			if (profileSpecific)
				path.append(m_profile).append(c_pathSeparatorStr);
			path.append(key);
		}
		else
		{
			if (profileSpecific)
				path.append(m_profile);
			else
				path.append("");
		}


        struct _stat st;
        res = (_stat(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), "key lenght = 0. key: %s", key.c_str());
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::isCorrectDataPath(const char* path, const char* node)
{
    struct _stat st;
    bool res = (_stat(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), "_stat failed. Path: %s, node: %s", path, node);
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool CDataStorage::RemovePrivateData(const String& key)
{
    bool res = (key.length() > 0);
    if (res)
    {
        String path = m_base_path + (String)c_pathSeparatorStr + m_profile + (String)c_pathSeparatorStr + key;
        if (!(res = DeleteFilesystemEntity(path)))
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't remove private data. path: %s", path.c_str());
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "key lenght = 0. key: %s", 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), "fopen failed. key file: %s", 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), "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: %s", key, strerror(errno));
                }
            }
            else
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "can't allocate buffer. key file: %s", key);
            }
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "failed to read buffer size. key file: %s", 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 ? (String)c_pathSeparatorStr + m_profile : "");

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

        status = CreateDirectoryA(FormedPath.c_str(), NULL);
        if ( ! ((status == TRUE) || ((status == FALSE) && (GetLastError() == ERROR_ALREADY_EXISTS)))  )
        {
	        LOG_ERROR_(NS_Logging::GetLogger(c_DataStorageLog), "create directory failed: %s", FormedPath.c_str());
            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 + (String)c_pathSeparatorStr + m_profile + (String)c_pathSeparatorStr;
    String path;
    while ( (pos = Key.rfind(c_pathSeparatorStr, pos)) != String::npos )
    {
        path = base_path + Key.substr(0, pos);
        RemoveDirectoryA(path.c_str());
        pos_last = pos--;
    }
    if (pos_last > 0)
    {
        path = base_path + Key.substr(0, pos_last);
        DeleteFileA(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 + (String)c_pathSeparatorStr +
        (profileSpecific ? m_profile + c_pathSeparatorStr : "") + 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;
}
//------------------------------------------------------------------------------------------------------


}
}
