/*!
    @file           DBMCli_EventHandlerMainExtendDB.cpp
    @author         MarcW
    @brief          handles DB_ABOVE_LIMIT events with extending the DB - Implementation

\if EMIT_LICENCE

    ========== licence begin  GPL
    Copyright (c) 2003-2005 SAP AG

    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.
    ========== licence end


\endif
*/

/* 
  -----------------------------------------------------------------------------
  includes
  -----------------------------------------------------------------------------
*/
#include <signal.h>
#include <stdio.h>

#include "ToolsCommon/Tools_ParameterParser.hpp"
#include "ToolsCommon/Tools_Properties.hpp"
#include "SAPDB/DBM/Cli/DBMCli_EventHandler.hpp"
#include "SAPDB/DBM/Cli/DBMCli_EventHandlerExtendDB.hpp"

/*
  -----------------------------------------------------------------------------
  globals
  -----------------------------------------------------------------------------
*/
const SAPDB_Int DBMCli_EventHandlerExtendDB::ms_limitMax(95);
const SAPDB_Int DBMCli_EventHandlerExtendDB::ms_limitDefault(90);
const SAPDB_Int DBMCli_EventHandlerExtendDB::ms_limitMin(60);
//const SAPDB_Int DBMCli_EventHandlerExtendDB::ms_nSizeDefault(50);

const DBMCli_String DBMCli_EventHandlerExtendDB::ms_EventName("DB_ABOVE_LIMIT");
const DBMCli_String DBMCli_EventHandlerExtendDB::ms_AutoExtDir("AutoExtDir");
//const DBMCli_String DBMCli_EventHandlerExtendDB::ms_LimitKey("Limit");
//const DBMCli_String DBMCli_EventHandlerExtendDB::ms_SizeKey("PercentPerNewVolume");

const DBMCli_String DBMCli_EventHandlerExtendDB::sRundirectory("RUNDIRECTORY");
// default name of protocol file
const DBMCli_String DBMCli_EventHandlerExtendDB::sPrtfile("dbmevthndl_extendDB.prt");

DBMCli_EventHandlerExtendDB::DBMCli_EventHandlerExtendDB
        (int argc, char * argv[], SAPDBErr_MessageList& oMessageList)
            :m_nLimit(0),
             m_bVerbose(false),
             //m_nPercentPerNewVolume(0),
             m_Logger(tin100_GlobalLog::createObject()),
             m_doLogging(false),
             DBMCli_EventHandler(argc, argv, oMessageList) {

    // read necessary data from command line
    Tools_ParameterParser parser;
    DBMCli_String sName, sDesc;
    //limit
    sName = "l";
    sDesc = "limit";
    Tools_Parameter limit(sName, sDesc, false);
    //limit
    sName = "v";
    sDesc = "verbose";
    Tools_Parameter verbose(sName, sDesc, false, false);
    //limit
    sName = "lf";
    sDesc = "logfile";
    Tools_Parameter logfile(sName, sDesc, false);

    parser.addFormalParameter(limit);
    parser.addFormalParameter(verbose);
    parser.addFormalParameter(logfile);
    parser.setActualParameters(argc, argv);

    // verbosity
    m_bVerbose = parser.isParameterSet(verbose.getName());

    // prepare logfile
    if( m_Logger != NULL ) {
        DBMCli_String aLogfile(logfile.getValue());
        if( aLogfile.GetLength() <= 0 ) {
            // no logfile name passed
            // use default logfile in db's rundirectory
            DBMCli_Parameters params(pDatabase->GetParameters());
            SAPDBErr_MessageList msgList;
            pDatabase->NodeInfo().Refresh(msgList);
            const char cSlash((pDatabase->NodeInfo().OS().Compare("UNIX") == 0) ? '/' : '\\');
            aLogfile = params.GetValueAsString(DBMCli_EventHandlerExtendDB::sRundirectory)
                       + cSlash
                       + DBMCli_EventHandlerExtendDB::sPrtfile;
        }
        // prepare logger and logfile
        tsp00_Pathc szPath;
        szPath.rawAssign((const char*) aLogfile);
        tin105_SequentialLogFile* logFile =
            new tin105_SequentialLogFile(szPath, LOG_ALL_MIN105);
        if( logFile != NULL ) {
            m_Logger->addFile(logFile, true);
            // just do the logging, if the logfile works file
            m_doLogging = logFile->checkFile();
        }
    }

    // get limit
    DBMCli_String sLimit(limit.getValue());
    m_nLimit = atoi((const char*)sLimit);
    // if no limit is passed or not between min and max, we use default
    if( m_nLimit < ms_limitMin || m_nLimit > ms_limitMax )
        m_nLimit = ms_limitDefault;

    if( m_bVerbose ) {
        char intBuf[32];
        sprintf(intBuf, "%d", m_nLimit);
        // info message
        dumpText("Using limit %s%%", intBuf);
    }

/*  not used
    // load properties from config file that we got on command line
    Tools_Properties props;
    Tools_DynamicUTF8String cfg((const char*) aCfgfile);
    props.load(cfg);
    

    Tools_DynamicUTF8String limitkey((const char*)ms_LimitKey);
    char buf[128];
    sprintf(buf, "%d", 62);
    Tools_DynamicUTF8String limitval(buf);
    props.setProperty(limitkey, limitval);

    Tools_DynamicUTF8String sizekey((const char*)ms_SizeKey);
    sprintf(buf, "%d", 10);
    Tools_DynamicUTF8String sizeval(buf);
    props.setProperty(sizekey, sizeval);

    limitval = props.getProperty(limitkey);
    m_nLimit = atoi((const char*)limitval.StrPtr());
    if( m_nLimit < ms_limitMin || m_nLimit > ms_limitMax )
        m_nLimit = ms_limitDefault;

    sizeval = props.getProperty(sizekey);
    m_nPercentPerNewVolume = atoi((const char*)sizeval.StrPtr());
    if( m_nPercentPerNewVolume < 1 || m_nPercentPerNewVolume > 100 )
        m_nPercentPerNewVolume = ms_nSizeDefault;
*/
}

DBMCli_EventHandlerExtendDB::~DBMCli_EventHandlerExtendDB() {
    delete m_Logger;
}

void DBMCli_EventHandlerExtendDB::HandleEvent() {
    if( !eventReceived() ) {
        dumpText("no event received from event dispatcher.");
        return;
    }

    if( pEvent->GetName() != ms_EventName ) {
        dumpText("event \"%s\" received from event dispatcher, but not handled", (const char*)pEvent->GetName());
        return;
    }

    // connect to database
    if( !pDatabase->IsConnected() ) {
        m_oMessageList.ClearMessageList();
        if( !pDatabase->Connect(m_oMessageList) ) {
            dumpError(__LINE__, __FILE__, "could not connect to DB.");
        }
    }
    
    bool addIt(true);
    // keep adding volumes until filling is below desired limit
    while( addIt && (DBMCLI_DBEXTSTATE_CANADD == checkDB()) ) {
        // use utility session to synchronize DB extenders (possible if
        // db is filled very quickly...)
        if( addIt = pDatabase->UTLConnect(m_oMessageList) ) {
            addIt = addVolume();
            m_oMessageList.ClearMessageList();
            if( !pDatabase->UTLRelease(m_oMessageList) ) {
                dumpError(__LINE__, __FILE__, "could not release utility session.");
            }
        }
    }   
    if( !addIt ) {
        dumpError(__LINE__, __FILE__, "could not add new volume.");
    }
}

// add new volume
bool DBMCli_EventHandlerExtendDB::addVolume() {

    DBMCli_Config    & oConfig    = pDatabase->GetConfig();
    DBMCli_String   sLocation;
    DBMCli_String   sName;
    DBMCli_String   sPath;
    char            szNumber[20];
    char            szNewNumber[20];
    char            szPages[20];
    char            cSlash = (pDatabase->NodeInfo().OS().Compare("UNIX") == 0) ? '/' : '\\';
    SAPDB_Int       nPos   = 0;
    bool success(true);

    // is there an autoextend dir?
    oConfig.GetRawValue(ms_AutoExtDir, sPath, m_oMessageList);
  
    m_oMessageList.ClearMessageList(); 

    if ( success = pDatabase->GetDevspaces().Refresh(m_oMessageList) ) {
        DBMCli_DevspaceArray& oDevArray = pDatabase->GetDevspaces().DevspaceArray();
        // get location of last datavolume
        sLocation = oDevArray[oDevArray.GetSize() - 1].Location();

        SAPDB_Int nPages;

/* not used for now
        // calculate pages for new data volume
        DBMCli_State& oState = pDatabase->GetState();
        m_oMessageList.ClearMessageList();
        if( oState.Refresh(m_oMessageList) ) {
            nPages = oState.DataMaxPages();
            //printf("Datapages returned %d.\n", (int)nPages);
            nPages = (SAPDB_Int)(((float)nPages) * (((float)m_nPercentPerNewVolume)/100.0));
            //printf("will add %d.\n", (int)nPages);
        }
        else {
            dumpError("could not calculate pages, adding 1000.", __FILE__, __LINE__);
            nPages = 1000;
        }
*/
        // new volume will have same size as predecessing volume
        nPages = (SAPDB_Int) oDevArray[oDevArray.GetSize()-1].Pages();

        // extract volume name 
        nPos = sLocation.ReverseFind(cSlash);

        if (nPos >= 0) {
            if (sPath.GetLength() == 0) {
                sPath = sLocation.Left(nPos);
            } // end if
            sName = sLocation.Mid(nPos + 1);
        }
        else {
            sName = sLocation;
        } // end if

        sLocation = sPath;
        if (sLocation.GetLength() > 0) {
            sLocation = sLocation + DBMCli_String(cSlash);
        } // end if

        sprintf(szNumber,    "%d", (int) pDatabase->GetDevspaces().UsedDataDevs());
        sprintf(szNewNumber, "%d", (int) pDatabase->GetDevspaces().UsedDataDevs() + 1);
        nPos = sName.Find(szNumber);
        if (nPos > -1) {
            sLocation = sLocation + sName.Left(nPos);
            sLocation = sLocation + szNewNumber;
            sLocation = sLocation +  sName.Right(sName.GetLength() - nPos - (int) strlen(szNumber));
        } 
        else {
            sLocation = sLocation + sName;
            sLocation = sLocation + ".";
            sLocation = sLocation + szNewNumber;
        } // end if

        // now we have the absolute volume name (path and name) -> add the volume
        DBMCli_String sCommand;

        sprintf(szPages,"%d", nPages);
        sCommand = "db_addvolume DATA " + sLocation + " F " + szPages;

        // do it
        if( success = pDatabase->Execute(sCommand, m_oMessageList) ) {
            dumpText("New data volume added!");
            dumpText("  Number:   %s", szNewNumber);
            dumpText("  Location: %s", (const char *) sLocation);
            dumpText("  Pages:    %s\n", szPages);
        }
        else {
            dumpError(__LINE__, __FILE__, "error executing command \"%s\".", (const char*) sCommand);
        }
    }
    else {
        dumpError(__LINE__, __FILE__, "could not get data volumes info.");
    }
    return success;
}

// check database filling with sql
DBMCli_DBExtState DBMCli_EventHandlerExtendDB::checkDB() {
    static char intBuf[32];

    DBMCli_State& oState = pDatabase->GetState();
    m_oMessageList.ClearMessageList();
    oState.Refresh(m_oMessageList);
    if (m_oMessageList.IsEmpty() && oState.Value() == DBMCLI_DBSTATE_WARM) {
        sprintf(intBuf, "%d", (int)oState.DataPercent());
        dumpText("SQL check: actual filling is %s%%.", intBuf);
        if( oState.DataPercent() <= m_nLimit )
            return DBMCLI_DBEXTSTATE_NOTNEC;
        else
            return DBMCLI_DBEXTSTATE_CANADD;
    }
    else {
        dumpText("DB is not online.");
        return DBMCLI_DBEXTSTATE_NOTONL;
    }
}

void DBMCli_EventHandlerExtendDB::dumpError(const int line,
                                            const char* file, 
                                            const char* format,
                                            const char* s0,
                                            const char* s1,
                                            const char* s2,
                                            const char* s3,
                                            const char* s4 ) const  {
    static char newFormat[128];
    static char intBuf[32];

    sprintf(newFormat, "At file %s, line %d:\n%s\n", file, line, format);
    fprintf(stderr, newFormat, s0, s1, s2, s3, s4);
    if( m_doLogging ) {
        sprintf(intBuf, "%d", line);
        m_Logger->tin100_Log::writeEntry("Error at file %s, line %s:", file, intBuf);
    }


    if( !m_oMessageList.IsEmpty() ) {
        SAPDBErr_MessageList* pMsg(&m_oMessageList);
        while( pMsg != NULL ) {
            fprintf(stderr,"%d, %s\n", (int)pMsg->ID(), pMsg->Message());                
            if( m_doLogging ) {
                sprintf(intBuf, "%d", (int)pMsg->ID());
                m_Logger->tin100_Log::writeEntry("  %s, %s", intBuf, pMsg->Message());
            }
            pMsg = pMsg->NextMessage();
        }
    }
}


void DBMCli_EventHandlerExtendDB::dumpText( const char* format,
                                            const char* s0,
                                            const char* s1,
                                            const char* s2,
                                            const char* s3,
                                            const char* s4 ) const  {
    if( !m_bVerbose )
        return;
                                                
    static char newFormat[128];
    static char intBuf[32];
    sprintf(newFormat, "%s\n", format);
    fprintf(stdout, newFormat, s0, s1, s2, s3, s4);
    if( m_doLogging ) {
        m_Logger->tin100_Log::writeEntry(format, s0, s1, s2, s3, s4);
    }

    if( !m_oMessageList.IsEmpty() ) {
        SAPDBErr_MessageList* pMsg(&m_oMessageList);
        while( pMsg != NULL ) {
            fprintf(stdout,"%d, %s\n", (int)pMsg->ID(), pMsg->Message());                
            if( m_doLogging ) {
                sprintf(intBuf, "%d", (int)pMsg->ID());
                m_Logger->tin100_Log::writeEntry("  %s, %s", intBuf, pMsg->Message());
            }
            pMsg = pMsg->NextMessage();
        }
    }
}
