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

#include <algorithm>

#include "vtkRegressionTestImage.h"
#include "vtkTestUtilities.h"

#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkHardwareSelector.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInteractorStyleRubberBandPick.h"
#include "vtkNew.h"
#include "vtkPolyDataMapper.h"
#include "vtkProp3DCollection.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderedAreaPicker.h"
#include "vtkRenderer.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkSphereSource.h"

class PointPickCommand : public vtkCommand
{
protected:
  std::vector<int> PointIds;
  vtkRenderer* Renderer;
  vtkAreaPicker* Picker;
  vtkPolyDataMapper* Mapper;

public:
  static PointPickCommand* New() { return new PointPickCommand; }
  vtkTypeMacro(PointPickCommand, vtkCommand);

  PointPickCommand() = default;

  ~PointPickCommand() override = default;

  void SetPointIds(vtkSelection* selection)
  {
    // Find selection node that we're interested in:
    const vtkIdType numNodes = selection->GetNumberOfNodes();
    for (vtkIdType nodeId = 0; nodeId < numNodes; ++nodeId)
    {
      vtkSelectionNode* node = selection->GetNode(nodeId);

      // Check if the mapper is this instance of MoleculeMapper
      vtkActor* selActor =
        vtkActor::SafeDownCast(node->GetProperties()->Get(vtkSelectionNode::PROP()));
      if (selActor && (selActor->GetMapper() == this->Mapper))
      {
        // Separate the selection ids into atoms and bonds
        vtkIdTypeArray* selIds = vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList());
        if (selIds)
        {
          vtkIdType numIds = selIds->GetNumberOfTuples();
          for (vtkIdType i = 0; i < numIds; ++i)
          {
            vtkIdType curId = selIds->GetValue(i);
            this->PointIds.push_back(curId);
          }
        }
      }
    }
  }

  std::vector<int>& GetPointIds() { return this->PointIds; }

  void SetMapper(vtkPolyDataMapper* m) { this->Mapper = m; }

  void SetRenderer(vtkRenderer* r) { this->Renderer = r; }

  void SetPicker(vtkAreaPicker* p) { this->Picker = p; }

  void Execute(vtkObject*, unsigned long, void*) override
  {
    vtkProp3DCollection* props = this->Picker->GetProp3Ds();
    if (props->GetNumberOfItems() != 0)
    {
      // If anything was picked during the fast area pick, do a more detailed
      // pick.
      vtkNew<vtkHardwareSelector> selector;
      selector->SetFieldAssociation(vtkDataObject::FIELD_ASSOCIATION_POINTS);
      selector->SetRenderer(this->Renderer);
      selector->SetArea(static_cast<unsigned int>(this->Renderer->GetPickX1()),
        static_cast<unsigned int>(this->Renderer->GetPickY1()),
        static_cast<unsigned int>(this->Renderer->GetPickX2()),
        static_cast<unsigned int>(this->Renderer->GetPickY2()));
      // Make the actual pick and pass the result to the convenience function
      // defined earlier
      vtkSelection* result = selector->Select();
      this->SetPointIds(result);
      this->DumpPointSelection();
      result->Delete();
    }
  }

  // Convenience function to print out the atom and bond ids that belong to
  // molMap and are contained in sel
  void DumpPointSelection()
  {
    // Print selection
    cerr << "\n### Selection ###\n";
    cerr << "Points: ";
    for (std::vector<int>::iterator i = this->PointIds.begin(); i != this->PointIds.end(); ++i)
    {
      cerr << *i << " ";
    }
    cerr << endl;
  }
};

int TestPointSelection(int argc, char* argv[])
{
  // create a line and a mesh
  vtkNew<vtkSphereSource> sphere;

  // Set up render engine
  vtkNew<vtkPolyDataMapper> sphereMapper;
  sphereMapper->SetInputConnection(sphere->GetOutputPort());

  vtkNew<vtkActor> actor;
  actor->SetMapper(sphereMapper);

  vtkNew<vtkRenderer> ren;
  ren->AddActor(actor);
  vtkNew<vtkRenderWindow> win;
  win->SetMultiSamples(0);
  win->AddRenderer(ren);
  vtkNew<vtkRenderWindowInteractor> iren;
  iren->SetRenderWindow(win);

  ren->SetBackground(0.0, 0.0, 0.0);
  win->SetSize(450, 450);
  win->Render();
  ren->GetActiveCamera()->Zoom(1.2);

  // Setup picker
  vtkNew<vtkInteractorStyleRubberBandPick> pickerInt;
  iren->SetInteractorStyle(pickerInt);
  vtkNew<vtkRenderedAreaPicker> picker;
  iren->SetPicker(picker);

  // We'll follow up the cheap RenderedAreaPick with a detailed selection
  // to obtain the atoms and bonds.
  vtkNew<PointPickCommand> com;
  com->SetRenderer(ren);
  com->SetPicker(picker);
  com->SetMapper(sphereMapper);
  picker->AddObserver(vtkCommand::EndPickEvent, com);

  // Make pick -- lower left quarter of renderer
  win->Render();
  picker->AreaPick(0, 0, 225, 225, ren);
  win->Render();

  // Interact if desired
  int retVal = vtkRegressionTestImage(win);
  if (retVal == vtkRegressionTester::DO_INTERACTOR)
  {
    iren->Start();
  }

  // Verify pick
  std::vector<int>& pIds = com->GetPointIds();
  if (pIds.size() < 7 || std::find(pIds.begin(), pIds.end(), 0) == pIds.end() ||
    std::find(pIds.begin(), pIds.end(), 26) == pIds.end() ||
    std::find(pIds.begin(), pIds.end(), 27) == pIds.end() ||
    std::find(pIds.begin(), pIds.end(), 32) == pIds.end() ||
    std::find(pIds.begin(), pIds.end(), 33) == pIds.end() ||
    std::find(pIds.begin(), pIds.end(), 38) == pIds.end() ||
    std::find(pIds.begin(), pIds.end(), 39) == pIds.end())
  {
    cerr << "Incorrect atoms/bonds picked! (if any picks were performed inter"
            "actively this could be ignored).\n";
    return EXIT_FAILURE;
  }

  return !retVal;
}
