/*!
  @file           DBMCli_AutoExtendMain.cpp
  @author         Bernd Vorsprach - bernd.vorsprach@sap.com
  @brief          Main program for DBM Auto Add Volume program

\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 <stdio.h>
#include <signal.h>

#include "SAPDB/DBM/Cli/DBMCli_Logo.hpp"
#include "SAPDB/DBM/Cli/DBMCli_EventListener.hpp"

/* 
  -----------------------------------------------------------------------------
  private macros
  -----------------------------------------------------------------------------
*/
#define EVT_DB_ABOVE_LIMIT "DB_ABOVE_LIMIT"
#define EVT_ALIVE          "ALIVE"

#define CFG_AUTOEXTFLAG    "AutoExtFlag"
#define CFG_AUTOEXTDIR     "AutoExtDir"

#define VAL_YES            "YES"

#define LIMIT_MAX          95
#define LIMIT_DEFAULT      90
#define LIMIT_MIN          60


/* 
  -----------------------------------------------------------------------------
  private classes
  -----------------------------------------------------------------------------
*/
class MyEventListener : public DBMCli_EventListener
{

public:

  /*! @brief constructor  */
  MyEventListener
    ( const DBMCli_String  & sServer,
      const DBMCli_String  & sDatabase,
      const DBMCli_String  & sUserPwd,
      SAPDBErr_MessageList & oMessage,
      SAPDB_Int              nLimitIn)
      : DBMCli_EventListener(sServer, sDatabase, sUserPwd, oMessage) 
  {
    this->nLimit = nLimitIn;
  } // end MyEventListener

  /*! @brief call back function   */
  bool EventProc ( const DBMCli_Event & oEvent )
    {
      bool bReturn = true;
      bool bAdd    = false;

      if (strcmp((const char *) oEvent.GetName(), EVT_DB_ABOVE_LIMIT) == 0) {

        // check the limit 
        if (oEvent.GetValue2() >= this->nLimit) {
          bAdd = true;

          // check database filling with sql
          DBMCli_State & oState = this->GetState();
          oState.Refresh(this->GetMsgObj());
          if (this->LastMessage().IsEmpty() && oState.Value() == DBMCLI_DBSTATE_WARM) {

            if (oState.DataPercent() < this->nLimit) {
              bAdd = false;
            } // end if
          } // end if

          if (bAdd) {
            bReturn = this->AddVolume();
          } // end if

        } // end if

      } // end if

      return bReturn;
    } // end EventProc

  /*! @brief call back function   */
  bool AddVolume ( )
  {
    DBMCli_Config    & oConfig    = this->GetConfig();

    DBMCli_String   sLocation;
    DBMCli_String   sName;
    DBMCli_String   sPath;
    char            szNumber[20];
    char            szNewNumber[20];
    char            szPages[20];
    char            cSlash = (this->NodeInfo().OS().Compare("UNIX") == 0) ? '/' : '\\';
    SAPDB_Int       nPos   = 0;

    // is there e autoextend dir?
    oConfig.GetRawValue(CFG_AUTOEXTDIR, sPath, this->GetMsgObj());
    // ignore errors
    this->GetMsgObj().ClearMessageList(); 

    // refresh devspaces
    this->GetDevspaces().Refresh(this->GetMsgObj());

    if (this->GetMsgObj().IsEmpty()) {
      DBMCli_DevspaceArray & oDevArray = this->GetDevspaces().DevspaceArray();

      sLocation = oDevArray[oDevArray.GetSize() - 1].Location();

      // 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) this->GetDevspaces().UsedDataDevs());
      sprintf(szNewNumber, "%d", (int) this->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 I have it -> add the volume
      DBMCli_String sCommand;

      sprintf(szPages,"%d", (int) oDevArray[oDevArray.GetSize() - 1].Pages());
      sCommand = "db_addvolume DATA " + sLocation + " F " + szPages;

      this->Execute(sCommand, this->GetMsgObj());

      if (this->LastMessage().IsEmpty()) {
        printf("New data volume added!\n");
        printf("  Number:   %s\n", szNewNumber);
        printf("  Location: %s\n", (const char *) sLocation);
        printf("  Pages:    %s\n\n", szPages);
      } // end if

    } // end if

    return this->LastMessage().IsEmpty();
  } // end AddVolume

  /*! @brief limit */
  SAPDB_Int nLimit;

}; // end class MyEventListener

MyEventListener * pListener = NULL;

/* 
  -----------------------------------------------------------------------------
  private declarations
  -----------------------------------------------------------------------------
*/
#define C_SWITCH         '-'
#define SW_NODE          "n"
#define SW_DATABASE      "d"
#define SW_USER          "u"
#define SW_LIMIT         "l"
#define SW_NOLOGO        "nologo"
#define SW_VERSION       "v"

#define PRG_FATAL        1001
#define PRG_VERYFATAL    1002

#define DBM_OK           "OK"
#define DBM_END          "END"

class tOptions {
public:
    tOptions() 
    {
       szApp    = "";
       szDB     = "";
       szUser   = "";
       szNode   = "";
       bLogo    = true;
       bVersion = false;
       nLimit   = LIMIT_DEFAULT;
    }
    const char *   szApp;
    const char *   szDB;
    const char *   szUser;
    const char *   szNode;
    bool           bLogo;
    bool           bVersion;
    int            nLimit;
};

/* 
  -----------------------------------------------------------------------------
  function: abortHandler
  -----------------------------------------------------------------------------
*/
extern "C" void abortHandler(int sig) {

  switch (sig) {
    case SIGINT:
    case SIGTERM:
    case SIGABRT:
      if (pListener != NULL) {
        delete pListener;
      } // end if
      exit(0);
      break;

    default:
      break;
  } // end switch
} // end abortHandler

/* 
  -----------------------------------------------------------------------------
  function: FatalError
  -----------------------------------------------------------------------------
*/
static void FatalError
      ( const char * errmsg,
        int          nExitCode )
{
    fprintf (stderr, "%s\n", errmsg);
    exit (nExitCode);
} // end FatalError

/* 
  -----------------------------------------------------------------------------
  function: Usage
  -----------------------------------------------------------------------------
*/
static void Usage 
      (tOptions * pOpt)
{
  fprintf (stderr, "usage: %s [<options>]\n", pOpt->szApp);
  fprintf (stderr, "  <options>:\n");
  fprintf (stderr, "    -%-4s dbname   (name of datbase)                \n", SW_DATABASE);
  fprintf (stderr, "    -%-4s user,pwd (user for authorization)         \n", SW_USER);
  fprintf (stderr, "    -%-4s node     (name of servernode)             \n", SW_NODE);
  fprintf (stderr, "    -%-4s value    (limit between 60% and 95%)      \n", SW_LIMIT);
  fprintf (stderr, "    -%-4s          (without logo))                  \n", SW_NOLOGO);
  fprintf (stderr, "    -%-4s          (print version)                  \n", SW_VERSION);
  FatalError("", PRG_FATAL);
} /* end Usage */

/* 
  -----------------------------------------------------------------------------
  function: ReadNextArg
  -----------------------------------------------------------------------------
*/
static char * ReadNextArg
      ( char * szArgs[],
        int  & nCurArg,
        int    nMaxArgs,
        bool   bSwitch = false)
{
  int    nCurrent  = nCurArg + 1;
  char * pArgument = NULL;

  if (nCurrent < nMaxArgs) {
    if (bSwitch && szArgs[nCurrent][0] == C_SWITCH) {
      pArgument = &(szArgs[nCurrent][1]);
      nCurArg   = nCurrent;
    } else if (!bSwitch && szArgs[nCurrent][0] != C_SWITCH) {
      pArgument = szArgs[nCurrent];
      nCurArg   = nCurrent;
    } // end if
  } // end if

  return pArgument;
} // end ReadNextArg

/* 
  -----------------------------------------------------------------------------
  function: ParseOptions
  -----------------------------------------------------------------------------
*/
static void ParseOptions 
      ( tOptions       * pOpt, 
        int                  argc, 
        char               * argv[] )
{
  int    argI      = 0;
  char * pSwitch   = NULL;
  char * pValue    = NULL;

  /* init options struct */
  pOpt->szApp      = argv[0];
 
  /* read options form command line into struct */
  pSwitch = ReadNextArg(argv, argI, argc, true);

  while (pSwitch != NULL) {

    // * Node -n ******************************
    if (strcmp(pSwitch, SW_NODE) == 0) {
      pOpt->szNode = ReadNextArg(argv, argI, argc);
      if (pOpt->szNode == NULL) {
        FatalError ("Error, missing node name!", PRG_VERYFATAL);
      } // end if

    // * Database -d **************************
    } else if (strcmp(pSwitch, SW_DATABASE) == 0) {
      pOpt->szDB = ReadNextArg(argv, argI, argc);

    // * User -u ******************************
    } else if (strcmp(pSwitch, SW_USER) == 0) {
      pOpt->szUser = ReadNextArg(argv, argI, argc);

    // * Limit -l *****************************
    } else if (strcmp(pSwitch, SW_LIMIT) == 0) {
      pOpt->nLimit = atoi(ReadNextArg(argv, argI, argc));

    // * -nologo ******************************
    } else if (strcmp(pSwitch, SW_NOLOGO) == 0) {
      pOpt->bLogo = false;

    // * Version -v ***************************
    } else if (strcmp(pSwitch, SW_VERSION) == 0) {
      pOpt->bVersion = true;

    // ****************************************
    } else {
      Usage (pOpt);
    } // end if

    pSwitch = ReadNextArg(argv, argI, argc, true);
  } /* end while */

  if (pOpt->szDB == NULL) {
    FatalError ("Error, missing database name!", PRG_VERYFATAL);
  } // end if
  if (pOpt->szUser == NULL) {
    FatalError ("Error, missing user name and password!", PRG_VERYFATAL);
  } // end if
  if ((pOpt->nLimit > LIMIT_MAX) || (pOpt->nLimit < LIMIT_MIN)) {
    Usage (pOpt);
  } // end if


} // end ParseOptions

/* 
  -----------------------------------------------------------------------------
  function: main
  -----------------------------------------------------------------------------
*/
int main (int argc, char* argv[])
{
  int                 nExit    = 0;
  tOptions        aOpt;

  // read arguments
  ParseOptions (&aOpt, argc, argv);

  DBMCli_Logo oLogo;

  oLogo.PrintLogo ("SAP DB Extender, The Database Enlargment Tool", aOpt.bLogo);
  oLogo.PrintVersion("Extender", aOpt.bVersion);

  SAPDBErr_MessageList oMessage;

  pListener = new MyEventListener(aOpt.szNode, aOpt.szDB, aOpt.szUser, oMessage, aOpt.nLimit);

  if (oMessage.IsEmpty()) {
    // check configuration
    DBMCli_Config & oConfig = pListener->GetConfig();
    DBMCli_String   sValue;

    if(oConfig.GetValue(CFG_AUTOEXTFLAG, sValue,oMessage)) {
      if (sValue.CompareNoCase(VAL_YES) != 0) {
        SAPDBErr_MessageList oMsg( DBMCLI_COMPID, __FILE__, __LINE__,  
                                   SAPDBErr_MessageList::Error, (unsigned int) -1, 
                                   "AutoExtend ist disabled for database %s", 1, aOpt.szDB);
        oMessage = oMsg;
      } // end if
    } // end if
  } // end if

  // get database state
  if (oMessage.IsEmpty()) {
    DBMCli_State & oState = pListener->GetState();
    oState.Refresh(oMessage);

    // check database state
    if (oMessage.IsEmpty()) {
      if (oState.Value() != DBMCLI_DBSTATE_WARM) {
        SAPDBErr_MessageList oMsg( DBMCLI_COMPID, __FILE__, __LINE__,  
                                    SAPDBErr_MessageList::Error, (unsigned int)  -1, 
                                    "Database not in state ONLINE", 0 );
        oMessage = oMsg;
      } // end if
    } // end if

    // check database filling
    if (oMessage.IsEmpty()) {
      if (oState.DataPercent() >= aOpt.nLimit) {
        // place here a add volume
        pListener->AddVolume();
        oMessage = pListener->LastMessage();
      } // end if
    } // end if
  } // end if

  // run the event listener
  if (oMessage.IsEmpty()) {
    // accept signals
    signal(SIGINT,   abortHandler);
    signal(SIGTERM,  abortHandler);
    signal(SIGABRT,  abortHandler);

    pListener->Run();
    oMessage = pListener->LastMessage();
  } // end if

  if (!oMessage.IsEmpty()) {

    SAPDBErr_MessageList * pNextMsg = &oMessage;
  
    while (pNextMsg != NULL) {
      if (((int) pNextMsg->ID() == -24988)  && 
          (pNextMsg->NextMessage() != NULL) &&
          ((int) pNextMsg->NextMessage()->ID() == -9413)) {
        return 0;
      } // end if

      printf("%d,%s\n", (int) pNextMsg->ID(), pNextMsg->Message());
      pNextMsg = pNextMsg->NextMessage();
    } // end while
    return PRG_FATAL;
  } // end if

  return 0;
} // end main
