// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright 2008 Sandia Corporation
// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-Sandia-USGov
#include "vtkSQLiteQuery.h"

#include "vtkObjectFactory.h"
#include "vtkSQLiteDatabase.h"
#include "vtkSQLiteDatabaseInternals.h"
#include "vtkStringArray.h"
#include "vtkVariant.h"
#include "vtkVariantArray.h"

#include "vtk_sqlite.h"

#include <cassert>

#include <sstream>
#include <vector>

#define BEGIN_TRANSACTION "BEGIN TRANSACTION"
#define COMMIT_TRANSACTION "COMMIT"
#define ROLLBACK_TRANSACTION "ROLLBACK"

VTK_ABI_NAMESPACE_BEGIN
class vtkSQLiteQuery::Priv
{
public:
  sqlite3_stmt* Statement;
};

vtkStandardNewMacro(vtkSQLiteQuery);

//------------------------------------------------------------------------------
vtkSQLiteQuery::vtkSQLiteQuery()
{
  this->Private = new Priv;
  this->Private->Statement = nullptr;
  this->InitialFetch = true;
  this->InitialFetchResult = SQLITE_DONE;
  this->LastErrorText = nullptr;
  this->TransactionInProgress = false;
}

//------------------------------------------------------------------------------
vtkSQLiteQuery::~vtkSQLiteQuery()
{
  this->SetLastErrorText(nullptr);
  if (this->TransactionInProgress)
  {
    this->RollbackTransaction();
  }

  if (this->Private->Statement != nullptr)
  {
    if (this->Database != nullptr)
    {
      sqlite3_finalize(this->Private->Statement);
      this->Private->Statement = nullptr;
    }
  }
  delete this->Private;
}

//------------------------------------------------------------------------------
void vtkSQLiteQuery::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Statement: ";
  if (this->Private->Statement)
  {
    os << this->Private->Statement << "\n";
  }
  else
  {
    os << "(null)"
       << "\n";
  }
  os << indent << "InitialFetch: " << this->InitialFetch << "\n";
  os << indent << "InitialFetchResult: " << this->InitialFetchResult << "\n";
  os << indent << "TransactionInProgress: " << this->TransactionInProgress << "\n";
  os << indent << "LastErrorText: " << (this->LastErrorText ? this->LastErrorText : "(null)")
     << endl;
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::SetQuery(const char* newQuery)
{
  vtkDebugMacro(<< this->GetClassName() << " (" << this << "): setting Query to "
                << (newQuery ? newQuery : "(null)"));

  if (this->Query == nullptr && newQuery == nullptr)
  {
    return true;
  }

  if (this->Query && newQuery && (!strcmp(this->Query, newQuery)))
  {
    return true; // we've already got that query
  }

  delete[] this->Query;

  if (newQuery)
  {
    // Keep a local copy of the query - this is from vtkSetGet.h
    size_t n = strlen(newQuery) + 1;
    char* cp1 = new char[n];
    const char* cp2 = (newQuery);
    this->Query = cp1;
    do
    {
      *cp1++ = *cp2++;
    } while (--n);
  }
  else
  {
    this->Query = nullptr;
  }

  // If we get to this point the query has changed.  We need to
  // finalize the already-prepared statement if one exists and then
  // prepare a new statement.
  if (this->Private->Statement)
  {
    vtkDebugMacro(<< "Finalizing old statement");
    int finalizeStatus = sqlite3_finalize(this->Private->Statement);
    if (finalizeStatus != SQLITE_OK)
    {
      vtkWarningMacro(<< "SetQuery(): Finalize returned unexpected code " << finalizeStatus);
    }
    this->Private->Statement = nullptr;
  }

  if (this->Query)
  {
    vtkSQLiteDatabase* dbContainer = vtkSQLiteDatabase::SafeDownCast(this->Database);

    if (dbContainer == nullptr)
    {
      vtkErrorMacro(<< "This should never happen: SetQuery() called when there is no underlying "
                       "database.  You probably instantiated vtkSQLiteQuery directly instead of "
                       "calling vtkSQLDatabase::GetInstance().  This also happens during "
                       "TestSetGet in the CDash testing.");
      return false;
    }

    sqlite3* db = dbContainer->Internal->SQLiteInstance;
    const char* unused_statement;

    int prepareStatus = sqlite3_prepare_v2(db, this->Query, static_cast<int>(strlen(this->Query)),
      &this->Private->Statement, &unused_statement);

    if (prepareStatus != SQLITE_OK)
    {
      this->SetLastErrorText(sqlite3_errmsg(db));
      vtkWarningMacro(<< "SetQuery(): sqlite3_prepare_v2() failed with error message "
                      << this->GetLastErrorText() << " on statement: '" << this->Query << "'");
      this->Active = false;
      return false;
    }
  } // Done preparing new statement

  this->Modified();
  return true;
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::Execute()
{

  if (this->Query == nullptr)
  {
    vtkErrorMacro(<< "Cannot execute before a query has been set.");
    return false;
  }

  if (this->Private->Statement == nullptr)
  {
    vtkErrorMacro(<< "Execute(): Query is not null but prepared statement is.  There may have been "
                     "an error during SetQuery().");
    this->Active = false;
    return false;
  }
  else
  {
    sqlite3_reset(this->Private->Statement);
  }

  vtkDebugMacro(<< "Execute(): Query ready to execute.");

  this->InitialFetch = true;
  int result = sqlite3_step(this->Private->Statement);
  this->InitialFetchResult = result;

  if (result == SQLITE_DONE)
  {
    this->SetLastErrorText(nullptr);
    this->Active = true;
    return true;
  }
  else if (result != SQLITE_ROW)
  {
    vtkSQLiteDatabase* dbContainer = vtkSQLiteDatabase::SafeDownCast(this->Database);
    assert(dbContainer != nullptr);

    sqlite3* db = dbContainer->Internal->SQLiteInstance;

    this->SetLastErrorText(sqlite3_errmsg(db));
    vtkDebugMacro(<< "Execute(): sqlite3_step() returned error message "
                  << this->GetLastErrorText());
    this->Active = false;
    return false;
  }

  this->SetLastErrorText(nullptr);
  this->Active = true;
  return true;
}

//------------------------------------------------------------------------------
int vtkSQLiteQuery::GetNumberOfFields()
{
  if (!this->Active)
  {
    vtkErrorMacro(<< "GetNumberOfFields(): Query is not active!");
    return 0;
  }
  else
  {
    return sqlite3_column_count(this->Private->Statement);
  }
}

//------------------------------------------------------------------------------
const char* vtkSQLiteQuery::GetFieldName(int column)
{
  if (!this->Active)
  {
    vtkErrorMacro(<< "GetFieldName(): Query is not active!");
    return nullptr;
  }
  else if (column < 0 || column >= this->GetNumberOfFields())
  {
    vtkErrorMacro(<< "GetFieldName(): Illegal field index " << column);
    return nullptr;
  }
  else
  {
    return sqlite3_column_name(this->Private->Statement, column);
  }
}

//------------------------------------------------------------------------------
int vtkSQLiteQuery::GetFieldType(int column)
{
  if (!this->Active)
  {
    vtkErrorMacro(<< "GetFieldType(): Query is not active!");
    return -1;
  }
  else if (column < 0 || column >= this->GetNumberOfFields())
  {
    vtkErrorMacro(<< "GetFieldType(): Illegal field index " << column);
    return -1;
  }
  else
  {
    switch (sqlite3_column_type(this->Private->Statement, column))
    {
      case SQLITE_INTEGER:
        return VTK_INT;
      case SQLITE_FLOAT:
        return VTK_FLOAT;
      case SQLITE_TEXT:
        return VTK_STRING;
      case SQLITE_BLOB:
        return VTK_STRING; // until we have a BLOB type of our own
      case SQLITE_NULL:
        return VTK_VOID; // ??? what makes sense here?
      default:
      {
        vtkErrorMacro(<< "GetFieldType(): Unknown data type "
                      << sqlite3_column_type(this->Private->Statement, column) << " from SQLite.");
        return VTK_VOID;
      }
    }
  }
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::NextRow()
{
  if (!this->IsActive())
  {
    vtkErrorMacro(<< "NextRow(): Query is not active!");
    return false;
  }

  if (this->InitialFetch)
  {
    vtkDebugMacro(<< "NextRow(): Initial fetch being handled.");
    this->InitialFetch = false;
    return this->InitialFetchResult != SQLITE_DONE;
  }
  else
  {
    int result = sqlite3_step(this->Private->Statement);
    if (result == SQLITE_DONE)
    {
      return false;
    }
    else if (result == SQLITE_ROW)
    {
      return true;
    }
    else
    {
      vtkSQLiteDatabase* dbContainer = vtkSQLiteDatabase::SafeDownCast(this->Database);
      assert(dbContainer != nullptr);
      sqlite3* db = dbContainer->Internal->SQLiteInstance;
      this->SetLastErrorText(sqlite3_errmsg(db));
      vtkErrorMacro(<< "NextRow(): Database returned error code " << result
                    << " with the following message: " << this->GetLastErrorText());
      this->Active = false;
      return false;
    }
  }
}

//------------------------------------------------------------------------------
vtkVariant vtkSQLiteQuery::DataValue(vtkIdType column)
{
  if (!this->IsActive())
  {
    vtkWarningMacro(<< "DataValue() called on inactive query");
    return vtkVariant();
  }
  else if (column < 0 || column >= this->GetNumberOfFields())
  {
    vtkWarningMacro(<< "DataValue() called with out-of-range column index " << column);
    return vtkVariant();
  }
  else
  {
    switch (sqlite3_column_type(this->Private->Statement, column))
    {
      case SQLITE_INTEGER:
        return vtkVariant(sqlite3_column_int(this->Private->Statement, column));

      case SQLITE_FLOAT:
        return vtkVariant(sqlite3_column_double(this->Private->Statement, column));

      case SQLITE_TEXT:
      {
        std::ostringstream str;
        str << sqlite3_column_text(this->Private->Statement, column);
        return vtkVariant(vtkStdString(str.str()));
      }

      case SQLITE_BLOB:
      {
        // This is a hack ... by passing the BLOB to vtkStdString with an explicit
        // byte count, we ensure that the string will store all of the BLOB's bytes,
        // even if there are nullptr values.

        return vtkVariant(vtkStdString(
          static_cast<const char*>(sqlite3_column_blob(this->Private->Statement, column)),
          sqlite3_column_bytes(this->Private->Statement, column)));
      }

      case SQLITE_NULL:
      default:
        return vtkVariant();
    }
  }
}

//------------------------------------------------------------------------------
const char* vtkSQLiteQuery::GetLastErrorText()
{
  return this->LastErrorText;
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::HasError()
{
  return (this->GetLastErrorText() != nullptr);
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::BeginTransaction()
{
  if (this->TransactionInProgress)
  {
    vtkErrorMacro(<< "Cannot start a transaction.  One is already in progress.");
    return false;
  }

  vtkSQLiteDatabase* dbContainer = vtkSQLiteDatabase::SafeDownCast(this->Database);
  assert(dbContainer != nullptr);

  sqlite3* db = dbContainer->Internal->SQLiteInstance;
  char* errorMessage = nullptr;
  int result = sqlite3_exec(db, BEGIN_TRANSACTION, nullptr, nullptr, &errorMessage);

  if (result == SQLITE_OK)
  {
    this->TransactionInProgress = true;
    this->SetLastErrorText(nullptr);
    vtkDebugMacro(<< "BeginTransaction() succeeded.");
    return true;
  }
  else
  {
    vtkErrorMacro(<< "BeginTransaction(): sqlite3_exec returned unexpected result code " << result);
    if (errorMessage)
    {
      vtkErrorMacro(<< " and error message " << errorMessage);
    }
    this->TransactionInProgress = false;
    return false;
  }
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::CommitTransaction()
{
  if (this->Private->Statement)
  {
    sqlite3_finalize(this->Private->Statement);
    this->Private->Statement = nullptr;
  }

  if (!this->TransactionInProgress)
  {
    vtkErrorMacro(<< "Cannot commit.  There is no transaction in progress.");
    return false;
  }

  vtkSQLiteDatabase* dbContainer = vtkSQLiteDatabase::SafeDownCast(this->Database);
  assert(dbContainer != nullptr);
  sqlite3* db = dbContainer->Internal->SQLiteInstance;
  char* errorMessage = nullptr;
  int result = sqlite3_exec(db, COMMIT_TRANSACTION, nullptr, nullptr, &errorMessage);

  if (result == SQLITE_OK)
  {
    this->TransactionInProgress = false;
    this->SetLastErrorText(nullptr);
    vtkDebugMacro(<< "CommitTransaction() succeeded.");
    return true;
  }
  else
  {
    vtkErrorMacro(<< "CommitTransaction(): sqlite3_exec returned unexpected result code "
                  << result);
    if (errorMessage)
    {
      this->SetLastErrorText(errorMessage);
      vtkErrorMacro(<< " and error message " << errorMessage);
    }
    assert(1 == 0);
    return false;
  }
}

//------------------------------------------------------------------------------
bool vtkSQLiteQuery::RollbackTransaction()
{
  if (!this->TransactionInProgress)
  {
    vtkErrorMacro(<< "Cannot rollback.  There is no transaction in progress.");
    return false;
  }

  vtkSQLiteDatabase* dbContainer = vtkSQLiteDatabase::SafeDownCast(this->Database);
  assert(dbContainer != nullptr);
  sqlite3* db = dbContainer->Internal->SQLiteInstance;
  char* errorMessage = nullptr;
  int result = sqlite3_exec(db, ROLLBACK_TRANSACTION, nullptr, nullptr, &errorMessage);

  if (result == SQLITE_OK)
  {
    this->TransactionInProgress = false;
    this->SetLastErrorText(nullptr);
    vtkDebugMacro(<< "RollbackTransaction() succeeded.");
    return true;
  }
  else
  {
    vtkErrorMacro(<< "RollbackTransaction(): sqlite3_exec returned unexpected result code "
                  << result);
    if (errorMessage)
    {
      this->SetLastErrorText(errorMessage);
      vtkErrorMacro(<< " and error message " << errorMessage);
    }
    return false;
  }
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, unsigned char value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, signed char value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, unsigned short value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, short value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, unsigned int value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, int value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, unsigned long value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, long value)
{
  return this->BindIntegerParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, unsigned long long value)
{
  return this->BindInt64Parameter(index, static_cast<vtkTypeInt64>(value));
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, long long value)
{
  return this->BindInt64Parameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, float value)
{
  return this->BindDoubleParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, double value)
{
  return this->BindDoubleParameter(index, value);
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, const char* value)
{
  return this->BindParameter(index, value, strlen(value));
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, const char* data, size_t length)
{
  return this->BindStringParameter(index, data, static_cast<int>(length));
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, const vtkStdString& value)
{
  return this->BindParameter(index, value.c_str());
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, const void* data, size_t length)
{
  return this->BindBlobParameter(index, data, static_cast<int>(length));
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindIntegerParameter(int index, int value)
{
  if (!this->Private->Statement)
  {
    vtkErrorMacro(<< "No statement available.  Did you forget to call SetQuery?");
    return false;
  }

  if (this->Active)
  {
    this->Active = false;
    sqlite3_reset(this->Private->Statement);
  }
  int status = sqlite3_bind_int(this->Private->Statement, index + 1, value);

  if (status != SQLITE_OK)
  {
    std::ostringstream errormessage;
    errormessage << "sqlite_bind_int returned error: " << status;
    this->SetLastErrorText(errormessage.str().c_str());
    vtkErrorMacro(<< errormessage.str());
    return false;
  }
  return true;
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindInt64Parameter(int index, vtkTypeInt64 value)
{
  if (!this->Private->Statement)
  {
    vtkErrorMacro(<< "No statement available.  Did you forget to call SetQuery?");
    return false;
  }

  if (this->Active)
  {
    this->Active = false;
    sqlite3_reset(this->Private->Statement);
  }
  int status =
    sqlite3_bind_int(this->Private->Statement, index + 1, static_cast<sqlite_int64>(value));

  if (status != SQLITE_OK)
  {
    std::ostringstream errormessage;
    errormessage << "sqlite_bind_int64 returned error: " << status;
    this->SetLastErrorText(errormessage.str().c_str());
    vtkErrorMacro(<< this->GetLastErrorText());
    return false;
  }
  return true;
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindDoubleParameter(int index, double value)
{
  if (!this->Private->Statement)
  {
    vtkErrorMacro(<< "No statement available.  Did you forget to call SetQuery?");
    return false;
  }

  if (this->Active)
  {
    this->Active = false;
    sqlite3_reset(this->Private->Statement);
  }

  int status = sqlite3_bind_double(this->Private->Statement, index + 1, value);

  if (status != SQLITE_OK)
  {
    std::ostringstream errormessage;
    errormessage << "sqlite_bind_double returned error: " << status;
    this->SetLastErrorText(errormessage.str().c_str());
    vtkErrorMacro(<< this->GetLastErrorText());
    return false;
  }
  return true;
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindStringParameter(int index, const char* value, int length)
{
  if (!this->Private->Statement)
  {
    vtkErrorMacro(<< "No statement available.  Did you forget to call SetQuery?");
    return false;
  }

  if (this->Active)
  {
    this->Active = false;
    sqlite3_reset(this->Private->Statement);
  }

  int status =
    sqlite3_bind_text(this->Private->Statement, index + 1, value, length, SQLITE_TRANSIENT);

  if (status != SQLITE_OK)
  {
    std::ostringstream errormessage;
    errormessage << "sqlite_bind_text returned error: " << status;
    this->SetLastErrorText(errormessage.str().c_str());
    vtkErrorMacro(<< this->GetLastErrorText());
    return false;
  }
  return true;
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindBlobParameter(int index, const void* data, int length)
{
  if (!this->Private->Statement)
  {
    vtkErrorMacro(<< "No statement available.  Did you forget to call SetQuery?");
    return false;
  }

  if (this->Active)
  {
    this->Active = false;
    sqlite3_reset(this->Private->Statement);
  }

  int status =
    sqlite3_bind_blob(this->Private->Statement, index + 1, data, length, SQLITE_TRANSIENT);

  if (status != SQLITE_OK)
  {
    std::ostringstream errormessage;
    errormessage << "sqlite_bind_blob returned error: " << status;
    this->SetLastErrorText(errormessage.str().c_str());
    vtkErrorMacro(<< this->GetLastErrorText());
    return false;
  }
  return true;
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::ClearParameterBindings()
{
  if (!this->Private->Statement)
  {
    vtkErrorMacro(<< "No statement available.  Did you forget to call SetQuery?");
    return false;
  }

  if (this->Active)
  {
    this->Active = false;
    sqlite3_reset(this->Private->Statement);
  }

  int status = sqlite3_clear_bindings(this->Private->Statement);

  if (status != SQLITE_OK)
  {
    std::ostringstream errormessage;
    errormessage << "sqlite_clear_bindings returned error: " << status;
    this->SetLastErrorText(errormessage.str().c_str());
    vtkErrorMacro(<< this->GetLastErrorText());
    return false;
  }
  return true;
}

//------------------------------------------------------------------------------

bool vtkSQLiteQuery::BindParameter(int index, vtkVariant value)
{
  return this->Superclass::BindParameter(index, value);
}
VTK_ABI_NAMESPACE_END
