#include <G4UIdirectory.hh>
#include <G4StateManager.hh>

#include <RAT/Log.hh>
#include <RAT/DBMessenger.hh>
#include <RAT/DB.hh>
#include <RAT/DBCommandLoader.hh>
#include <RAT/MetaInformation.hh>

#include <RAT/GLG4StringUtil.hh>
using namespace RAT;

#include <string>
using std::string;

void DBMessenger::Init(DB *dbToUse)
{
  fDb = dbToUse;
  // Build UI commands
  G4UIdirectory* dbdir = new G4UIdirectory("/rat/db/");
  dbdir->SetGuidance("RATDB commands");

  // load database file command
  fLoadCmd = new G4UIcmdWithAString("/rat/db/load", this);
  fLoadCmd->SetParameterName("filename", false); // required
  fLoadCmd->SetGuidance("Load a file into the database");

  // set database attribute
  fSetCmd = new G4UIcommand("/rat/db/set", this);
  G4UIparameter *aParam;
  aParam = new G4UIparameter("table", 's', false); //required
  fSetCmd->SetParameter(aParam);
  aParam = new G4UIparameter("field", 's', false); //required
  fSetCmd->SetParameter(aParam);
  aParam = new G4UIparameter("newvalue", 's', false); //required
  fSetCmd->SetParameter(aParam);

  // connect to server command
  serverCmd = new G4UIcmdWithAString("/rat/db/server", this);
  serverCmd->SetParameterName("server_url", false); // required
  serverCmd->SetGuidance("Connect to CouchDB database");

  // allow self to set uninitialised values in tables
  fAllowDangerCmd = new G4UIcommand("/rat/db/allowdanger", this);
  G4UIparameter *anotherParam;
  anotherParam = new G4UIparameter("allow", 's', false); //required
  fAllowDangerCmd->SetParameter(anotherParam);

  fSetDBEntryPassCmd = new  G4UIcommand("/rat/db/set_pass", this);
  G4UIparameter *yaParam = new G4UIparameter("type[index]",'s',true);
  fSetDBEntryPassCmd->SetGuidance("Set the pass number that should be loaded for specific types and index of RATDB objects.");
  fSetDBEntryPassCmd->SetParameter(yaParam);
  yaParam = new G4UIparameter("pass",'i',true);
  fSetDBEntryPassCmd->SetParameter(yaParam);

  fAddTblNoDefaultPlane = new G4UIcmdWithAString("/rat/db/disallow_default_plane", this);
  fAddTblNoDefaultPlane->SetParameterName("table", true); // required
  fAddTblNoDefaultPlane->SetGuidance("Set table to not fetch from the default plane and instead throw an exception.");

  fRmTblNoDefaultPlane = new G4UIcmdWithAString("/rat/db/allow_default_plane", this);
  fRmTblNoDefaultPlane->SetParameterName("table", true); // required
  fRmTblNoDefaultPlane->SetGuidance("Remove table from default plane lock.");

  fRmAllTblNoDefaultPlane = new G4UIcmdWithABool("/rat/db/allow_all_default_plane", this);
  fRmAllTblNoDefaultPlane->SetParameterName("status",false);
  fRmAllTblNoDefaultPlane->SetGuidance("Bypass default plane lock for all tables.");

  fSetRATDBTag = new G4UIcmdWithAString("/rat/db/set_tag", this);
  fSetRATDBTag->SetParameterName("tag", true); // required
  fSetRATDBTag->SetGuidance("Set tag for use in RATDB.");

  fAirplaneMode = new G4UIcmdWithABool("/rat/db/airplane_mode",this);
  fAirplaneMode->SetParameterName("status",true);
  fAirplaneMode->SetGuidance("Activate airplane mode (bypass remote database connection.");
}

DBMessenger::~DBMessenger()
{
  delete fLoadCmd;
  delete fSetCmd;
  delete fAllowDangerCmd;
  delete fSetDBEntryPassCmd;
  delete fAddTblNoDefaultPlane;
  delete fRmTblNoDefaultPlane;
  delete fRmAllTblNoDefaultPlane;
  delete fSetRATDBTag;
  delete fAirplaneMode;
}

G4String DBMessenger::GetCurrentValue(G4UIcommand * /*unused*/)
{
  warn << "DBMessenger::GetCurrentValue: Get current value not implemented\n";
  return "";
}


void DBMessenger::SetNewValue(G4UIcommand * command, G4String newValue)
{
  G4ApplicationState state = G4StateManager::GetStateManager()->GetCurrentState();
  if (state != G4State_PreInit)
    Log::Die("Error: Cannot call " + command->GetCommandPath()
             + " after /run/initialize.");

  if( command == fLoadCmd )
    {
      fDb->Load( newValue, true ); // Prints the path
      MetaInformation::Get()->AddLoadCommand( newValue );
    }
  else if( command == fSetCmd )
    {
      // The value is after the second space
      const size_t tableFieldIndex = newValue.find( " ", newValue.find( " " ) + 1 );
      const string tableField = newValue.substr( 0, tableFieldIndex );
      const string value = newValue.substr( tableFieldIndex );
      DBCommandLoader::LoadCommand( tableField, value );
      MetaInformation::Get()->AddOverrideCommand( tableField, value );
    }
  else if (command == serverCmd) {
    Server(newValue);
  } else if (command == fAllowDangerCmd) {
    AllowDanger(newValue);
  } else if  (command == fSetDBEntryPassCmd) {
    // First strip the field from the pass
    std::vector<std::string> parts = util_split(newValue, " ");
    if (parts.size() != 2) {
      Log::Die(dformat("DBMessenger::SetNewValue : Invalid input in pass number setting. Got [%s], expected (type[index] pass)",newValue.c_str()));
    }
    std::string type, index;
    if (fDb->ParseTableName(parts.at(0),type,index)) {
      fDb->SetPassDBEntry(type,index,util_to_int(parts.at(1),0));
    } else {
      Log::Die(dformat("DBMessenger::SetNewValue : Invalid input in pass number setting. Got [%s], expected (type[index] pass)",newValue.c_str()));
    }
  } else if  (command == fAddTblNoDefaultPlane) {
    // Push it to RAT::DB object
    fDb->AddTblNoDefaultPlane(newValue);
  } else if (command == fRmTblNoDefaultPlane) {
    fDb->RemoveTblNoDefaultPlane(newValue);
  } else if (command == fRmAllTblNoDefaultPlane) {
    fDb->SetDefaultPlaneLockStatus(fRmAllTblNoDefaultPlane->GetNewBoolValue(newValue));
  } else if  (command == fSetRATDBTag) {
    // Push it into RAT::DB object
    fDb->SetDbTag(newValue);
  } else if (command == fAirplaneMode) {
    fDb->SetAirplaneModeStatus(fAirplaneMode->GetNewBoolValue(newValue));
  } else {
    warn << "DBMessenger::SetNewValue: Invalid DB command" << newline;
  }
}

void DBMessenger::AllowDanger(std::string allow){
  if(allow == "0")return;
  if(allow== "secretcode"){
    fDb->AllowDanger();
    return;
  }
  Log::Die("Error parsing \"AllowDanger\" parameter: `" + allow +
  "'. You didn't read the manual, did you???  Naughty.");
}

void DBMessenger::Server(std::string url)
{
  info << "RATDB: Connecting to PgSQL server at " << url << "\n";

  fDb->SetServer(url);
}