/*=================================================================
   Copyright (C) 2016 BizStation Corp All rights reserved.

   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; either version 2
   of the License, or (at your option) any later version.

   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., 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.
=================================================================*/
#pragma hdrstop
#include <global/replication/haCommand.h>
#include <locale.h>
#include <iostream>
#include <vector>
#include <boost/program_options.hpp>
#include <bzs/rtl/stl_uty.h>
#include <bzs/db/protocol/tdap/btrDate.h>

#ifdef __BCPLUSPLUS__
#define BZS_LINK_BOOST_PROGRAM_OPTIONS
#include <bzs/env/boost_bcb_link.h>
#endif

using namespace bzs::db::protocol::tdap::client;
using namespace bzs::db::protocol::tdap;
using namespace boost::program_options;
using namespace std;

static const char* cmds = "switchover,failover,demote_to_slave,"
                            "set_failover_enable,set_server_role,health_check";
#define CMD_SWITCHOVER            0
#define CMD_FAILOVER              1
#define CMD_DEMOTE_TO_SLAVE       2
#define CMD_SET_FAILOVER_ENABLE   3
#define CMD_SET_SERVER_ROLE       4
#define CMD_HEALTH_CHECK          5


_tstring str_conv(std::string& v)
{
#ifdef _UNICODE
    wchar_t wbuf[1024];
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, v.c_str(), -1, wbuf, 1024);
    return wbuf;
#else
    return v;
#endif //_UNICODE
}

class haMgrNotify : public haNotify
{
    _tstring m_host; 
public:
    virtual ~haMgrNotify() {};
    void onUpdateStaus(int status, const _TCHAR* msg)
    {
        const _TCHAR* p=_T("");
        switch(status)
        {
        case HA_NF_ROLE_SLAVE: p = _T(": set role=SLAVE ");break;
        case HA_NF_CANNELNAME: p = _T(": channel name=");break;
        case HA_SLAVE_STOP_ALL: p = _T(": stop slave all ");break;
        case HA_CHANGE_MASTER: p = _T(": change master to new master, pos=");break;
        case HA_SWITCH_MASTER: p = _T(": change master to new master, pos=");break;
        case HA_SLAVE_START: p = _T(": start slave ");break;
        case HA_NF_WAIT_TRX_START: p = _T(": waiting for trx...");break;
        case HA_NF_WAIT_TRX_COMP: p = _T(": wait is completed, pos=");break;
        case HA_NF_SLAVE_LIST: p = _T("SLAVE_LIST=");break;
        case HA_NF_PROMOTE_MASTER: p = _T(": promote to master ");break;
        case HA_NF_PROMOTE_CHANNEL: p = _T(": channel name=");break;
        case HA_NF_ROLE_MASTER: p = _T(": set role=MASTER ");break;
        case HA_NF_WAIT_POS_START: p = _T(": waiting for until...");break;
        case HA_NF_WAIT_POS_COMP: p = _T(": wait is completed, pos=");break;
        case HA_SLAVE_STOP: p = _T(": stop slave ");break;
        case HA_SET_READ_ONLY: p = _T(": set READ_ONLY=1");break;
        case HA_NF_DELAY: p = _T(": SQL thread delay=");break;
        case HA_NF_MSG_OK:  
            tcout << "  " << m_host << _T(": ") << msg << _T(" OK!") << endl; 
            return;
        case HA_NF_MSG_NG:  
            tcout << "  " << m_host << _T(": ") << msg << _T(" NG!") << endl; 
            return;
        }
        tcout << "  " << m_host << p << msg << endl;
    }
    void setHostName(const _TCHAR* host)
    {
        m_host = host;
    }
};

int checkParam(failOverParam& pm, int cmd, int v)
{
    if (cmd != CMD_FAILOVER)
    {
        if (pm.master.host == _T(""))
        {
            cout << "--cur_master is requered" << endl;
            return 1;
        }
    }else
    {
        if (pm.slaves == _T(""))
        {
            cout << "--slaves is requered" << endl;
            return 1;
        }
    }
        
    if (cmd == CMD_SWITCHOVER || cmd == CMD_DEMOTE_TO_SLAVE)
    {
        if (pm.newMaster.host == _T(""))
        {
            cout << "--new_master is requered" << endl;
            return 1;
        }
        if (pm.newMaster.repUser == "")
        {
            cout << "--repl_user is requered" << endl;
            return 1;
        }
        if (pm.newMaster.repPasswd == "")
        {
            cout << "--repl_passwd is requered" << endl;
            return 1;
        }
    }
    return 0;
}

int getCommandLineOption(int argc, _TCHAR* argv[], failOverParam& pm, int& cmd, int& v)
{
    std::string c, host, newMaster, user, pwd, slaves; 
    std::string port = "3306"; 
    bool readonly = false;
    bool disable_demote = false;
    options_description opt("command line option");
    opt.add_options()("command,c", value<std::string>(&c),
                      "command [switchover | failover | demote_to_slave | set_failover_enable | set_server_role | health_check]")
        ("cur_master,o", value<std::string>(&host), "current master host name")
        ("new_master,n", value<std::string>(&newMaster), "new master host name")
        ("channel,C", value<std::string>(&pm.newMaster.channel), "new master channel name")
        ("repl_port,P", value<std::string>(&pm.newMaster.repPort), "new master port")
        ("repl_user,r", value<std::string>(&pm.newMaster.repUser), "new master repl user ")
        ("repl_passwd,d", value<std::string>(&pm.newMaster.repPasswd), "new master repl password ")
        ("repl_option,O", value<std::string>(&pm.newMaster.repOption),  "option params for change master(ex: MASTER_CONNECT_RETRY=30)")
        ("slaves,s", value<std::string>(&slaves), "slave list for failover")
        ("portmap,a", value<std::string>(&pm.portMap), "port map ex:3307:8611")
        ("value,v", value<int>(&v), "value (For set_failover_enable or set_server_role)")
        ("username,u", value<std::string>(&user), "transactd username")
        ("password,p", value<std::string>(&pwd),  "transactd password")
        ("readonly,R", value<bool>(&readonly),  "0 | 1: When it is 1, the READONLY variable will be set ON to slaves and OFF to a master")
        ("disable_demote,D", value<bool>(&disable_demote),  "0 | 1: disable old master demote");
    variables_map values;
    store(parse_command_line(argc, argv, opt), values);
    notify(values);
    if (!values.count("command"))
    {
        cout << opt << endl;
        return 1;
    }
    vector<string> cmdList;
    split(cmdList, cmds, ",");
    vector<string>::iterator it = find(cmdList.begin(), cmdList.end(), c);
    if (it== cmdList.end())
    {
        cout << opt << endl;
        return 1;
    }
    cmd = (int)(it - cmdList.begin());
    if ((cmd == CMD_SET_SERVER_ROLE || cmd == CMD_SET_FAILOVER_ENABLE) &&
            !values.count("value"))
    {
        cout << opt << endl;
        return 1;
    }
    pm.newMaster.host = str_conv(newMaster);
    pm.master.host = str_conv(host);
    pm.master.user = str_conv(user);
    pm.master.passwd = str_conv(pwd);
    pm.slaves = str_conv(slaves);
    if (readonly) pm.option |= OPT_READONLY_CONTROL;
    if (disable_demote) pm.option |= OPT_DISABLE_OLD_TO_SLAVE;
    return checkParam(pm, cmd, v);
}

void printDateTime()
{
    btrDateTime bt;
    char tmp[256];
    bt.time.i = getNowTime();
    bt.date.i = getNowDate();
    btrstoa(bt, tmp, true);
    cout << tmp;
}

void printMessage(const char* msg, const char* msg2="")
{
    printDateTime();
    cout << " " << msg << msg2 << endl;
}

#ifdef _UNICODE
void printMessage(const wchar_t* msg, const wchar_t* msg2=_T(""))
{
    printDateTime();
    tcout << " " << msg << msg2 << endl;
}
#endif

/* Command line paramater 
command line option:
  -c [ --command ]         command [switchover | failover | demote_to_slave | set_failover_enable | set_server_role | health_check]
  -o [ --cur_master ]      current master host name
  -n [ --new_master ]      new master host name
  -C [ --channel ]         new master channel name
  -P [ --repl_port ]       new master port
  -r [ --repl_user ]       new master repl user
  -d [ --repl_passwd ]     new master repl password
  -O [ --repl_option ]     option params for change master(ex:MASTER_CONNECT_RETRY=30)
  -s [ --slaves ]          slave list for failover
  -a [ --portmap ]         port map ex:3307:8611
  -v [ --value ]           value (For set_failover_enable or set_server_role)
  -u [ --username ]        transactd username
  -p [ --password ]        transactd password
  -R [ --readonly ]        0 | 1: When it is 1, the READONLY variable will be set ON to slaves and OFF to a master
  -D [ --disable_demote ]  0 | 1: disable old master demote

example:
haMgr64 -c switchover -o localhost -n localhost:8611 -P 3307 -r replication_user -d xxxx -u root -p xxxx 

haMgr64 -c failover -s localhost:8611,localhost:8612 -a 8610:3306,8611:3307,8612:3308
 -u root -p

haMgr64 -c demote_to_slave -o localhost -n localhost:8611 -P 3307 -r replication_user -d xxxx
 -d abcd -u root -p 

haMgr64 -c set_failover_enable -v 1 -o localhost -u root -p

haMgr64 -c set_server_role -o localhost -v 1 -u root -p

haMgr64 -c health_check -o localhost -s localhost:8611,localhost:8612 -u root -p

*/

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _WIN32
    // set locale to current user locale.
    std::locale::global(std::locale(""));
#endif
    int ret = 1;
    try
    {
        failOverParam pm;
        int cmd = 0;
        int v = 0;
        ret = getCommandLineOption(argc, argv, pm, cmd, v);
        if (ret == 0)
        {
            haMgrNotify nf;
            switch (cmd)
            {
            case CMD_SWITCHOVER: 
                pm.option |= OPT_SO_AUTO_SLVAE_LIST;
                printMessage("Starting switch over...");
                switchOrver(pm, &nf); 
                break;
            case CMD_FAILOVER:
                printMessage("Starting fail over...");
                failOrver(pm, &nf); 
                break;
            case CMD_DEMOTE_TO_SLAVE:  
                printMessage("Starting demote to slave...");
                demoteToSlave(pm, &nf); 
                break;
            case CMD_SET_FAILOVER_ENABLE: 
                pm.option |= OPT_SO_AUTO_SLVAE_LIST;
                setEnableFailOver(pm, v == 1); 
                break;
            case CMD_SET_SERVER_ROLE:  
                setServerRole(pm, v); 
                break;
            case CMD_HEALTH_CHECK:
                printMessage("Starting health check...");
                int errors = healthCheck(pm, &nf);
                char tmp[256];
                if (errors)
                    sprintf_s(tmp, 256, "%d errors detected.", errors);
                else
                    sprintf_s(tmp, 256, "No errors detected.");
                cout << endl << "  *** " << tmp << endl << endl;
                ret = errors;
                break;
            }
            printMessage("Done!");
        }
    }
    catch (bzs::rtl::exception& e)
    {
        printMessage(_T("Error! "), getMsg(e)->c_str());
        ret = 1;
    }
    catch (std::exception& e)
    {
        printMessage("Error! ", e.what());
        ret = 1;
    }
    return ret;
}
//---------------------------------------------------------------------------