// local includes
#include "pqACE3PJobLoader.h"

// pq includes
#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqCoreUtilities.h"
#include "pqDataRepresentation.h"
#include "pqObjectBuilder.h"
#include "pqPipelineRepresentation.h"
#include "pqPipelineSource.h"
#include "pqRenderView.h"
#include "pqSMAdaptor.h"
#include "pqScalarsToColors.h"
#include "pqServerManagerModel.h"
#include "pqView.h"

#include "smtk/simulation/ace3p/qt/qtModeSelectDialog.h"

// vtk includes
#include "vtkDataObject.h"
#include "vtkPVArrayInformation.h"
#include "vtkPVDataInformation.h"
#include "vtkPVDataSetAttributesInformation.h"
#include "vtkSMPVRepresentationProxy.h"
#include "vtkSMParaViewPipelineControllerWithRendering.h"
#include "vtkSMProperty.h"
#include "vtkSMProxy.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMViewLayoutProxy.h"

// Qt includes
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QString>

//-----------------------------------------------------------------------------
pqACE3PJobLoader::pqACE3PJobLoader(QObject* parent)
  : Superclass(parent)
{
  m_dialog = new smtk::simulation::ace3p::qtModeSelectDialog(pqCoreUtilities::mainWidget());

  QObject::connect(
    m_dialog,
    &smtk::simulation::ace3p::qtModeSelectDialog::selectedModeFiles,
    this,
    &pqACE3PJobLoader::setupPipeline);
}

//-----------------------------------------------------------------------------
static pqACE3PJobLoader* g_instance = nullptr;

pqACE3PJobLoader* pqACE3PJobLoader::instance(QObject* parent)
{
  if (!g_instance)
  {
    g_instance = new pqACE3PJobLoader(parent);
  }

  if (g_instance->parent() == nullptr && parent)
  {
    g_instance->setParent(parent);
  }

  return g_instance;
}

//-----------------------------------------------------------------------------
pqACE3PJobLoader::~pqACE3PJobLoader()
{
  if (g_instance == this)
  {
    g_instance = nullptr;
  }

  QObject::disconnect(this);
}

//-----------------------------------------------------------------------------
pqView* pqACE3PJobLoader::findView(const pqPipelineSource* source)
{
  // Attempt 1, try to find a view in which the source is already shown.
  if (source)
  {
    foreach (pqView* view, source->getViews())
    {
      pqDataRepresentation* repr = source->getRepresentation(0, view);
      if (repr && repr->isVisible())
      {
        return view;
      }
    }
  }

  // Step 2, check all the views and see if one is the right type and not
  // showing anything.
  pqApplicationCore* core = pqApplicationCore::instance();
  pqServerManagerModel* smModel = core->getServerManagerModel();

  foreach (pqView* view, smModel->findItems<pqView*>())
  {
    if (
      view && (view->getViewType() == "RenderView") &&
      (view->getNumberOfVisibleRepresentations() < 1))
    {
      return view;
    }
  }

  // Attempt 3, create a render view
  pqObjectBuilder* builder = core->getObjectBuilder();
  pqServer* server = smModel->getItemAtIndex<pqServer*>(0);

  auto layout = pqActiveObjects::instance().activeLayout();
  pqView* newView = builder->createView("RenderView", server);
  pqRenderView* meshView = qobject_cast<pqRenderView*>(newView);
  layout->AssignViewToAnyCell(meshView->getViewProxy(), 1);
  return newView;
}

//-----------------------------------------------------------------------------
void initVizOptions(pqPipelineSource* meshReader, pqView* meshView)
{
  const char* name = "efield";
  // Get the (downcasted) representation.
  pqDataRepresentation* _repr = meshReader->getRepresentation(0, meshView);
  pqPipelineRepresentation* repr = qobject_cast<pqPipelineRepresentation*>(_repr);
  if (!repr)
  {
    qWarning() << "Could not find representation object.";
    return;
  }

  // Get information about the field we are supposed to be showing.
  vtkPVDataInformation* dataInfo = repr->getInputDataInformation();
  vtkPVDataSetAttributesInformation* pointInfo = dataInfo->GetPointDataInformation();
  vtkPVArrayInformation* arrayInfo = pointInfo->GetArrayInformation(name);
  if (!arrayInfo)
  {
    return;
  }

  // Set the field to color by.
  vtkSMPVRepresentationProxy::SetScalarColoring(repr->getProxy(), name, vtkDataObject::POINT);

  // Adjust the color map to be rainbow.
  pqScalarsToColors* lut = repr->getLookupTable();
  vtkSMProxy* lutProxy = lut->getProxy();

  pqSMAdaptor::setEnumerationProperty(lutProxy->GetProperty("ColorSpace"), "HSV");

  // Control points are 4-tuples comprising scalar value + RGB
  QList<QVariant> RGBPoints;
  RGBPoints << 0.0 << 0.0 << 0.0 << 1.0;
  RGBPoints << 1.0 << 1.0 << 0.0 << 0.0;
  pqSMAdaptor::setMultipleElementProperty(lutProxy->GetProperty("RGBPoints"), RGBPoints);

  // NaN color is a 3-tuple RGB.
  QList<QVariant> NanColor;
  NanColor << 0.5 << 0.5 << 0.5;
  pqSMAdaptor::setMultipleElementProperty(lutProxy->GetProperty("NanColor"), NanColor);

  // Set the range of the scalars to the current range of the field.
  double range[2];
  arrayInfo->GetComponentRange(-1, range);
  lut->setScalarRange(range[0], range[1]);

  lutProxy->UpdateVTKObjects();

  meshView->render();
}

//-----------------------------------------------------------------------------
pqPipelineSource* pqACE3PJobLoader::findPipelineSource(const char* SMName)
{
  pqApplicationCore* core = pqApplicationCore::instance();
  pqServerManagerModel* smModel = core->getServerManagerModel();
  pqServer* server = smModel->getItemAtIndex<pqServer*>(0);

  QList<pqPipelineSource*> sources = smModel->findItems<pqPipelineSource*>(server);
  foreach (pqPipelineSource* s, sources)
  {
    if (strcmp(s->getProxy()->GetXMLName(), SMName) == 0)
    {
      return s;
    }
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
void pqACE3PJobLoader::openModeSelectDialog(const QString& jobDir)
{
  m_dialog->setJobDir(jobDir);
  m_dialog->show();

  QStringList modeFiles = this->getModeFileNames(jobDir);
  m_dialog->initTable(modeFiles);
}

//-----------------------------------------------------------------------------
static void destroyPortConsumers(pqOutputPort* port)
{
  foreach (pqPipelineSource* consumer, port->getConsumers())
  {
    pqACE3PJobLoader::destroyPipelineSourceAndConsumers(consumer);
  }
}

void pqACE3PJobLoader::destroyPipelineSourceAndConsumers(pqPipelineSource* source)
{
  if (!source)
  {
    return;
  }

  foreach (pqOutputPort* port, source->getOutputPorts())
  {
    destroyPortConsumers(port);
  }

  pqApplicationCore* core = pqApplicationCore::instance();
  pqObjectBuilder* builder = core->getObjectBuilder();
  builder->destroy(source);
}

//-----------------------------------------------------------------------------
void pqACE3PJobLoader::setupPipeline(const QString& jobDir, const QStringList& modeFiles)
{
  pqApplicationCore* core = pqApplicationCore::instance();
  pqObjectBuilder* builder = core->getObjectBuilder();
  vtkNew<vtkSMParaViewPipelineControllerWithRendering> controller;

  pqServerManagerModel* smModel = core->getServerManagerModel();
  pqServer* server = smModel->getItemAtIndex<pqServer*>(0);

  // find the last pipeline source if one exists already in the pipeline
  QList<pqPipelineSource*> sources = smModel->findItems<pqPipelineSource*>(server);
  pqPipelineSource* oldSource = nullptr;
  foreach (pqPipelineSource* s, sources)
  {
    if (strcmp(s->getProxy()->GetXMLName(), "SLACReader") == 0)
    {
      oldSource = s;
    }
  }

  // get the first view the old source is in and delete the old source from the pipeline
  pqView* meshView = nullptr;
  if (oldSource)
  {
    QList<pqView*> views = oldSource->getViews();
    if (views.size() > 0)
    {
      meshView = views[0];
    }
    destroyPipelineSourceAndConsumers(oldSource);
  }

  // get the mesh file name
  QStringList meshFiles = this->getMeshFileName(jobDir);

  if (!meshFiles.isEmpty())
  {
    pqPipelineSource* meshReader =
      builder->createReader("sources", "SLACReader", meshFiles, server);

    vtkSMSourceProxy* meshReaderProxy = vtkSMSourceProxy::SafeDownCast(meshReader->getProxy());

    // Set up mode (if any).
    pqSMAdaptor::setFileListProperty(meshReaderProxy->GetProperty("ModeFileName"), modeFiles);

    // Push changes to server so that when the representation gets updated,
    // it uses the property values we set.
    meshReaderProxy->UpdateVTKObjects();

    // ensures that new timestep range, if any gets fetched from the server.
    meshReaderProxy->UpdatePipelineInformation();

    // ensures that the FrequencyScale and PhaseShift have correct default
    // values.
    meshReaderProxy->GetProperty("FrequencyScale")
      ->Copy(meshReaderProxy->GetProperty("FrequencyScaleInfo"));
    meshReaderProxy->GetProperty("PhaseShift")
      ->Copy(meshReaderProxy->GetProperty("PhaseShiftInfo"));

    // if a meshview is not already available, get a new one
    if (!meshView)
    {
      meshView = findView(meshReader);
    }

    // cast to a render view
    pqRenderView* meshRenderView = qobject_cast<pqRenderView*>(meshView);

    // Make representations.
    controller->HideAll(meshRenderView->getViewProxy());
    controller->Show(meshReaderProxy, 0, meshRenderView->getViewProxy());
    controller->Show(meshReaderProxy, 1, meshRenderView->getViewProxy());

    meshRenderView->resetCamera();
    initVizOptions(meshReader, meshRenderView);
    // We have already made the representations and pushed everything to the
    // server manager.  Thus, there is no state left to be modified.
    meshReader->setModifiedState(pqProxy::UNMODIFIED);
  }
}

//-----------------------------------------------------------------------------
QDir pqACE3PJobLoader::findDownloadFolder(const QString& jobDir)
{
  QDir dir(jobDir);

  QStringList dirFilter;
  dirFilter << "download";
  QFileInfoList downloadDirs = dir.entryInfoList(dirFilter, QDir::Dirs);

  // if the directory is not found, exit
  if (!downloadDirs.size())
  {
    qWarning() << "Job not downloaded";
    return QDir();
  }
  // navigate to download folder
  return QDir(downloadDirs[0].absolutePath());
}

QStringList pqACE3PJobLoader::getMeshFileName(const QString& jobDir)
{
  QStringList ret;
  // navigate to the download folder
  QDir dir = findDownloadFolder(jobDir);

  // find the mesh file and return it in a list
  QStringList nameFilter;
  nameFilter << "*.ncdf"
             << "*.netcdf"
             << "*.nc";

  QDirIterator it(dir.absolutePath(), nameFilter, QDir::Files, QDirIterator::Subdirectories);
  while (it.hasNext())
  {
    ret.append(it.next());
  }

  return ret;
}

QStringList pqACE3PJobLoader::getModeFileNames(const QString& jobDir)
{
  QStringList ret;
  QDir dir = findDownloadFolder(jobDir);
  QStringList nameFilter;
  nameFilter << "*.mod";

  QDirIterator it(dir.absolutePath(), nameFilter, QDir::Files, QDirIterator::Subdirectories);
  while (it.hasNext())
  {
    ret.append(it.next());
  }

  return ret;
}
