/**********************************************************************
 *
 * GEOS-Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2009-2011  Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2005 Refractions Research Inc.
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Public Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 **********************************************************************
 *
 * Last port: operation/buffer/OffsetCurveBuilder.java r378 (JTS-1.12)
 *
 **********************************************************************/

#include <cassert>
#include <cmath>
#include <vector>

#include <geos/algorithm/CGAlgorithms.h>
#include <geos/algorithm/Angle.h>
#include <geos/operation/buffer/OffsetCurveBuilder.h>
#include <geos/operation/buffer/BufferInputLineSimplifier.h>
#include <geos/operation/buffer/BufferOp.h>
#include <geos/operation/buffer/BufferParameters.h>
#include <geos/geomgraph/Position.h>
#include <geos/geom/CoordinateArraySequence.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/PrecisionModel.h>
#include <geos/algorithm/NotRepresentableException.h>
#include <geos/algorithm/HCoordinate.h>
#include <geos/util.h>
#include <geos/util/IllegalArgumentException.h>

#ifndef GEOS_DEBUG
#define GEOS_DEBUG 0
#endif

using namespace std;
using namespace geos::geomgraph;
using namespace geos::algorithm;
using namespace geos::geom;

namespace geos {
namespace operation { // geos.operation
namespace buffer { // geos.operation.buffer

/*private data*/
const double OffsetCurveBuilder::SIMPLIFY_FACTOR = 100.0;

/*public*/
void
OffsetCurveBuilder::getLineCurve(const CoordinateSequence *inputPts,
		double nDistance, vector<CoordinateSequence*>& lineList)
{
  distance = nDistance;

	// a zero or (non-singlesided) negative width buffer of a line/point is empty
	if (distance == 0.0) return;
  if (distance < 0.0 && ! bufParams.isSingleSided()) return;

  double posDistance = std::abs(distance);

  std::unique_ptr<OffsetSegmentGenerator> segGen = getSegGen(posDistance);
  if (inputPts->getSize() <= 1) {
    computePointCurve(inputPts->getAt(0), *segGen);
  } else {
    if (bufParams.isSingleSided()) {
      bool isRightSide = distance < 0.0;
      computeSingleSidedBufferCurve(*inputPts, isRightSide, *segGen);
    }
    else {
      computeLineBufferCurve(*inputPts, *segGen);
    }
  }

  segGen->getCoordinates(lineList);
}

/* private */
void
OffsetCurveBuilder::computePointCurve(const Coordinate& pt,
                                      OffsetSegmentGenerator& segGen)
{
  switch (bufParams.getEndCapStyle()) {
    case BufferParameters::CAP_ROUND:
      segGen.createCircle(pt, distance);
      break;
    case BufferParameters::CAP_SQUARE:
      segGen.createSquare(pt, distance);
      break;
    default:
      // otherwise curve is empty (e.g. for a butt cap);
      break;
  }
}

/*public*/
void
OffsetCurveBuilder::getSingleSidedLineCurve(const CoordinateSequence* inputPts,
   double distance, vector<CoordinateSequence*>& lineList, bool leftSide,
   bool rightSide)
{
   // A zero or negative width buffer of a line/point is empty.
   if ( distance <= 0.0 ) return ;

   if ( inputPts->getSize() < 2 )
   {
      // No cap, so just return.
      return ;
   }

	double distTol = simplifyTolerance(distance);

  std::unique_ptr<OffsetSegmentGenerator> segGen = getSegGen(distance);

  if ( leftSide ) {
	  //--------- compute points for left side of line
    // Simplify the appropriate side of the line before generating
    std::unique_ptr<CoordinateSequence> simp1_ =
      BufferInputLineSimplifier::simplify( *inputPts, distTol );
    const CoordinateSequence& simp1 = *simp1_;


    int n1 = static_cast<int>(simp1.size()) - 1;
    if ( ! n1 )
      throw util::IllegalArgumentException("Cannot get offset of single-vertex line");
    segGen->initSideSegments(simp1[0], simp1[1], Position::LEFT);
    segGen->addFirstSegment();
    for (int i = 2; i <= n1; ++i) {
      segGen->addNextSegment(simp1[i], true);
    }
    segGen->addLastSegment();
  }

  if ( rightSide ) {

    //---------- compute points for right side of line
    // Simplify the appropriate side of the line before generating
    std::unique_ptr<CoordinateSequence> simp2_ =
      BufferInputLineSimplifier::simplify( *inputPts, -distTol );
    const CoordinateSequence& simp2 = *simp2_;

    int n2 = static_cast<int>(simp2.size()) - 1;
    if ( ! n2 )
      throw util::IllegalArgumentException("Cannot get offset of single-vertex line");
    segGen->initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
    segGen->addFirstSegment();
    for (int i = n2-2; i >= 0; --i) {
      segGen->addNextSegment(simp2[i], true);
    }
    segGen->addLastSegment();
  }

  segGen->getCoordinates(lineList);
}

/*public*/
void
OffsetCurveBuilder::getRingCurve(const CoordinateSequence *inputPts,
		int side, double nDistance,
		vector<CoordinateSequence*>& lineList)
{
  distance = nDistance;

	// optimize creating ring for zero distance
	if (distance == 0.0) {
		lineList.push_back(inputPts->clone());
		return;
	}

	if (inputPts->getSize() <= 2)
	{
		getLineCurve(inputPts, distance, lineList);
		return;
	}

  std::unique_ptr<OffsetSegmentGenerator> segGen = getSegGen(std::abs(distance));
	computeRingBufferCurve(*inputPts, side, *segGen);
  segGen->getCoordinates(lineList);
}

/* private */
double
OffsetCurveBuilder::simplifyTolerance(double bufDistance)
{
	return bufDistance / SIMPLIFY_FACTOR;
}

/*private*/
void
OffsetCurveBuilder::computeLineBufferCurve(const CoordinateSequence& inputPts,
    OffsetSegmentGenerator& segGen)
{
	double distTol = simplifyTolerance(distance);

	//--------- compute points for left side of line
	// Simplify the appropriate side of the line before generating
	std::unique_ptr<CoordinateSequence> simp1_ =
		BufferInputLineSimplifier::simplify(inputPts, distTol);
	const CoordinateSequence& simp1 = *simp1_;


	int n1 = static_cast<int>(simp1.size()) - 1;
	segGen.initSideSegments(simp1[0], simp1[1], Position::LEFT);
	for (int i = 2; i <= n1; ++i) {
		segGen.addNextSegment(simp1[i], true);
	}
	segGen.addLastSegment();
	// add line cap for end of line
	segGen.addLineEndCap(simp1[n1-1], simp1[n1]);

	//---------- compute points for right side of line
	// Simplify the appropriate side of the line before generating
	std::unique_ptr<CoordinateSequence> simp2_ =
		BufferInputLineSimplifier::simplify(inputPts, -distTol);
	const CoordinateSequence& simp2 = *simp2_;

	int n2 = static_cast<int>(simp2.size()) - 1;
	segGen.initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
	for (int i = n2-2; i >= 0; --i) {
		segGen.addNextSegment(simp2[i], true);
	}
	segGen.addLastSegment();
	// add line cap for start of line
	segGen.addLineEndCap(simp2[1], simp2[0]);

	segGen.closeRing();
}

/*private*/
void
OffsetCurveBuilder::computeRingBufferCurve(const CoordinateSequence& inputPts,
    int side, OffsetSegmentGenerator& segGen)
{
  // simplify input line to improve performance
  double distTol = simplifyTolerance(distance);
	// ensure that correct side is simplified
	if (side == Position::RIGHT)
		distTol = -distTol;
	std::unique_ptr<CoordinateSequence> simp_ =
		BufferInputLineSimplifier::simplify(inputPts, distTol);
	const CoordinateSequence& simp = *simp_;

	int n = static_cast<int>(simp.size())-1;
	segGen.initSideSegments(simp[n-1], simp[0], side);
	for (int i = 1; i <= n; i++) {
		bool addStartPoint = i != 1;
		segGen.addNextSegment(simp[i], addStartPoint);
	}
	segGen.closeRing();
}

/*private*/
void
OffsetCurveBuilder::computeSingleSidedBufferCurve(
    const CoordinateSequence& inputPts, bool isRightSide,
    OffsetSegmentGenerator& segGen)
{
	double distTol = simplifyTolerance(distance);

  if ( isRightSide ) {

    // add original line
    segGen.addSegments(inputPts, true);

    //---------- compute points for right side of line
    // Simplify the appropriate side of the line before generating
    std::unique_ptr<CoordinateSequence> simp2_ =
      BufferInputLineSimplifier::simplify(inputPts, -distTol);
    const CoordinateSequence& simp2 = *simp2_;

    int n2 = static_cast<int>(simp2.size()) - 1;
    segGen.initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
    segGen.addFirstSegment();
    for (int i = n2-2; i >= 0; --i) {
      segGen.addNextSegment(simp2[i], true);
    }

  } else {

    // add original line
    segGen.addSegments(inputPts, false);

    //--------- compute points for left side of line
    // Simplify the appropriate side of the line before generating
    std::unique_ptr<CoordinateSequence> simp1_ =
      BufferInputLineSimplifier::simplify(inputPts, distTol);
    const CoordinateSequence& simp1 = *simp1_;

    int n1 = static_cast<int>(simp1.size()) - 1;
    segGen.initSideSegments(simp1[0], simp1[1], Position::LEFT);
    segGen.addFirstSegment();
    for (int i = 2; i <= n1; ++i) {
      segGen.addNextSegment(simp1[i], true);
    }

  }
	segGen.addLastSegment();
	segGen.closeRing();
}

/*private*/
std::unique_ptr<OffsetSegmentGenerator>
OffsetCurveBuilder::getSegGen(double dist)
{
  std::unique_ptr<OffsetSegmentGenerator> osg(
    new OffsetSegmentGenerator(precisionModel, bufParams, dist)
  );
  return osg;
}

} // namespace geos.operation.buffer
} // namespace geos.operation
} // namespace geos

