#include <sstream>
#include "Grid.h"
#include "vtkCPDataDescription.h"
#include "vtkCPInputDataDescription.h"
#include "vtkCPProcessor.h"
#include "vtkCPPythonScriptPipeline.h"
#include "vtkUnstructuredGrid.h"
#include "vtkSmartPointer.h"


namespace
{
  vtkCPProcessor* g_coprocessor;             /// catalyst coprocessor
  vtkCPDataDescription* g_coprocessorData;   /// input, sinput, input3D, sinput3D
  bool g_isTimeDataSet;                      /// is time data set?
  CamAdaptor::Grid<CamAdaptor::RECTILINEAR>* g_grid;                 /// rectilinear grid (2D, 3D)
  CamAdaptor::Grid<CamAdaptor::SPHERE>* g_sgrid;                     /// structured (spherical) (2D, 3Da) grids

/// Deletes global data
void fv_finalize()
{
  if (g_grid)
    {
    delete g_grid;
    }
  if (g_sgrid)
    {
    delete g_sgrid;
    }
}

/// Deletes the Catalyt Coprocessor and data
void fv_coprocessorfinalize()
{
  if (g_coprocessor)
    {
    g_coprocessor->Delete();
    g_coprocessor = NULL;
    }
  if (g_coprocessorData)
    {
    g_coprocessorData->Delete();
    g_coprocessorData = NULL;
    }
}
};


//////////////////////////////////////////////////////////////////////

/// Initializes the Catalyst Coprocessor
/// WARNING: Make sure you pass a zero terminated string
extern "C" void fv_coprocessorinitializewithpython_(const char* pythonScriptName)
{
  if (!g_coprocessor)
    {
    g_coprocessor = vtkCPProcessor::New();
    g_coprocessor->Initialize();
    // python pipeline
    vtkSmartPointer<vtkCPPythonScriptPipeline> pipeline =
      vtkSmartPointer<vtkCPPythonScriptPipeline>::New();
    pipeline->Initialize(pythonScriptName);
    g_coprocessor->AddPipeline(pipeline);
    }
  if (!g_coprocessorData)
    {
    g_coprocessorData = vtkCPDataDescription::New();
    g_coprocessorData->AddInput("input");
    g_coprocessorData->AddInput("input3D");
    g_coprocessorData->AddInput("sinput");
    g_coprocessorData->AddInput("sinput3D");
    }
}

/// Creates the Grids for 2D, 3D rectilinear and 2D, 3D spherical
extern "C" void fv_create_grid_(
  int* dim, double* lonCoord, double* latCoord, double* levCoord,
  int* nCells2d, int* maxNcols,
  int* myRank)
{
  //printCreateGrid(dim, lonCoord, latCoord, levCoord, nCells2d, maxNcols, myRank);
  if (!g_coprocessorData)
    {
    vtkGenericWarningMacro("Unable to access CoProcessorData.");
    return;
    }
  g_grid = new CamAdaptor::Grid<CamAdaptor::RECTILINEAR>();
  g_grid->SetMpiRank(*myRank);
  g_grid->SetChunkCapacity(*maxNcols);
  g_grid->SetNCells2d(*nCells2d);
  g_grid->SetNLon(dim[0]);
  g_grid->SetNLat(dim[1]);
  g_grid->SetLev(dim[2], levCoord);
  g_grid->SetLonStep(lonCoord[1] - lonCoord[0]);
  g_grid->SetLatStep(latCoord[1] - latCoord[0]);
  g_grid->Create();
  if (! CamAdaptor::Grid<CamAdaptor::RECTILINEAR>::SetToCoprocessor(
        g_coprocessorData, "input", g_grid->GetGrid2d()) ||
      ! CamAdaptor::Grid<CamAdaptor::RECTILINEAR>::SetToCoprocessor(
        g_coprocessorData, "input3D", g_grid->GetGrid3d()))
    {
    vtkGenericWarningMacro(<< "No input data description");
    delete g_grid;
    g_grid = NULL;
    }

  g_sgrid = new CamAdaptor::Grid<CamAdaptor::SPHERE>();
  g_sgrid->SetMpiRank(*myRank);
  g_sgrid->SetChunkCapacity(*maxNcols);
  g_sgrid->SetNCells2d(*nCells2d);
  g_sgrid->SetNLon(dim[0]);
  g_sgrid->SetNLat(dim[1]);
  g_sgrid->SetLev(dim[2], levCoord);
  g_sgrid->SetLonStep(lonCoord[1] - lonCoord[0]);
  g_sgrid->SetLatStep(latCoord[1] - latCoord[0]);
  g_sgrid->Create();
  if (! CamAdaptor::Grid<CamAdaptor::SPHERE>::SetToCoprocessor(
        g_coprocessorData, "sinput", g_sgrid->GetGrid2d()) ||
      ! CamAdaptor::Grid<CamAdaptor::SPHERE>::SetToCoprocessor(
        g_coprocessorData, "sinput3D", g_sgrid->GetGrid3d()))
    {
    vtkGenericWarningMacro(<< "No input data description");
    delete g_sgrid;
    g_sgrid = NULL;
    }
}

/// for timestep 0: creates the points and cells for the grids.
/// for all timesteps: copies data from the simulation to Catalyst.
extern "C" void fv_add_chunk_(
  int* nstep, int* chunkSize,
  double* lonRad, double* latRad,
  double* psScalar, double *tScalar, double* uScalar, double* vScalar)
{
  if (*nstep == 0)
    {
    for (int i = 0; i < *chunkSize; ++i)
      {
      if (g_grid)
        {
        g_grid->AddPointsAndCells(lonRad[i], latRad[i]);
        }
      if (g_sgrid)
        {
        g_sgrid->AddPointsAndCells(lonRad[i], latRad[i]);
        }
      }
    }
  if (g_grid)
    {
    // g_grid->PrintAddChunk(
    //   nstep, chunkSize, lonRad, latRad, psScalar, tScalar);
    g_grid->SetAttributeValue(*chunkSize, lonRad, latRad,
                              psScalar, tScalar, uScalar, vScalar);
    }
  if (g_sgrid)
    {
    g_sgrid->SetAttributeValue(*chunkSize, lonRad, latRad,
                               psScalar, tScalar, uScalar, vScalar);
    }
}

extern "C" void fv_catalyst_finalize_()
{
  fv_finalize();
  fv_coprocessorfinalize();
}


/// Checks if Catalyst needs to coprocess data
extern "C" int fv_requestdatadescription_(int* timeStep, double* time)
{
  if(!g_coprocessorData || !g_coprocessor)
    {
    vtkGenericWarningMacro("Data or coprocessor are not initialized.");
    return 0;
    }
  vtkIdType tStep = *timeStep;
  g_coprocessorData->SetTimeData(*time, tStep);
  if(g_coprocessor->RequestDataDescription(g_coprocessorData))
    {
    g_isTimeDataSet = true;
    return 1;
    }
  else
    {
    g_isTimeDataSet = false;
    return 0;
    }
}

/// Checks if the grids need to be created
extern "C" int fv_needtocreategrid_()
{
  if(!g_isTimeDataSet)
    {
    vtkGenericWarningMacro("Time data not set.");
    return 0;
    }

  // assume that the grid is not changing so that we only build it
  // the first time, otherwise we clear out the field data
  vtkCPInputDataDescription* idd = 
    g_coprocessorData->GetInputDescriptionByName("input");
  return (idd == NULL || idd->GetGrid() == NULL);
}

/// calls the coprocessor
extern "C" void fv_coprocess_()
{
  if(!g_isTimeDataSet)
    {
    vtkGenericWarningMacro("Time data not set.");
    }
  else
    {
    g_coprocessor->CoProcess(g_coprocessorData);
    }
  // Reset time data.
  g_isTimeDataSet = false;
}
