#include <vtkActor.h>
#include <vtkCallbackCommand.h>
#include <vtkCamera.h>
#include <vtkContourFilter.h>
#include <vtkGenericOutlineFilter.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPointSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkProperty2D.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSliderRepresentation2D.h>
#include <vtkSliderWidget.h>
#include <vtkSphereSource.h>
#include <vtkStreamTracer.h>
#include <vtkTextProperty.h>
#include <vtkTubeFilter.h>
#include <vtkUnstructuredGrid.h>
#include <vtkVRMLImporter.h>
#include <vtkXMLUnstructuredGridReader.h>

#include <array>
#include <chrono>
#include <iostream>
#include <thread>

namespace {
struct SliderProperties
{
  double tubeWidth{0.01};
  double sliderLength{0.02};
  double sliderWidth{0.05};
  double endCapLength{0.015};
  double endCapWidth{0.05};
  double titleHeight{0.03};
  double labelHeight{0.025};

  double valueMinimum{0.0};
  double valueMaximum{1.0};
  double valueInitial{1.0};

  std::array<double, 2> p1{0.1, 0.05};
  std::array<double, 2> p2{0.2, 0.05};

  std::string title;

  std::string titleColor{"AliceBlue"};
  std::string labelColor{"AliceBlue"};
  std::string sliderColor{"BurlyWood"};
  std::string selectedColor{"Lime"};
  std::string barColor{"Black"};
  std::string barEndsColor{"Indigo"};
  std::string valueColor{"DarkSlateGray"};
};

/**
 * @param properties: The slider properties: dimensions, range, title name,
 *                    position, and colors.
 * @param renderer: The renderer.
 * @param interactor: The interactor.
 * @return The slider widget.
 */
vtkNew<vtkSliderWidget> MakeSliderWidget(SliderProperties const& sp,
                                         vtkRenderer* renderer,
                                         vtkRenderWindowInteractor* interactor);

class SliderCallback : public vtkCallbackCommand
{
public:
  static SliderCallback* New()
  {
    return new SliderCallback();
  }
  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);
    double value = static_cast<vtkSliderRepresentation2D*>(
                       sliderWidget->GetRepresentation())
                       ->GetValue();
    double center[3];
    this->pointSource->GetCenter(center);
    center[axis] = value;
    this->pointSource->SetCenter(center);
    sphereSource->SetCenter(center);
    pointSource->Modified();
    pointSource->Update();
    std::this_thread::sleep_for(std::chrono::milliseconds(delay));
  }

  SliderCallback() = default;

public:
  unsigned int axis = 0;
  unsigned int delay = 500;
  vtkPointSource* pointSource = nullptr;
  vtkSphereSource* sphereSource = nullptr;
};

} // namespace

int main(int argc, char* argv[])
{
  if (argc < 3)
  {
    std::cerr << "Usage: " << argv[0]
              << "  geometry.wrl velocity.vtu e.g. room_vis.wrl fire_ug.vtu"
              << std::endl;
    return EXIT_FAILURE;
  }
  unsigned int delay = 100;
  if (argc > 3)
  {
    delay = static_cast<unsigned int>(std::abs(atoi(argv[3])));
  }

  vtkNew<vtkNamedColors> colors;
  vtkColor3d isoSurfaceColor = colors->GetColor3d("WhiteSmoke");
  vtkColor3d sphereColor = colors->GetColor3d("HotPink");
  vtkColor3d backgroundColor = colors->GetColor3d("SlateGray");

  vtkNew<vtkRenderer> renderer;
  renderer->UseHiddenLineRemovalOn();

  vtkNew<vtkRenderWindow> renderWindow;
  renderWindow->AddRenderer(renderer);

  vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
  renderWindowInteractor->SetRenderWindow(renderWindow);

  // Import the VRML Files that define the geometry.
  vtkNew<vtkVRMLImporter> vrmlImport;
  vrmlImport->SetRenderWindow(renderWindow);
  vrmlImport->SetFileName(argv[1]);
  vrmlImport->Update();

  // Read the UnstructuredGrid define the solution.
  vtkNew<vtkXMLUnstructuredGridReader> solution;
  solution->SetFileName(argv[2]);
  solution->Update();

  std::array<double, 6> bounds;
  solution->GetOutput()->GetBounds(bounds.data());
  // std::array<double,3> center;
  // for (int i=0, idx=0; i < bounds.size();idx++, i += 2)
  // {
  //   center[idx] = (bounds[i+1]-bounds[i])/2.0;
  // }
  std::array<double, 3> center{3.0, 1.6, 1.25};

  // Create an outline.
  vtkNew<vtkGenericOutlineFilter> outline;
  outline->SetInputConnection(solution->GetOutputPort());

  // Create Seeds.
  vtkNew<vtkPointSource> seeds;
  seeds->SetRadius(0.2);
  seeds->SetCenter(center.data());
  // seeds->SetCenter(3.0, 1.6, 1.25);
  seeds->SetNumberOfPoints(50);

  // Create streamlines.
  vtkNew<vtkStreamTracer> streamTracer;
  streamTracer->SetIntegrationDirectionToBoth();
  streamTracer->SetInputConnection(solution->GetOutputPort());
  streamTracer->SetSourceConnection(seeds->GetOutputPort());
  streamTracer->SetMaximumPropagation(50);
  streamTracer->SetInitialIntegrationStep(.2);
  streamTracer->SetMinimumIntegrationStep(.01);
  streamTracer->SetIntegratorType(2);
  streamTracer->SetComputeVorticity(1);

  vtkNew<vtkTubeFilter> tubes;
  tubes->SetInputConnection(streamTracer->GetOutputPort());
  tubes->SetNumberOfSides(8);
  tubes->SetRadius(.02);
  tubes->SetVaryRadius(0);

  vtkNew<vtkPolyDataMapper> mapTubes;
  mapTubes->SetInputConnection(tubes->GetOutputPort());
  mapTubes->SetScalarRange(solution->GetOutput()->GetScalarRange());

  vtkNew<vtkActor> tubesActor;
  tubesActor->SetMapper(mapTubes);

  // Create an Isosurface.
  vtkNew<vtkContourFilter> isoSurface;
  isoSurface->SetValue(0, 500.0);
  isoSurface->SetInputConnection(solution->GetOutputPort());

  vtkNew<vtkPolyDataMapper> isoSurfaceMapper;
  isoSurfaceMapper->SetInputConnection(isoSurface->GetOutputPort());
  isoSurfaceMapper->ScalarVisibilityOff();

  vtkNew<vtkActor> isoSurfaceActor;
  isoSurfaceActor->SetMapper(isoSurfaceMapper);
  isoSurfaceActor->GetProperty()->SetOpacity(.5);
  isoSurfaceActor->GetProperty()->SetDiffuseColor(isoSurfaceColor.GetData());

  vtkNew<vtkSphereSource> sphere;
  sphere->SetCenter(seeds->GetCenter());
  sphere->SetRadius(seeds->GetRadius());
  sphere->SetThetaResolution(20);
  sphere->SetPhiResolution(11);
  vtkNew<vtkPolyDataMapper> sphereMapper;
  sphereMapper->SetInputConnection(sphere->GetOutputPort());

  vtkNew<vtkActor> sphereActor;
  sphereActor->SetMapper(sphereMapper);
  sphereActor->GetProperty()->SetOpacity(1.0);
  sphereActor->GetProperty()->SetSpecular(.4);
  sphereActor->GetProperty()->SetSpecularPower(80);
  sphereActor->GetProperty()->SetDiffuseColor(sphereColor.GetData());

  renderer->AddActor(tubesActor);
  renderer->AddActor(sphereActor);
  renderer->AddActor(isoSurfaceActor);

  renderer->SetBackground(backgroundColor.GetData());
  renderWindow->SetSize(640, 512);
  renderWindow->SetWindowName("FireFlowDemo");
  renderWindow->Render();

  renderer->GetActiveCamera()->Azimuth(20.0);
  renderer->GetActiveCamera()->Elevation(10.0);
  renderer->GetActiveCamera()->Dolly(1.25);
  renderer->ResetCameraClippingRange();

  // Create widgets to manipulate point source center.
  auto sp = SliderProperties();
  sp.p1[0] = 0.1;
  sp.p1[1] = 0.08;
  sp.p2[0] = 0.3;
  sp.p2[1] = 0.08;
  sp.valueMinimum = bounds[0];
  sp.valueMaximum = bounds[1];
  sp.valueInitial = center[0];
  sp.title = 'X';
  auto widgetX = MakeSliderWidget(sp, renderer, renderWindowInteractor);
  vtkNew<SliderCallback> cbX;
  cbX->axis = 0;
  cbX->pointSource = seeds;
  cbX->sphereSource = sphere;
  cbX->delay = delay;
  widgetX->AddObserver(vtkCommand::InteractionEvent, cbX);

  sp.p1[0] = 0.4;
  sp.p1[1] = 0.08;
  sp.p2[0] = 0.6;
  sp.p2[1] = 0.08;
  sp.valueMinimum = bounds[2];
  sp.valueMaximum = bounds[3];
  sp.valueInitial = center[1];
  sp.title = 'Y';
  auto widgetY = MakeSliderWidget(sp, renderer, renderWindowInteractor);
  vtkNew<SliderCallback> cbY;
  cbY->axis = 1;
  cbY->pointSource = seeds;
  cbY->sphereSource = sphere;
  cbY->delay = delay;
  widgetY->AddObserver(vtkCommand::InteractionEvent, cbY);

  sp.p1[0] = 0.7;
  sp.p1[1] = 0.08;
  sp.p2[0] = 0.9;
  sp.p2[1] = 0.08;
  sp.valueMinimum = bounds[4];
  sp.valueMaximum = bounds[5];
  sp.valueInitial = center[2];
  sp.title = 'Z';
  auto widgetZ = MakeSliderWidget(sp, renderer, renderWindowInteractor);
  vtkNew<SliderCallback> cbZ;
  cbZ->axis = 2;
  cbZ->pointSource = seeds;
  cbZ->sphereSource = sphere;
  cbZ->delay = delay;
  widgetZ->AddObserver(vtkCommand::InteractionEvent, cbZ);

  renderWindow->Render();
  vtkNew<vtkInteractorStyleTrackballCamera> style;
  renderWindowInteractor->SetInteractorStyle(style);

  renderWindowInteractor->Start();

  return EXIT_SUCCESS;
}

namespace {
vtkNew<vtkSliderWidget> MakeSliderWidget(SliderProperties const& sp,
                                         vtkRenderer* renderer,
                                         vtkRenderWindowInteractor* interactor)
{
  vtkNew<vtkSliderRepresentation2D> slider;

  slider->SetMinimumValue(sp.valueMinimum);
  slider->SetMaximumValue(sp.valueMaximum);
  slider->SetValue(sp.valueInitial);
  slider->SetTitleText(sp.title.c_str());

  slider->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedDisplay();
  slider->GetPoint1Coordinate()->SetValue(sp.p1[0], sp.p1[1]);
  slider->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedDisplay();
  slider->GetPoint2Coordinate()->SetValue(sp.p2[0], sp.p2[1]);

  slider->SetTubeWidth(sp.tubeWidth);
  slider->SetSliderLength(sp.sliderLength);
  slider->SetSliderWidth(sp.sliderWidth);
  slider->SetEndCapLength(sp.endCapLength);
  slider->SetEndCapWidth(sp.endCapWidth);
  slider->SetTitleHeight(sp.titleHeight);
  slider->SetLabelHeight(sp.labelHeight);

  vtkNew<vtkNamedColors> colors;
  // Change the color of the text displaying the value.
  slider->GetTitleProperty()->SetColor(
      colors->GetColor3d(sp.titleColor).GetData());
  slider->GetLabelProperty()->SetColor(
      colors->GetColor3d(sp.labelColor).GetData());
  // Set the colors of the slider components.
  slider->GetTubeProperty()->SetColor(
      colors->GetColor3d(sp.barColor).GetData());
  // Change the color of the ends of the bar.
  slider->GetCapProperty()->SetColor(
      colors->GetColor3d(sp.barEndsColor).GetData());
  // Change the color of the knob that slides.
  slider->GetSliderProperty()->SetColor(
      colors->GetColor3d(sp.sliderColor).GetData());
  // Change the color of the knob when the mouse is held on it.
  slider->GetSelectedProperty()->SetColor(
      colors->GetColor3d(sp.selectedColor).GetData());

  vtkNew<vtkSliderWidget> sliderWidget;
  sliderWidget->SetRepresentation(slider);
  sliderWidget->SetInteractor(interactor);
  sliderWidget->SetAnimationModeToAnimate();
  sliderWidget->SetNumberOfAnimationSteps(10);
  sliderWidget->EnabledOn();

  return sliderWidget;
}

} // namespace
