#include "Helper.h"

using namespace Geo::Geos;
using namespace xs;
using namespace geos::geom;


static double get_ordinate(const ::geos::geom::Coordinate* c, size_t idx) {
    if (idx == 0) return c->x;
    if (idx == 1) return c->y;
    if (idx == 2) return c->z;
    throw "unknown ordinate";
}

Helper::CoordinateSequence* Helper::convert_copy(Helper::GeometryFactory& factory, Helper::Array coords, size_t dims) {
    if (!coords) throw("wrong argument");
    auto size = coords.size();
    Helper::CoordinateSequence* seq = factory.getCoordinateSequenceFactory()->create(size, dims);
    for(size_t i = 0; i < size; ++i)  {
        auto& coord = xs::in<Coordinate&>(coords[i]);
        for(size_t j = 0; j < dims; ++j) {
            seq->setOrdinate(i, j, get_ordinate(&coord, j));
        }
    }
    return seq;
}

Helper::Array Helper::convert_copy(const Helper::CoordinateSequence* seq) {
    auto size = seq->size();
    auto arr = xs::Array::create(size);
    for(size_t idx = 0; idx < size; ++idx) {
        Coordinate native_coord;
        seq->getAt(idx, native_coord);
        Coordinate* copy_coord = new Coordinate(native_coord);
        auto coord = xs::out<Coordinate*>(copy_coord);
        arr.push(coord);
    }
    return arr;
}

Helper::CoordinateArraySequence Helper::convert_coords(Helper::Array coords) {
    CoordinateArraySequence seq;
    for(const auto& c: coords) {
        seq.add( xs::in<Coordinate&>(c) );
    }
    return seq;
}


Helper::Geometries Helper::convert_geometries(Helper::Array geometries) {
    Geometries result;
    result.reserve(geometries.size());
    for(const auto& item: geometries) {
        auto& g = xs::in<Geometry&>(item);
        result.push_back(&g);
    }
    return result;
}

xs::Array Helper::wrap_inc_SVs(std::vector<void*>* v, Helper::lookup_map_t& lookup_map) {
    if (v) {
        Array result = Array::create(v->size());
        for(auto it: *v) {
            SV* key = static_cast<SV*>(it);
            HE* he = hv_fetch_ent(lookup_map, key, 0, 0);
            if (!he) throw ("Cannot lookup key in map");
            result.push(Sv(HeVAL(he)));
        }
        return result;
    }
    return Array{};
}

SV* Helper::store_sv(SV* item, lookup_map_t& lookup_map) {
    SV* value;
    HE* he = hv_fetch_ent(lookup_map, item, 0, 0);
    if (he) {
       value = HeVAL(he);
    }
    else  {
       value = newSVsv(item);
       auto result = hv_store_ent(lookup_map, value, value, 0);
       if (!result) {
            SvREFCNT_dec(value);
            throw "cannot store value in hash";
        }
   }
    return value;
}

Sv Helper::uplift(::geos::geom::Geometry* g) {
    using namespace panda;
    switch (g->getGeometryTypeId()) {
        case GEOS_POINT: return xs::out<>(dyn_cast<geos::geom::Point*>(g));
        case GEOS_LINESTRING: return xs::out<>(dyn_cast<geos::geom::LineString*>(g));
        case GEOS_LINEARRING: return xs::out<>(dyn_cast<geos::geom::LinearRing*>(g));
        case GEOS_POLYGON: return xs::out<>(dyn_cast<geos::geom::Polygon*>(g));
        case GEOS_MULTIPOINT: return xs::out<>(dyn_cast<geos::geom::MultiPoint*>(g));
        case GEOS_MULTILINESTRING: return xs::out<>(dyn_cast<geos::geom::MultiLineString*>(g));
        case GEOS_MULTIPOLYGON: return xs::out<>(dyn_cast<geos::geom::MultiPolygon*>(g));
        case GEOS_GEOMETRYCOLLECTION: return xs::out<>(dyn_cast<geos::geom::GeometryCollection*>(g));
    default:
        throw "unknown geometry type";
    }
}