//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#include "smtk/extension/vtk/source/vtkModelRepresentation.h"
#include "smtk/extension/vtk/source/vtkModelSource.h"
#include "smtk/extension/vtk/source/vtkModelView.h"
#include "smtk/io/LoadJSON.h"

#include "smtk/model/Manager.h"

#include "vtkAnnotationLink.h"
#include "vtkCommand.h"
#include "vtkDataSetAttributes.h"
#include "vtkInteractorStyleSwitch.h"
#include "vtkNew.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkPolyDataWriter.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderedSurfaceRepresentation.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkTextProperty.h"
#include "vtkViewTheme.h"

#include "vtkRegressionTestImage.h"

using smtk::shared_ptr;
using namespace smtk::common;
using namespace smtk::model;
using namespace smtk::io;

void applyPublicationTheme(vtkModelView* view)
{
  vtkNew<vtkViewTheme> theme;
  theme->SetPointSize(7);
  theme->SetLineWidth(2);
  theme->SetBackgroundColor(1., 1., 1.);
  theme->SetBackgroundColor2(1., 1., 1.);
  theme->GetPointTextProperty()->SetColor(0, 0, 0);
  theme->GetCellTextProperty()->SetColor(.7, .7, 1);
  theme->SetPointColor(0.5, 0.5, 0.5);
  theme->SetPointHueRange(0.667, 0);
  theme->SetCellColor(0.85, 0.85, 0.85);
  theme->SetCellOpacity(1.);
  theme->SetCellHueRange(0.667, 0);
  theme->SetCellAlphaRange(1, 1);
  theme->SetCellValueRange(0.5, 1);
  theme->SetCellSaturationRange(0.5, 1);
  theme->SetOutlineColor(0.8, 0.4, 0.4);
  theme->SetSelectedPointColor(0.8, 0.6, 0.4);
  theme->SetSelectedCellColor(0.8, 0.6, 0.6);
  view->ApplyViewTheme(theme.GetPointer());
}

// An observer that prints out selected model faces and
// switches interaction modes when the "m" key is pressed.
// (By default, mouse motion moves the camera but this
//  is toggled to selecting model faces with the "m" key.)
class vtkModelSelectionHelper : public vtkCommand
{
public:
  static vtkModelSelectionHelper* New() { return new vtkModelSelectionHelper; }
  vtkModelSelectionHelper() {}
  ~vtkModelSelectionHelper() {}
  void PrintSelectionMask(int mask)
  {
    cout << "Selecting:" << (mask & smtk::model::DIMENSION_0 ? " Vertices" : "")
         << (mask & smtk::model::DIMENSION_1 ? " Edges" : "")
         << (mask & smtk::model::DIMENSION_2 ? " Faces" : "")
         << (mask & smtk::model::DIMENSION_3 ? " Volumes" : "") << "\n";
  }
  virtual void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData))
  {
    /*
    cout
      << "Event " << eventId << " caller " << caller
      << " (" << caller->GetClassName() << ")"
      << " data " << callData << "\n";
     */
    if (eventId == vtkCommand::KeyPressEvent)
    {
      vtkRenderWindowInteractor* iren = vtkRenderWindowInteractor::SafeDownCast(caller);
      if (iren)
      {
        char key = iren->GetKeySym()[0];
        if (key == 'm')
        {
          this->SwitchInteractors();
          this->PrintSelectionMask(this->Representation->GetSelectionMask());
        }
        else if (key == 'd' || key == 'D')
        {
          int mask = this->Representation->GetSelectionMask();
          mask = (mask + 1) % (smtk::model::DIMENSION_0 + smtk::model::DIMENSION_1 +
                                smtk::model::DIMENSION_2 + smtk::model::DIMENSION_3 + 1);
          if (mask == 0)
            ++mask; // Don't allow "select nothing"
          this->Representation->SetSelectionMask(mask);
          this->PrintSelectionMask(mask);
        }
        else if (key == 'q' || key == 'e')
        {
          // A quirk of keeping 2 interactors around is that only one's
          // TerminateApp() method actually stops processing the event
          // loop (the one on which Start() was called).
          this->CameraInteractor->TerminateApp();
        }
      }
    }
    else if (eventId == vtkCommand::SelectionChangedEvent)
    {
      vtkSelection* selection = this->Representation->GetAnnotationLink()->GetCurrentSelection();
      vtkSelectionNode* node = selection->GetNode(0);
      if (selection && node && selection->GetNumberOfNodes() == 1 &&
        node->GetContentType() == vtkSelectionNode::PEDIGREEIDS)
      {
        cout << "Selection changed to [\n";
        vtkIndent indent;
        vtkStringArray* uuids =
          vtkStringArray::SafeDownCast(node->GetSelectionData()->GetAbstractArray(0));
        if (uuids)
        {
          vtkIdType nids = uuids->GetNumberOfTuples();
          std::set<std::string> uniques;
          for (vtkIdType i = 0; i < nids; ++i)
          {
            uniques.insert(uuids->GetValue(i));
          }
          std::set<std::string>::iterator it;
          for (it = uniques.begin(); it != uniques.end(); ++it)
          {
            cout << indent << *it << "  "
                 << (this->Manager ? this->Manager->name(smtk::common::UUID(*it)) : "--") << "\n";
          }
        }
        cout << "]\n";
      }
      else if (selection && !node)
      {
        cout << "Selection cleared\n";
      }
    }
  }

  void SetSelectionInteractor(vtkRenderWindowInteractor* si)
  {
    if (this->SelectionInteractor == si)
    {
      return;
    }
    if (this->SelectionInteractor)
    {
      this->SelectionInteractor->RemoveObserver(this);
    }
    this->SelectionInteractor = si;
    if (this->SelectionInteractor)
    {
      this->SelectionInteractor->AddObserver(vtkCommand::KeyPressEvent, this);
    }
  }

  void SetCameraInteractor(vtkRenderWindowInteractor* si)
  {
    if (this->CameraInteractor == si)
    {
      return;
    }
    if (this->CameraInteractor)
    {
      this->CameraInteractor->RemoveObserver(this);
    }
    this->CameraInteractor = si;
    if (this->CameraInteractor)
    {
      this->CameraInteractor->AddObserver(vtkCommand::KeyPressEvent, this);
    }
  }

  void SetRenderWindow(vtkRenderWindow* rw)
  {
    if (this->RenderWindow == rw)
    {
      return;
    }
    this->RenderWindow = rw;
  }

  void SetRepresentation(vtkModelRepresentation* rep)
  {
    if (this->Representation == rep)
    {
      return;
    }
    this->Representation = rep;
  }

  void SetManager(smtk::model::ManagerPtr sm) { this->Manager = sm; }

  void SwitchInteractors()
  {
    if (this->RenderWindow->GetInteractor() == this->CameraInteractor)
    {
      this->RenderWindow->SetInteractor(this->SelectionInteractor);
    }
    else
    {
      this->RenderWindow->SetInteractor(this->CameraInteractor);
    }
  }

  vtkSmartPointer<vtkRenderWindowInteractor> GetSelectionInteractor()
  {
    return this->SelectionInteractor;
  }

  vtkSmartPointer<vtkRenderWindowInteractor> GetCameraInteractor()
  {
    return this->CameraInteractor;
  }

protected:
  vtkSmartPointer<vtkRenderWindowInteractor> CameraInteractor;
  vtkSmartPointer<vtkRenderWindowInteractor> SelectionInteractor;
  vtkSmartPointer<vtkRenderWindow> RenderWindow;
  vtkSmartPointer<vtkModelRepresentation> Representation;
  smtk::model::ManagerPtr Manager;
};

int main(int argc, char* argv[])
{
  int debug = argc > 2 ? (argv[2][0] == '-' ? 0 : 1) : 0;
  std::ifstream file(argc > 1 ? argv[1] : "smtkModel.json");
  if (!file.good())
  {
    cout << "Could not open file \"" << (argc > 1 ? argv[1] : "smtkModel.json") << "\".\n\n"
         << "Usage:\n  " << argv[0] << " [[filename] debug]\n"
         << "where\n"
         << "  filename is the path to a JSON model.\n"
         << "  debug    is any character other than '-'; its presence turns the test into an "
            "interactive demo.\n\n";
    return 1;
  }
  if (debug)
  {
    cout << "\n\n"
         << "Press 'm' to switch interaction modes (camera motion vs selection)\n"
         << "      'q' to exit\n\n";
  }
  std::string data((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));

  ManagerPtr sm = smtk::model::Manager::create();

  int status = !LoadJSON::intoModelManager(data.c_str(), sm);
  if (!status)
  {
    vtkNew<vtkModelView> view;
    vtkNew<vtkModelSource> src;
    vtkNew<vtkModelRepresentation> rep;
    vtkModelSelectionHelper* hlp = NULL;
    view->SetDisplayHoverText(0);
    if (debug)
    {
      hlp = vtkModelSelectionHelper::New();
      view->GetRenderWindow()->SetMultiSamples(16);
    }
    else
    {
      view->GetRenderWindow()->SetMultiSamples(0);
    }
    EntityRefs thingsToDraw = sm->entitiesMatchingFlagsAs<EntityRefs>(smtk::model::MODEL_ENTITY);
    src->SetEntities(thingsToDraw);
    rep->SetModel(sm);
    rep->SetSelectionMask(smtk::model::DIMENSION_1);
    rep->SetInputConnection(src->GetOutputPort());
    rep->SetSelectionType(vtkSelectionNode::PEDIGREEIDS);

    view->AddRepresentation(rep.GetPointer());
    view->ResetCamera();
    view->ResetCameraClippingRange();

    if (hlp)
    {
      rep->AddObserver(vtkCommand::SelectionChangedEvent, hlp);
      hlp->SetSelectionInteractor(view->GetRenderWindow()->GetInteractor());
      vtkRenderWindowInteractor* iac = view->GetRenderWindow()->MakeRenderWindowInteractor();
      vtkInteractorStyleSwitch::SafeDownCast(iac->GetInteractorStyle())
        ->SetCurrentStyleToTrackballCamera();
      view->GetRenderWindow()->SetInteractor(iac);
      hlp->SetCameraInteractor(iac);
      hlp->SetRenderWindow(view->GetRenderWindow());
      hlp->SetRepresentation(rep.GetPointer());
      hlp->SetManager(sm);
    }

    view->Render();
    view->ResetCamera();
    view->ResetCameraClippingRange();

    status = !vtkRegressionTestImage(view->GetRenderWindow());

    if (debug)
    {
      sm->assignDefaultNames();
      applyPublicationTheme(view.GetPointer());
      view->GetInteractor()->Start();
      hlp->GetSelectionInteractor()->RemoveAllObservers();
      hlp->GetCameraInteractor()->RemoveAllObservers();
      hlp->SetCameraInteractor(NULL);
      hlp->SetRenderWindow(NULL);
      hlp->SetRepresentation(NULL);
      hlp->SetSelectionInteractor(NULL);
      hlp->Delete();
    }
    view->RemoveAllRepresentations();
  }

  return status;
}
