//=============================================================================
// 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/bridge/polygon/operators/ForceCreateFace.h"

#include "smtk/bridge/polygon/Session.h"
#include "smtk/bridge/polygon/internal/Config.h"
#include "smtk/bridge/polygon/internal/Model.h"
#include "smtk/bridge/polygon/internal/Edge.h"
#include "smtk/bridge/polygon/internal/Util.h"

#include "smtk/common/UnionFind.h"

#include "smtk/io/Logger.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/StringItem.h"

#include "smtk/model/Tessellation.h"
#include "smtk/model/Edge.h"
#include "smtk/model/EdgeUse.h"
#include "smtk/model/Face.h"
#include "smtk/model/FaceUse.h"
#include "smtk/model/Loop.h"
#include "smtk/model/Manager.txx"

#include "smtk/bridge/polygon/ForceCreateFace_xml.h"

#include "boost/polygon/polygon.hpp"

#include <deque>
#include <map>
#include <set>
#include <vector>

namespace poly = boost::polygon;
using namespace boost::polygon::operators;

namespace smtk {
  namespace bridge {
    namespace polygon {

smtk::model::OperatorResult ForceCreateFace::operateInternal()
{
  smtk::attribute::DoubleItem::Ptr pointsItem = this->findDouble("points");
  smtk::attribute::IntItem::Ptr coordinatesItem = this->findInt("coordinates");
  smtk::attribute::IntItem::Ptr offsetsItem = this->findInt("offsets");

  int numCoordsPerPt = coordinatesItem->value(0);

  smtk::attribute::ModelEntityItem::Ptr modelItem = this->specification()->associations();
  smtk::model::Model model;

  bool ok = true;

  Session* sess = this->polygonSession();
  smtk::model::ManagerPtr mgr;
  if (!sess || !(mgr = sess->manager()))
    {
    // error logging requires mgr...
    return this->createResult(smtk::model::OPERATION_FAILED);
    }

  model = modelItem->value(0).as<smtk::model::Model>();
  internal::pmodel::Ptr pmodel = this->findStorage<internal::pmodel>(model.entity());
  if (!pmodel)
    {
    smtkErrorMacro(this->log(), "The associated model is not a polygon-session model.");
    return this->createResult(smtk::model::OPERATION_FAILED);
    }

  if (pointsItem->numberOfValues() % numCoordsPerPt > 0)
    {
    smtkErrorMacro(this->log(),
      "Number of point-coordinates (" << pointsItem->numberOfValues() << ") " <<
      "not a multiple of the number of coordinates per pt (" << numCoordsPerPt << ")");
    return this->createResult(smtk::model::OPERATION_FAILED);
    }

  // I. Map user point coordinates into model points.
  std::vector<double>::const_iterator cit; // point-coordinate iterator
  std::vector<internal::Point> polypts;
  polypts.reserve(pointsItem->numberOfValues() / numCoordsPerPt);
  for (cit = pointsItem->begin(); cit != pointsItem->end(); cit += numCoordsPerPt)
    {
    internal::Point pt = pmodel->projectPoint(cit, cit + numCoordsPerPt);
    if (!polypts.empty() && polypts.back() == pt)
      {
      continue;
      }
    polypts.push_back(pt);
    }

  // II. Invoke boost's create_polygon to get actual edges to create.
  int oidx = 0;
  std::size_t numOffsets = offsetsItem->numberOfValues();
  std::size_t numPts = polypts.size();
  int offset = offsetsItem->value(oidx);
  if (offset == 0)
    {
    ++oidx;
    offset = (oidx == numOffsets) ?
      polypts.size() :
      offsetsItem->value(oidx);
    }
  if (offset <= 3 || offset > static_cast<int>(polypts.size()))
    {
    smtkErrorMacro(this->log(), "Invalid offset (" << offset << ") or too few points (" << polypts.size() << ").");
    return this->createResult(smtk::model::OPERATION_FAILED);
    }

  poly::polygon_with_holes_data<internal::Coord> pface;
  pface.set(polypts.begin(), polypts.begin() + offset);

  if (offset < static_cast<int>(numPts))
    {
    std::vector<poly::polygon_data<internal::Coord> > holes;
    std::vector<int> holeOffsets(offsetsItem->begin() + oidx, offsetsItem->end());
    holeOffsets.push_back(pointsItem->numberOfValues() / numCoordsPerPt);
    std::vector<int>::const_iterator oit; // point-coordinate iterator
    for (oit = holeOffsets.begin(); oit != holeOffsets.end(); ++oit)
      {
      if (*oit == offset)
        continue;

      poly::polygon_data<internal::Coord> loop;
      loop.set(polypts.begin() + offset, polypts.begin() + *oit);
      holes.push_back(loop);

      offset = *oit;
      }
    pface.set_holes(holes.begin(), holes.end());
    }
  // III. Create edges
  smtk::model::Operator::Ptr ceo = sess->op("create edge");
  if (!ceo)
    {
    smtkErrorMacro(this->log(), "Could not create model edge operator.");
    return this->createResult(smtk::model::OPERATION_FAILED);
    }
  ceo->associateEntity(model);
  ceo->findInt("construction method")->setDiscreteIndex(0); // construct from points, not vertices
  ceo->findInt("coordinates")->setValue(0, numCoordsPerPt);
  ceo->findInt("offsets")->setValues(offsetsItem->begin(), offsetsItem->end());
  ceo->findDouble("points")->setValues(pointsItem->begin(), pointsItem->end());
  smtk::model::OperatorResult edgeResult = ceo->operate();
  if (edgeResult->findInt("outcome")->value(0) != smtk::model::OPERATION_SUCCEEDED)
    {
    smtkErrorMacro(this->log(), "Could not create model edge(s) of face.");
    return this->createResult(smtk::model::OPERATION_FAILED);
    }
  smtk::attribute::ModelEntityItem::Ptr created = edgeResult->findModelEntity("created");

  // IV. Transcribe face (uses, loops, face) to smtk
  //
  // Make up some UUIDs for the new entities.
  std::vector<std::pair<smtk::model::Edge, bool> > orientedLoopEdges;
  smtk::model::Edge outerEdge = created->value(0).as<smtk::model::Edge>();
  orientedLoopEdges.push_back(std::make_pair(outerEdge, true));
  smtk::common::UUID modelFaceId = mgr->unusedUUID();
  smtk::common::UUID modelFaceUseId = mgr->unusedUUID();
  smtk::common::UUID outerLoopId = mgr->unusedUUID();
  if (!mgr->insertModelFaceWithOrientedOuterLoop(modelFaceId, modelFaceUseId, outerLoopId, orientedLoopEdges))
    {
    smtkErrorMacro(this->log(), "Could not create SMTK outer loop of face.");
    return this->createResult(smtk::model::OPERATION_FAILED);
    }
  smtk::model::Face modelFace(mgr, modelFaceId);
  model.removeCell(outerEdge);
  model.addCell(modelFace);
  modelFace.assignDefaultName();

  // Now transcribe inner-loop edges:
  int numInner = static_cast<int>(created->numberOfValues());
  for (int inner = 1; inner < numInner; ++inner)
    {
    orientedLoopEdges.clear();
    smtk::model::Edge innerEdge = created->value(inner).as<smtk::model::Edge>();
    orientedLoopEdges.push_back(std::make_pair(innerEdge, true));
    smtk::common::UUID innerLoopId = mgr->unusedUUID();
    if (!mgr->insertModelFaceOrientedInnerLoop(innerLoopId, outerLoopId, orientedLoopEdges))
      {
      smtkErrorMacro(this->log(), "Could not create SMTK inner loop of face.");
      return this->createResult(smtk::model::OPERATION_FAILED);
      }
    model.removeCell(innerEdge);
    }

  // V. Add tessellation
  poly::polygon_set_data<internal::Coord> polys;
  std::vector<poly::polygon_data<internal::Coord> > tess;
  polys += pface;
  get_trapezoids(tess, polys);
  std::vector<poly::polygon_data<internal::Coord> >::const_iterator pit;
  smtk::model::Tessellation blank;
  smtk::model::UUIDsToTessellations::iterator smtkTess =
    mgr->setTessellation(modelFaceId, blank);
  double smtkPt[3];
  for (pit = tess.begin(); pit != tess.end(); ++pit)
    {
    //std::cout << "Fan\n";
    poly::polygon_data<internal::Coord>::iterator_type pcit;
    pcit = poly::begin_points(*pit);
    std::vector<int> triConn;
    triConn.resize(4);
    triConn[0] = smtk::model::TESS_TRIANGLE;
    pmodel->liftPoint(*pcit, &smtkPt[0]);
    triConn[1] = smtkTess->second.addCoords(&smtkPt[0]);
    //std::cout << "  " << triConn[1] << "  " << smtkPt[0] << " " << smtkPt[1] << " " << smtkPt[2] << "\n";
    ++pcit;
    pmodel->liftPoint(*pcit, &smtkPt[0]);
    triConn[3] = smtkTess->second.addCoords(&smtkPt[0]);
    ++pcit;
    //std::cout << "  " << triConn[3] << "  " << smtkPt[0] << " " << smtkPt[1] << " " << smtkPt[2] << "\n";
    for (; pcit != poly::end_points(*pit); ++pcit)
      {
      triConn[2] = triConn[3];
      pmodel->liftPoint(*pcit, &smtkPt[0]);
      triConn[3] = smtkTess->second.addCoords(&smtkPt[0]);
      //std::cout << "  " << triConn[3] << "  " << smtkPt[0] << " " << smtkPt[1] << " " << smtkPt[2] << "\n";
      smtkTess->second.insertNextCell(triConn);
      }
    //std::cout << "\n";
    }

  smtk::model::OperatorResult result;
  if (ok)
    {
    result = this->createResult(smtk::model::OPERATION_SUCCEEDED);
    this->addEntityToResult(result, modelFace, CREATED);
    }

  if (!result)
    {
    result = this->createResult(smtk::model::OPERATION_FAILED);
    }

  return result;
}

    } // namespace polygon
  } //namespace bridge
} // namespace smtk

smtkImplementsModelOperator(
  SMTKPOLYGONSESSION_EXPORT,
  smtk::bridge::polygon::ForceCreateFace,
  polygon_force_create_face,
  "force create face",
  ForceCreateFace_xml,
  smtk::bridge::polygon::Session);
