/* 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. */

/*
 * $Id: viewtablelock.cpp 2101 2013-01-21 14:12:52Z rdempsey $
 */

#include <ctime>
#include <iostream>
#include <vector>
#include <stdexcept>
#include <sstream>
#include <string>

#include "calpontsystemcatalog.h"
#include "dbrm.h"
#include "exceptclasses.h"

using namespace execplan;

namespace
{
//------------------------------------------------------------------------------
// Print command line usage
//------------------------------------------------------------------------------
void usage()
{
  std::cout << "Usage: viewtablelock [schemaname tablename]" << std::endl
            << "  If schema/table are specified, then that table's lock "
            << "information is displayed." << std::endl
            << "  If no schema/table are specified, then all table locks "
               "are displayed."
            << std::endl;
}

//------------------------------------------------------------------------------
// Print table locks.
// This function closely resembles CommandPackageProcessor::viewTableLock().
//------------------------------------------------------------------------------
int printTableLocks(boost::shared_ptr<CalpontSystemCatalog> systemCatalogPtr, const std::string& tableNameIn,
                    const std::vector<BRM::TableLockInfo> tableLocks)
{
  if (tableLocks.size() == 1)
    std::cout << " There is " << tableLocks.size() << " table lock" << std::endl << std::endl;
  else
    std::cout << " There are " << tableLocks.size() << " table locks" << std::endl << std::endl;

  std::string tableName(tableNameIn);

  // Make preliminary pass through the table locks in order to determine our
  // output column widths based on the data.  Min column widths are based on
  // the width of the column heading (except for the 'state' column).
  uint64_t maxLockID = 0;
  uint32_t maxPID = 0;
  int32_t maxSessionID = 0;
  int32_t minSessionID = 0;
  int32_t maxTxnID = 0;

  unsigned int tableNameColumnWidth = 5;    // "Table"
  unsigned int lockIDColumnWidth = 6;       // "LockID"
  unsigned int ownerColumnWidth = 7;        // "Process"
  unsigned int pidColumnWidth = 3;          // "PID"
  unsigned int sessionIDColumnWidth = 7;    // "Session"
  unsigned int txnIDColumnWidth = 3;        // "Txn"
  unsigned int createTimeColumnWidth = 12;  // "CreationTime"
  unsigned int pmColumnWidth = 7;           // "DBRoots"
  std::vector<std::string> createTimes;
  char cTimeBuffer[1024];
  std::vector<std::string> tableNames;

  std::ostringstream errMsgs;

  for (unsigned int i = 0; i < tableLocks.size(); i++)
  {
    if (tableNameIn.empty())
    {
      try
      {
        CalpontSystemCatalog::TableName tableNameStruct;
        tableNameStruct = systemCatalogPtr->tableName(tableLocks[i].tableOID);
        tableName = tableNameStruct.toString();
      }
      catch (logging::IDBExcept&)
      {
        tableName.clear();

        errMsgs << "Table with oid " << tableLocks[i].tableOID << " (Lock " << tableLocks[i].id << ")"
                << " is not in systable." << std::endl;
      }
      catch (std::runtime_error& e)
      {
        tableName.clear();

        errMsgs << "Error searching for table " << tableLocks[i].tableOID << " (Lock " << tableLocks[i].id
                << ")"
                << " in system catalog. " << e.what() << std::endl;
      }
      catch (...)
      {
        tableName.clear();

        errMsgs << "Unknown error searching for table " << tableLocks[i].tableOID << " (Lock "
                << tableLocks[i].id << ")"
                << " in system catalog. " << std::endl;
      }
    }

    tableNames.push_back(tableName);

    tableNameColumnWidth = std::max(tableNameColumnWidth, static_cast<unsigned int>(tableName.length()));
    maxLockID = std::max(maxLockID, tableLocks[i].id);
    ownerColumnWidth =
        std::max(ownerColumnWidth, static_cast<unsigned int>(tableLocks[i].ownerName.length()));
    maxPID = std::max(maxPID, tableLocks[i].ownerPID);
    maxSessionID = std::max(maxSessionID, tableLocks[i].ownerSessionID);
    minSessionID = std::min(minSessionID, tableLocks[i].ownerSessionID);
    maxTxnID = std::max(maxTxnID, tableLocks[i].ownerTxnID);

    ctime_r(&tableLocks[i].creationTime, cTimeBuffer);
    cTimeBuffer[strlen(cTimeBuffer) - 1] = '\0';  // strip trailing '\n'
    std::string cTimeStr(cTimeBuffer);
    createTimeColumnWidth = std::max(createTimeColumnWidth, static_cast<unsigned int>(cTimeStr.length()));
    createTimes.push_back(cTimeStr);

    std::ostringstream pms;

    for (unsigned k = 0; k < tableLocks[i].dbrootList.size(); k++)
    {
      if (k > 0)
        pms << ',';

      pms << tableLocks[i].dbrootList[k];
    }

    pmColumnWidth = std::max(pmColumnWidth, static_cast<unsigned int>(pms.str().length()));
  }

  tableNameColumnWidth += 2;
  ownerColumnWidth += 2;
  pmColumnWidth += 2;
  createTimeColumnWidth += 2;

  std::ostringstream idString;
  idString << maxLockID;
  lockIDColumnWidth = std::max(lockIDColumnWidth, static_cast<unsigned int>(idString.str().length()));
  lockIDColumnWidth += 2;

  std::ostringstream pidString;
  pidString << maxPID;
  pidColumnWidth = std::max(pidColumnWidth, static_cast<unsigned int>(pidString.str().length()));
  pidColumnWidth += 2;

  const std::string sessionNoneStr("BulkLoad");
  std::ostringstream sessionString;
  sessionString << maxSessionID;
  sessionIDColumnWidth =
      std::max(sessionIDColumnWidth, static_cast<unsigned int>(sessionString.str().length()));

  if (minSessionID < 0)
    sessionIDColumnWidth = std::max(sessionIDColumnWidth, static_cast<unsigned int>(sessionNoneStr.length()));

  sessionIDColumnWidth += 2;

  const std::string txnNoneStr("n/a");
  std::ostringstream txnString;
  txnString << maxTxnID;
  txnIDColumnWidth = std::max(txnIDColumnWidth, static_cast<unsigned int>(txnString.str().length()));
  txnIDColumnWidth += 2;

  std::cout.setf(std::ios::left, std::ios::adjustfield);
  std::cout << "  " << std::setw(tableNameColumnWidth) << "Table" << std::setw(lockIDColumnWidth) << "LockID"
            << std::setw(ownerColumnWidth) << "Process" << std::setw(pidColumnWidth) << "PID"
            << std::setw(sessionIDColumnWidth) << "Session" << std::setw(txnIDColumnWidth) << "Txn"
            << std::setw(createTimeColumnWidth) << "CreationTime" << std::setw(9) << "State"
            << std::setw(pmColumnWidth) << "DBRoots" << std::endl;

  // Make second pass through the table locks to display our result.
  for (unsigned idx = 0; idx < tableLocks.size(); idx++)
  {
    std::ostringstream pms;  // dbroots now

    for (unsigned k = 0; k < tableLocks[idx].dbrootList.size(); k++)
    {
      if (k > 0)
        pms << ',';

      pms << tableLocks[idx].dbrootList[k];
    }

    std::cout << "  " << std::setw(tableNameColumnWidth) << tableNames[idx] << std::setw(lockIDColumnWidth)
              << tableLocks[idx].id << std::setw(ownerColumnWidth) << tableLocks[idx].ownerName
              << std::setw(pidColumnWidth) << tableLocks[idx].ownerPID;

    // Log session ID, or "BulkLoad" if session is -1
    if (tableLocks[idx].ownerSessionID < 0)
      std::cout << std::setw(sessionIDColumnWidth) << sessionNoneStr;
    else
      std::cout << std::setw(sessionIDColumnWidth) << tableLocks[idx].ownerSessionID;

    // Log txn ID, or "n/a" if txn is -1
    if (tableLocks[idx].ownerTxnID < 0)
      std::cout << std::setw(txnIDColumnWidth) << txnNoneStr;
    else
      std::cout << std::setw(txnIDColumnWidth) << tableLocks[idx].ownerTxnID;

    std::cout << std::setw(createTimeColumnWidth) << createTimes[idx] << std::setw(9)
              << ((tableLocks[idx].state == BRM::LOADING) ? "LOADING" : "CLEANUP") << std::setw(pmColumnWidth)
              << pms.str() << std::endl;
  }

  if (!errMsgs.str().empty())
    std::cerr << std::endl << errMsgs.str() << std::endl;

  return 0;
}

}  // namespace

//------------------------------------------------------------------------------
// Main entry point to this program
//------------------------------------------------------------------------------
int main(int argc, char** argv)
{
  int c;

  while ((c = getopt(argc, argv, "h")) != EOF)
  {
    switch (c)
    {
      case 'h':
      case '?':
      default:
        usage();
        return (c == 'h' ? 0 : 1);
        break;
    }
  }

  int nargs = argc - optind;

  if ((nargs > 2) || (nargs == 1))
  {
    usage();
    return 1;
  }

  BRM::DBRM dbrm;
  std::vector<BRM::TableLockInfo> tableLocks;

  try
  {
    tableLocks = dbrm.getAllTableLocks();
  }
  catch (std::exception& ex)
  {
    std::cerr << "Error getting list of table locks: " << ex.what() << std::endl;
    return 2;
  }

  int rc = 0;

  if (nargs == 2)  // List table lock information for a given table
  {
    std::string schema(argv[optind++]);
    std::string table(argv[optind++]);

    // Get table oid
    boost::shared_ptr<CalpontSystemCatalog> systemCatalogPtr =
        CalpontSystemCatalog::makeCalpontSystemCatalog(1);
    systemCatalogPtr->identity(CalpontSystemCatalog::EC);
    CalpontSystemCatalog::TableName tableName;
    tableName.schema = schema;
    tableName.table = table;
    CalpontSystemCatalog::ROPair roPair;

    try
    {
      roPair = systemCatalogPtr->tableRID(tableName);
    }
    catch (logging::IDBExcept& e)
    {
      std::cerr << e.what() << std::endl;
      return 3;
    }
    catch (std::runtime_error& e)
    {
      std::cerr << "Error searching for table in system catalog. " << e.what() << std::endl;
      return 4;
    }
    catch (...)
    {
      std::cerr << "Unknown error searching for table in system catalog." << std::endl;
      return 5;
    }

    // Keep in mind the same table could have more than 1 lock
    // (on different PMs), so we don't exit loop after "first" match.
    std::vector<BRM::TableLockInfo> matchingTableLocks;

    for (unsigned int i = 0; i < tableLocks.size(); i++)
    {
      if (roPair.objnum == (CalpontSystemCatalog::OID)tableLocks[i].tableOID)
      {
        matchingTableLocks.push_back(tableLocks[i]);
      }
    }

    if (matchingTableLocks.size() > 0)
    {
      std::string tableName(schema);
      tableName += '.';
      tableName += table;
      rc = printTableLocks(systemCatalogPtr, tableName, matchingTableLocks);
    }
    else
    {
      std::cout << " Table " << schema << "." << table << " is not locked by any process. " << std::endl;
    }
  }
  else  // List table lock information for all table locks
  {
    // All table lock info required
    if (tableLocks.size() == 0)
    {
      std::cout << " No tables are locked in the database." << std::endl;
    }
    else
    {
      boost::shared_ptr<CalpontSystemCatalog> systemCatalogPtr =
          CalpontSystemCatalog::makeCalpontSystemCatalog();
      systemCatalogPtr->identity(CalpontSystemCatalog::EC);

      std::string tableName;
      rc = printTableLocks(systemCatalogPtr, tableName, tableLocks);
    }
  }

  return rc;
}
