/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   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 General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

#include <unistd.h>
#include <string>
#include <iostream>
#include <fstream>
#include <time.h>
using namespace std;

#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;

#include "configcpp.h"
using namespace config;

#include "sedit.h"
#include "idbregistry.h"
using namespace winport;

#include "fixup.h"

namespace
{

int fixupCalpontXML()
{
    int rc = -1; //assume the worst

    DWORDLONG totalMemSize = 1024ULL * 1024ULL * 1024ULL * 2ULL;
#ifdef _WIN64
    totalMemSize *= 4ULL;
#endif
    MEMORYSTATUSEX memStat;
    memStat.dwLength = sizeof(memStat);

    if (GlobalMemoryStatusEx(&memStat) == 0)
        //FIXME: Assume 2GB?
        (void)0;
    else
    {
#ifndef _WIN64
        memStat.ullTotalPhys = std::min(memStat.ullTotalVirtual, memStat.ullTotalPhys);
#endif
        //We now have the total phys mem in bytes
        //FIXME: should we use avail phys mem instead?
        totalMemSize = memStat.ullTotalPhys;
    }

    try
    {
        Config* cf = Config::makeConfig();
        string section;
        string parm;
        string val;

        //Fixup ConnectionsPerPrimProc
        section = "PrimitiveServers";
        parm = "ConnectionsPerPrimProc";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "2";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }
        else
        {
            cf->delConfig(section, parm);
            cf->setConfig(section, parm, val);
        }

        //Fixup PrefetchThreshold
        parm = "PrefetchThreshold";
        val = cf->getConfig(section, parm);

        if (val == "30")
        {
            val = "5";
            cf->setConfig(section, parm, val);
            cout << "Fixing " << section << "." << parm << " = " << val << endl;
        }

        //Fixup Count
        parm = "Count";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "1";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup PMS2
        section = "PMS2";
        parm = "IPAddr";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "127.0.0.1";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "Port";
            val = "8620";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup UmMaxMemorySmallSide
        section = "HashJoin";
        parm = "UmMaxMemorySmallSide";
        val = cf->getConfig(section, parm);
#ifdef _WIN64

        if (val == "4G")
#else
        if (val == "256M")
#endif
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup TotalUmMaxMemorySmallSide
        parm = "TotalUmMaxMemorySmallSide";
        val = cf->getConfig(section, parm);
#ifdef _WIN64

        if (val == "8G")
#else
        if (val == "512M")
#endif
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup TotalUmMemory
        parm = "TotalUmMemory";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
#ifdef _WIN64
            totalMemSize /= (1024ULL * 1024ULL * 1024ULL);

            if (totalMemSize >= 31)
                val = "16G";
            else if (totalMemSize >= 15)
                val = "8G";
            else if (totalMemSize >= 7)
                val = "4G";
            else
                val = "2G";

#else
            val = "2G";
#endif
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup MaxMemoryPerUnion
        section = "Union";
        parm = "MaxMemoryPerUnion";
        val = cf->getConfig(section, parm);
#ifdef _WIN64

        if (val == "4G")
#else
        if (val == "256M")
#endif
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup TotalUnionMemory
        parm = "TotalUnionMemory";
        val = cf->getConfig(section, parm);
#ifdef _WIN64

        if (val == "8G")
#else
        if (val == "512M")
#endif
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup RowAggregation.MaxMemory
        section = "RowAggregation";
        parm = "MaxMemory";
        val = cf->getConfig(section, parm);
#ifdef _WIN64

        if (val == "1G")
#else
        if (val == "128M")
#endif
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup OrderByLimit.MaxMemory
        section = "OrderByLimit";
        parm = "MaxMemory";
        val = cf->getConfig(section, parm);
#ifdef _WIN64

        if (val == "1G")
#else
        if (val == "128M")
#endif
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup Installation.UMStorageType
        section = "Installation";
        parm = "UMStorageType";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "internal";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup DBRootStorageType
        parm = "DBRootStorageType";
        val = cf->getConfig(section, parm);

        if (val == "local")
        {
            val = "internal";
            cf->setConfig(section, parm, val);
            cout << "Fixing " << section << "." << parm << " = " << val << endl;
        }

        //Fixup WES
        section = "pm1_WriteEngineServer";
        parm = "IPAddr";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "127.0.0.1";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "Port";
            val = "8630";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup TableLockSaveFile
        section = "SystemConfig";
        parm = "TableLockSaveFile";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = IDBreadRegistry("") + "/dbrm/tablelocks";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup SystemModuleConfig
        section = "SystemModuleConfig";
        parm = "ModuleDBRootCount1-1";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "0";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "ModuleDBRootCount1-2";
            val = "0";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "ModuleDBRootCount1-3";
            val = "1";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "ModuleDBRootID1-1-1";
            val = "0";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "ModuleDBRootID1-1-2";
            val = "0";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
            parm = "ModuleDBRootID1-1-3";
            val = "1";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup NVBF
        section = "VersionBuffer";
        parm = "NumVersionBufferFiles";
        val = cf->getConfig(section, parm);

        if (!val.empty())
        {
            cf->delConfig(section, parm);
            cout << "Deleting " << section << "." << parm << endl;
        }

        //Fixup AllowDiskBasedJoin
        section = "HashJoin";
        parm = "AllowDiskBasedJoin";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "N"; // We default to No
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup TempFilePath
        section = "HashJoin";
        parm = "TempFilePath";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "$INSTALLDIR/tmp";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        //Fixup TempFileCompression
        section = "HashJoin";
        parm = "TempFileCompression";
        val = cf->getConfig(section, parm);

        if (val.empty())
        {
            val = "Y";
            cf->setConfig(section, parm, val);
            cout << "Adding " << section << "." << parm << " = " << val << endl;
        }

        cf->write();
        rc = 0;
    }
    catch (exception& e)
    {
        cout << "fixupCalpontXML caught exception: " << e.what() << endl;
    }
    catch (...)
    {
        cout << "fixupCalpontXML caught exception (...): " << endl;
    }

    return rc;
}

int fixupMyIni()
{
    return 0;
}

}

namespace bootstrap
{

int fixupConfig(const string& installDir, const string& mysqlPort)
{
    int rc = -1;

    string id = installDir;
    string::size_type p;
    p = id.find('\\');

    while (p != string::npos)
    {
        id[p] = '/';
        p = id.find('\\');
    }

    fs::path cFilePath;
    fs::path tmpPath;
    ifstream ifs;
    ofstream ofs;
    string strLine;
    bool okayToRename = false;

    cFilePath = installDir;
    cFilePath /= "etc";
    cFilePath /= "Columnstore.xml";
    tmpPath = cFilePath;
    tmpPath.replace_extension(".tmp");
    fs::remove(tmpPath);
    ifs.open(cFilePath.string().c_str());
    ofs.open(tmpPath.string().c_str());
    getline(ifs, strLine);

    while (ifs.good())
    {
        sedit(strLine, "##INSTDIR##", id);
        ofs << strLine << endl;
        getline(ifs, strLine);
    }

    if (!ifs.bad() && !ofs.bad())
        okayToRename = true;

    ofs.close();
    ifs.close();

    if (okayToRename)
    {
        try
        {
            fs::remove(cFilePath);
            fs::rename(tmpPath, cFilePath);
        }
        catch (exception& e)
        {
            cout << "Failed to remove " << cFilePath.c_str() << e.what() << endl;
        }
    }
    else
        return -1;

    ifs.clear();
    ofs.clear();

    okayToRename = false;
    cFilePath = installDir;
    cFilePath /= "my.ini";
    tmpPath = cFilePath;
    tmpPath.replace_extension(".tmp");
    fs::remove(tmpPath);
    ifs.open(cFilePath.string().c_str());
    ofs.open(tmpPath.string().c_str());
    getline(ifs, strLine);

    while (ifs.good())
    {
        if (strLine[0] != '#')
        {
            sedit(strLine, "##INSTDIR##", id);
            sedit(strLine, "##PORT##", mysqlPort);
        }

#ifndef SKIP_MYSQL_SETUP4
        sedit(strLine, "#infinidb_compression_type=0", "infinidb_compression_type=2");
        sedit(strLine, "infinidb_compression_type=1", "infinidb_compression_type=2");
#endif
        ofs << strLine << endl;
        getline(ifs, strLine);
    }

    if (!ifs.bad() && !ofs.bad())
        okayToRename = true;

    ofs.close();
    ifs.close();

    if (okayToRename)
    {
        try
        {
            fs::remove(cFilePath);
            fs::rename(tmpPath, cFilePath);
        }
        catch (boost::exception const& ex)
        {
            std::exception const* se = dynamic_cast<std::exception const*>(&ex);

            if (se)
            {
                // will enter here only for my application exception and not for pure boost exception
                cerr << se->what() << "my.ini not fixedup" << endl;
            }
            else
            {
                cerr << " Boost exception while renaming my.ini: Failed to decode the exception" << endl;
            }
        }
        catch (std::exception const& ex)
        {
            cerr << ex.what() << "my.ini not fixedup" << endl;
        }
        catch (...)
        {
            cerr << "An unknown exception (...) was caught while renaming my.ini" << endl;
        }
    }
    else
        return -1;

    ifs.clear();
    ofs.clear();

    // Add install timestamp to CalpontVersion.txt
    char szTime[24];
    time_t now = time(NULL);
    struct tm nowtm;
    localtime_s(&nowtm, &now);
    strftime(szTime, 24, "%Y-%m-%d %H:%M:%S", &nowtm);
    okayToRename = false;
    cFilePath = installDir;
    cFilePath /= "etc/CalpontVersion.txt";
    tmpPath = cFilePath;
    tmpPath.replace_extension(".tmp");
    fs::remove(tmpPath);
    ifs.open(cFilePath.string().c_str());
    ofs.open(tmpPath.string().c_str());
    getline(ifs, strLine);

    while (ifs.good())
    {
        sedit(strLine, "@@INSTALLDATE@@", szTime);
        ofs << strLine << endl;
        getline(ifs, strLine);
    }

    if (!ifs.bad() && !ofs.bad())
        okayToRename = true;

    ofs.close();
    ifs.close();

    if (okayToRename)
    {
        fs::remove(cFilePath);
        fs::rename(tmpPath, cFilePath);
    }
    else
        return -1;

    ifs.clear();
    ofs.clear();

    //Now do any final fixups...
    rc = fixupMyIni();

    if (rc == 0)
        rc = fixupCalpontXML();

    return rc;
}

}
