// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkSQLDatabaseGraphSource.h"

#include "vtkDataSetAttributes.h"
#include "vtkDirectedGraph.h"
#include "vtkEventForwarderCommand.h"
#include "vtkExecutive.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkRowQueryToTable.h"
#include "vtkSQLDatabase.h"
#include "vtkSQLQuery.h"
#include "vtkSmartPointer.h"
#include "vtkTableToGraph.h"
#include "vtkUndirectedGraph.h"

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
class vtkSQLDatabaseGraphSource::implementation
{
public:
  implementation()
    : Database(0)
    , EdgeQuery(0)
    , EdgeTable(0)
    , VertexQuery(0)
    , VertexTable(0)
    , TableToGraph(vtkTableToGraph::New())
  {
  }

  ~implementation()
  {
    if (this->TableToGraph)
      this->TableToGraph->Delete();
    if (this->VertexTable)
      this->VertexTable->Delete();
    if (this->VertexQuery)
      this->VertexQuery->Delete();
    if (this->EdgeTable)
      this->EdgeTable->Delete();
    if (this->EdgeQuery)
      this->EdgeQuery->Delete();
    if (this->Database)
      this->Database->Delete();
  }

  std::string URL;
  std::string Password;
  std::string EdgeQueryString;
  std::string VertexQueryString;

  vtkSQLDatabase* Database;
  vtkSQLQuery* EdgeQuery;
  vtkRowQueryToTable* EdgeTable;
  vtkSQLQuery* VertexQuery;
  vtkRowQueryToTable* VertexTable;
  vtkTableToGraph* TableToGraph;
};

vtkStandardNewMacro(vtkSQLDatabaseGraphSource);

//------------------------------------------------------------------------------
vtkSQLDatabaseGraphSource::vtkSQLDatabaseGraphSource()
  : Implementation(new implementation())
  , Directed(true)
{
  this->SetNumberOfInputPorts(0);
  this->SetNumberOfOutputPorts(1);
  this->GenerateEdgePedigreeIds = true;
  this->EdgePedigreeIdArrayName = 0;
  this->SetEdgePedigreeIdArrayName("id");

  // Set up eventforwarder
  this->EventForwarder = vtkEventForwarderCommand::New();
  this->EventForwarder->SetTarget(this);

  // Now forward progress events from the graph layout
  this->Implementation->TableToGraph->AddObserver(vtkCommand::ProgressEvent, this->EventForwarder);
}

//------------------------------------------------------------------------------
vtkSQLDatabaseGraphSource::~vtkSQLDatabaseGraphSource()
{
  delete this->Implementation;
  this->SetEdgePedigreeIdArrayName(0);
  this->EventForwarder->Delete();
}

//------------------------------------------------------------------------------
void vtkSQLDatabaseGraphSource::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "URL: " << this->Implementation->URL << endl;
  os << indent << "EdgeQuery: " << this->Implementation->EdgeQueryString << endl;
  os << indent << "VertexQuery: " << this->Implementation->VertexQueryString << endl;
  os << indent << "Directed: " << this->Directed << endl;
  os << indent << "GenerateEdgePedigreeIds: " << this->GenerateEdgePedigreeIds << endl;
  os << indent << "EdgePedigreeIdArrayName: "
     << (this->EdgePedigreeIdArrayName ? this->EdgePedigreeIdArrayName : "(null)") << endl;
}

vtkStdString vtkSQLDatabaseGraphSource::GetURL()
{
  return this->Implementation->URL;
}

void vtkSQLDatabaseGraphSource::SetURL(const vtkStdString& url)
{
  if (url == this->Implementation->URL)
    return;

  if (this->Implementation->EdgeQuery)
  {
    this->Implementation->EdgeQuery->Delete();
    this->Implementation->EdgeQuery = 0;
  }

  if (this->Implementation->VertexQuery)
  {
    this->Implementation->VertexQuery->Delete();
    this->Implementation->VertexQuery = 0;
  }

  if (this->Implementation->Database)
  {
    this->Implementation->Database->Delete();
    this->Implementation->Database = 0;
  }

  this->Implementation->URL = url;

  this->Modified();
}

void vtkSQLDatabaseGraphSource::SetPassword(const vtkStdString& password)
{
  if (password == this->Implementation->Password)
    return;

  if (this->Implementation->EdgeQuery)
  {
    this->Implementation->EdgeQuery->Delete();
    this->Implementation->EdgeQuery = 0;
  }

  if (this->Implementation->VertexQuery)
  {
    this->Implementation->VertexQuery->Delete();
    this->Implementation->VertexQuery = 0;
  }

  if (this->Implementation->Database)
  {
    this->Implementation->Database->Delete();
    this->Implementation->Database = 0;
  }

  this->Implementation->Password = password;

  this->Modified();
}

vtkStdString vtkSQLDatabaseGraphSource::GetEdgeQuery()
{
  return this->Implementation->EdgeQueryString;
}

void vtkSQLDatabaseGraphSource::SetEdgeQuery(const vtkStdString& query)
{
  if (query == this->Implementation->EdgeQueryString)
    return;

  this->Implementation->EdgeQueryString = query;
  this->Modified();
}

vtkStdString vtkSQLDatabaseGraphSource::GetVertexQuery()
{
  return this->Implementation->VertexQueryString;
}

void vtkSQLDatabaseGraphSource::SetVertexQuery(const vtkStdString& query)
{
  if (query == this->Implementation->VertexQueryString)
    return;

  this->Implementation->VertexQueryString = query;
  this->Modified();
}

void vtkSQLDatabaseGraphSource::AddLinkVertex(const char* column, const char* domain, int hidden)
{
  this->Implementation->TableToGraph->AddLinkVertex(column, domain, hidden);
  this->Modified();
}

void vtkSQLDatabaseGraphSource::ClearLinkVertices()
{
  this->Implementation->TableToGraph->ClearLinkVertices();
  this->Modified();
}

void vtkSQLDatabaseGraphSource::AddLinkEdge(const char* column1, const char* column2)
{
  this->Implementation->TableToGraph->AddLinkEdge(column1, column2);
  this->Modified();
}

void vtkSQLDatabaseGraphSource::ClearLinkEdges()
{
  this->Implementation->TableToGraph->ClearLinkEdges();
  this->Modified();
}

//------------------------------------------------------------------------------
int vtkSQLDatabaseGraphSource::RequestDataObject(
  vtkInformation*, vtkInformationVector**, vtkInformationVector*)
{
  vtkGraph* output = 0;
  if (this->Directed)
  {
    output = vtkDirectedGraph::New();
  }
  else
  {
    output = vtkUndirectedGraph::New();
  }
  this->GetExecutive()->SetOutputData(0, output);
  output->Delete();

  return 1;
}

//------------------------------------------------------------------------------
int vtkSQLDatabaseGraphSource::RequestData(
  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
{
  if (this->Implementation->URL.empty())
    return 1;

  if (this->Implementation->EdgeQueryString.empty())
    return 1;

  // Set Progress Text
  this->SetProgressText("DatabaseGraphSource");

  // I've started so 1% progress :)
  this->UpdateProgress(.01);

  // Setup the database if it doesn't already exist ...
  if (!this->Implementation->Database)
  {
    this->Implementation->Database = vtkSQLDatabase::CreateFromURL(this->Implementation->URL);
    if (!this->Implementation->Database)
    {
      vtkErrorMacro(<< "Error creating database using URL: " << this->Implementation->URL);
      return 0;
    }

    if (!this->Implementation->Database->Open(this->Implementation->Password))
    {
      this->Implementation->Database->Delete();
      this->Implementation->Database = 0;

      vtkErrorMacro(<< "Error opening database: " << this->Implementation->URL);
      return 0;
    }
  }

  // I have a database: 5% progress
  this->UpdateProgress(.05);

  // Setup the edge query if it doesn't already exist ...
  if (!this->Implementation->EdgeQuery)
  {
    this->Implementation->EdgeQuery = this->Implementation->Database->GetQueryInstance();
    if (!this->Implementation->EdgeQuery)
    {
      vtkErrorMacro(<< "Internal error creating edge query instance.");
      return 0;
    }
  }

  this->Implementation->EdgeQuery->SetQuery(this->Implementation->EdgeQueryString.c_str());
  if (!this->Implementation->EdgeQuery->Execute())
  {
    vtkErrorMacro(<< "Error executing edge query: " << this->Implementation->EdgeQueryString);
    return 0;
  }

  // Executed edge query: 30% progress
  this->UpdateProgress(.3);

  if (!this->Implementation->EdgeTable)
  {
    this->Implementation->EdgeTable = vtkRowQueryToTable::New();
  }
  this->Implementation->EdgeTable->SetQuery(this->Implementation->EdgeQuery);

  this->Implementation->TableToGraph->SetInputConnection(
    0, this->Implementation->EdgeTable->GetOutputPort());

  // Setup the (optional) vertex query if it doesn't already exist ...
  if (this->Implementation->VertexQueryString.size())
  {
    if (!this->Implementation->VertexQuery)
    {
      this->Implementation->VertexQuery = this->Implementation->Database->GetQueryInstance();
      if (!this->Implementation->VertexQuery)
      {
        vtkErrorMacro(<< "Internal error creating vertex query instance.");
        return 0;
      }
    }

    this->Implementation->VertexQuery->SetQuery(this->Implementation->VertexQueryString.c_str());
    if (!this->Implementation->VertexQuery->Execute())
    {
      vtkErrorMacro(<< "Error executing vertex query: " << this->Implementation->VertexQueryString);
      return 0;
    }

    // Executed vertex query: 50% progress
    this->UpdateProgress(.5);

    if (!this->Implementation->VertexTable)
    {
      this->Implementation->VertexTable = vtkRowQueryToTable::New();
    }
    this->Implementation->VertexTable->SetQuery(this->Implementation->VertexQuery);

    this->Implementation->TableToGraph->SetInputConnection(
      1, this->Implementation->VertexTable->GetOutputPort());
  }

  // Set Progress Text
  this->SetProgressText("DatabaseGraphSource:TableToGraph");

  // Get the graph output ...
  this->Implementation->TableToGraph->SetDirected(this->Directed);
  this->Implementation->TableToGraph->Update();

  // Set Progress Text
  this->SetProgressText("DatabaseGraphSource");

  // Finished table to graph: 90% progress
  this->UpdateProgress(.9);

  vtkGraph* const output = vtkGraph::SafeDownCast(
    outputVector->GetInformationObject(0)->Get(vtkDataObject::DATA_OBJECT()));

  output->ShallowCopy(this->Implementation->TableToGraph->GetOutput());

  if (this->GenerateEdgePedigreeIds)
  {
    vtkIdType numEdges = output->GetNumberOfEdges();
    vtkSmartPointer<vtkIdTypeArray> arr = vtkSmartPointer<vtkIdTypeArray>::New();
    arr->SetName(this->EdgePedigreeIdArrayName);
    arr->SetNumberOfTuples(numEdges);
    for (vtkIdType i = 0; i < numEdges; ++i)
    {
      arr->InsertValue(i, i);
    }
    output->GetEdgeData()->SetPedigreeIds(arr);
  }
  else
  {
    vtkAbstractArray* arr = output->GetEdgeData()->GetAbstractArray(this->EdgePedigreeIdArrayName);
    if (!arr)
    {
      vtkErrorMacro(<< "Could not find edge pedigree id array: " << this->EdgePedigreeIdArrayName);
      return 0;
    }
    output->GetEdgeData()->SetPedigreeIds(arr);
  }

  // Done: 100% progress
  this->UpdateProgress(1);

  return 1;
}
VTK_ABI_NAMESPACE_END
