MODULE = Geo::Geos                PACKAGE = Geo::Geos::Operation
PROTOTYPES: DISABLE

Sv buffer(Geometry& g, double distance, SV* quadrantSegments = NULL, SV* endCapStyle = NULL) {
    int vQuadrantSegments = quadrantSegments ? SvIV(quadrantSegments) : (int)BufferParameters::DEFAULT_QUADRANT_SEGMENTS;
    int vEndCapStyle = endCapStyle? SvIV(endCapStyle) : (int)BufferParameters::CAP_ROUND;
    Geometry* r = BufferOp::bufferOp(&g, distance, vQuadrantSegments, vEndCapStyle);
    RETVAL = Helper::uplift(r);
}

double distance(Geometry& g0, Geometry& g1) {
    RETVAL = DistanceOp::distance(&g0, &g1);
}

Array nearestPoints(Geometry& g0, Geometry& g1) {
    Array r;
    auto seq = DistanceOp::nearestPoints(&g0, &g1);
    if(seq) {
        auto seq_ptr = std::unique_ptr<CoordinateSequence>(seq);
        r = Helper::convert_copy(seq);
    }
    RETVAL = r;
}

Array closestPoints(Geometry& g0, Geometry& g1) {
    Array r;
    auto seq = DistanceOp::closestPoints(&g0, &g1);
    if(seq) {
        auto seq_ptr = std::unique_ptr<CoordinateSequence>(seq);
        r = Helper::convert_copy(seq);
    }
    RETVAL = r;
}

Sv overlayOp(Geometry& g0, Geometry& g1, int opCode) {
    if (opCode > 4) throw "wrong opCode";
    OverlayOp::OpCode code = static_cast<OverlayOp::OpCode>(opCode);
    Geometry* g = OverlayOp::overlayOp(&g0, &g1, code);
    RETVAL = Helper::uplift(g);
}

bool isValid(Object obj) {
    if (obj.stash().name() == "Geo::Geos::Coordinate") {
        auto& c = xs::in<Coordinate&>(obj);
        RETVAL = IsValidOp::isValid(c);
    }
    else {
        auto& g = xs::in<Geometry&>(obj);
        RETVAL = IsValidOp::isValid(g);
    }
}

IntersectionMatrix* relate(Geometry& g0, Geometry& g1, SV* boundaryNodeRule = NULL) {
    if (!boundaryNodeRule) RETVAL = RelateOp::relate(&g0, &g1);
    else {
        const BoundaryNodeRule* rule;
        auto rule_id = SvIV(boundaryNodeRule);
        switch(rule_id) {
            case 0: rule = &BoundaryNodeRule::getBoundaryRuleMod2(); break;
            case 1: rule = &BoundaryNodeRule::getBoundaryEndPoint(); break;
            case 2: rule = &BoundaryNodeRule::getBoundaryMultivalentEndPoint(); break;
            case 3: rule = &BoundaryNodeRule::getBoundaryMonovalentEndPoint(); break;
            case 4: rule = &BoundaryNodeRule::getBoundaryOGCSFS(); break;
            default: throw("Wrong boundaryNodeRule");
        }
        RETVAL = RelateOp::relate(&g0, &g1, *rule);
    }
}

Array mergeLines(Array in) {
    LineMerger lm;

    for(auto it: in) {
        lm.add(&xs::in<Geometry&>(it));
    }

    auto v = lm.getMergedLineStrings();
    Array out = Array::create(v->size());
    for(LineString* it: *v) {
        auto wrapped = xs::out<LineString*>(it);
        out.push(wrapped);
    }
    RETVAL  = out;
    delete v;
}

bool isSequenced(Geometry& g) {
    RETVAL = LineSequencer::isSequenced(&g);
}

Sv sequence(Geometry& g) {
    RETVAL = Helper::uplift(LineSequencer::sequence(g));
}

BOOT {
    auto this_stash = Stash(__PACKAGE__);
    xs::exp::create_constants(this_stash, {
        {"TYPE_OP_INTERSECTION",         OverlayOp::OpCode::opINTERSECTION},
        {"TYPE_OP_UNION",                OverlayOp::OpCode::opUNION},
        {"TYPE_OP_DIFFERENCE",           OverlayOp::OpCode::opDIFFERENCE},
        {"TYPE_OP_SYMDIFFERENCE",        OverlayOp::OpCode::opSYMDIFFERENCE},

        {"TYPE_BOUNDARY_NODE_RULE_MOD2",            0},
        {"TYPE_BOUNDARY_ENDPOINT",                  1},
        {"TYPE_BOUNDARY_MULTIVALENT_ENDPOINT",      2},
        {"TYPE_BOUNDARY_MONOVALENT_ENDPOINT",       3},
        {"TYPE_BOUNDARY_OGCSFS",                    4}
    });
    xs::exp::autoexport(this_stash);
}