#include <yandex/maps/wiki/topo/common.h>

#include <yandex/maps/wiki/topo/exception.h>

namespace maps {
namespace wiki {
namespace topo {

std::ostream& operator << (std::ostream& out, const IncidenceType& incidenceType)
{
    out << (incidenceType == IncidenceType::Start ? "start" : "end");
    return out;
}

class TopologyRestrictions::TopologyRestrictionsImpl
{
public:
    TopologyRestrictionsImpl(
            double tolerance, 
            double junctionGravity,
            double vertexGravity,
            double groupJunctionGravity,
            double groupJunctionSnapVertexGravity,
            const Limits<double>& edgeSegmentLengthLimits,
            const Limits<double>& edgeLengthLimits,
            boost::optional<size_t> maxIntersectionsWithEdge,
            boost::optional<size_t> maxIntersectedEdges,
            MergeOfOverlappedEdgesPolicy mergeOfOverlappedEdgesPolicy)
        : tolerance(tolerance)
        , junctionGravity(junctionGravity)
        , vertexGravity(vertexGravity)
        , groupJunctionGravity(groupJunctionGravity)
        , groupJunctionSnapVertexGravity(groupJunctionSnapVertexGravity)
        , edgeSegmentLengthLimits(edgeSegmentLengthLimits)
        , edgeLengthLimits(edgeLengthLimits)
        , maxIntersectionsWithEdge(maxIntersectionsWithEdge)
        , maxIntersectedEdges(maxIntersectedEdges)
        , mergeOfOverlappedEdgesPolicy(mergeOfOverlappedEdgesPolicy)
    {
        if (vertexGravity < tolerance) {
            throw GeomProcessingError(ErrorCode::InvalidRestrictions)
                << " vertex gravity is less than tolerance";
        }        
        if (junctionGravity < vertexGravity) {
            throw GeomProcessingError(ErrorCode::InvalidRestrictions)
                << " junction gravity is less than tolerance";
        }        
        if (groupJunctionSnapVertexGravity < tolerance) {
            throw GeomProcessingError(ErrorCode::InvalidRestrictions)
                << " group junction snap to vertex is less than tolerance";
        }        
        if (groupJunctionGravity < groupJunctionSnapVertexGravity) {
            throw GeomProcessingError(ErrorCode::InvalidRestrictions)
                << " group junction gravity is less than group junction snap to vertex";
        }        
        if (edgeSegmentLengthLimits.min < tolerance) {
            throw GeomProcessingError(ErrorCode::InvalidRestrictions)
                << " min segment length is less than tolerance";
        }
        if (edgeLengthLimits.min < edgeSegmentLengthLimits.min) {
            throw GeomProcessingError(ErrorCode::InvalidRestrictions)
                << " min edge length is less than min segment length";
        }
    }

    double tolerance;
    double junctionGravity;
    double vertexGravity;
    double groupJunctionGravity;
    double groupJunctionSnapVertexGravity;
    Limits<double> edgeSegmentLengthLimits;
    Limits<double> edgeLengthLimits;
    boost::optional<size_t> maxIntersectionsWithEdge;
    boost::optional<size_t> maxIntersectedEdges;
    MergeOfOverlappedEdgesPolicy mergeOfOverlappedEdgesPolicy;
};

TopologyRestrictions::TopologyRestrictions(
        double tolerance, 
        double junctionGravity,
        double vertexGravity,
        double groupJunctionGravity,
        double groupJunctionSnapVertex,
        const Limits<double>& edgeSegmentLengthLimits,
        const Limits<double>& edgeLengthLimits,
        boost::optional<size_t> maxIntersectionsWithEdge,
        boost::optional<size_t> maxIntersectedEdges,
        MergeOfOverlappedEdgesPolicy mergeOfOverlappedEdgesPolicy)
    : impl_(new TopologyRestrictionsImpl(
                    tolerance, junctionGravity, vertexGravity, groupJunctionGravity,
                    groupJunctionSnapVertex, edgeSegmentLengthLimits, edgeLengthLimits,
                    maxIntersectionsWithEdge, maxIntersectedEdges, mergeOfOverlappedEdgesPolicy
                )
            )
{}

TopologyRestrictions::TopologyRestrictions(const TopologyRestrictions& oth)
    : impl_(new TopologyRestrictionsImpl(*oth.impl_))
{}

TopologyRestrictions::TopologyRestrictions(TopologyRestrictions&& oth)
    : impl_(std::move(oth.impl_))
{}

TopologyRestrictions&
TopologyRestrictions::operator = (const TopologyRestrictions& oth)
{
    *impl_ = *oth.impl_;
    return *this;
}

TopologyRestrictions&
TopologyRestrictions::operator = (TopologyRestrictions&& oth)
{
    impl_.reset(oth.impl_.release());
    return *this;
}

TopologyRestrictions::~TopologyRestrictions()
{}

boost::optional<size_t>
TopologyRestrictions::maxIntersectedEdges() const
{
    return impl_->maxIntersectedEdges;
}

boost::optional<size_t>
TopologyRestrictions::maxIntersectionsWithEdge() const
{
    return impl_->maxIntersectionsWithEdge;
}

bool
TopologyRestrictions::isMergeOfOverlappedEdgesAllowed() const
{
    return impl_->mergeOfOverlappedEdgesPolicy == MergeOfOverlappedEdgesPolicy::Allowed;
}

double
TopologyRestrictions::tolerance() const
{
    return impl_->tolerance;
}

double
TopologyRestrictions::junctionGravity() const
{
    return impl_->junctionGravity;
}

double
TopologyRestrictions::vertexGravity() const
{
    return impl_->vertexGravity;
}

double
TopologyRestrictions::groupJunctionGravity() const
{
    return impl_->groupJunctionGravity;
}

double
TopologyRestrictions::groupJunctionSnapVertexGravity() const
{
    return impl_->groupJunctionSnapVertexGravity;
}

const Limits<double>&
TopologyRestrictions::edgeSegmentLengthLimits() const
{
    return impl_->edgeSegmentLengthLimits;
}

const Limits<double>&
TopologyRestrictions::edgeLengthLimits() const
{
    return impl_->edgeLengthLimits;
}

void
TopologyRestrictions::setMaxIntersectionsWithEdge(boost::optional<size_t> count)
{
    impl_->maxIntersectionsWithEdge = count;
}

void
TopologyRestrictions::setMaxIntersectedEdges(boost::optional<size_t> count)
{
    impl_->maxIntersectedEdges = count;
}


bool operator==(const IncidentNodes& lhs, const IncidentNodes& rhs)
{
    return lhs.start == rhs.start && lhs.end == rhs.end;
}

} // namespace topo
} // namespace wiki
} // namespace maps
