// from https://www.npmjs.com/package/wellknown

/*eslint-disable no-cond-assign */
module.exports = WKTparse;
module.exports.WKTparse = WKTparse;
module.exports.stringify = stringify;

var numberRegexp = /[-+]?([0-9]*\.[0-9]+|[0-9]+)([eE][-+]?[0-9]+)?/;
// Matches sequences like '100 100' or '100 100 100'.
var tuples = new RegExp("^" + numberRegexp.source + "(\\s" + numberRegexp.source + "){1,}");

/*
 * Parse WKT and return GeoJSON.
 *
 * @param {string} _ A WKT geometry
 * @return {?Object} A GeoJSON geometry object
 */
function WKTparse(input) {
    var parts = input.split(";");
    var _ = parts.pop();
    var srid = (parts.shift() || "").split("=").pop();

    var i = 0;

    function $(re) {
        var match = _.substring(i).match(re);
        if (!match) return null;
        else {
            i += match[0].length;
            return match[0];
        }
    }

    function crs(obj) {
        if (obj && srid.match(/\d+/)) {
            obj.crs = {
                type: "name",
                properties: {
                    name: "urn:ogc:def:crs:EPSG::" + srid
                }
            };
        }

        return obj;
    }

    function white() {
        $(/^\s*/);
    }

    function multicoords() {
        white();
        var depth = 0;
        var rings = [];
        var stack = [rings];
        var pointer = rings;
        var elem;

        while (elem =
      $(/^(\()/) ||
      $(/^(\))/) ||
      $(/^(,)/) ||
      $(tuples)) {
            if (elem === "(") {
                stack.push(pointer);
                pointer = [];
                stack[stack.length - 1].push(pointer);
                depth++;
            } else if (elem === ")") {
                // For the case: Polygon(), ...
                if (pointer.length === 0) return null;

                pointer = stack.pop();
                // the stack was empty, input was malformed
                if (!pointer) return null;
                depth--;
                if (depth === 0) break;
            } else if (elem === ",") {
                pointer = [];
                stack[stack.length - 1].push(pointer);
            } else if (!elem.split(/\s/g).some(isNaN)) {
                Array.prototype.push.apply(pointer, elem.split(/\s/g).map(parseFloat));
            } else {
                return null;
            }
            white();
        }

        if (depth !== 0) return null;

        return rings;
    }

    function coords() {
        var list = [];
        var item;
        var pt;
        while (pt =
      $(tuples) ||
      $(/^(,)/)) {
            if (pt === ",") {
                list.push(item);
                item = [];
            } else if (!pt.split(/\s/g).some(isNaN)) {
                if (!item) item = [];
                Array.prototype.push.apply(item, pt.split(/\s/g).map(parseFloat));
            }
            white();
        }

        if (item) list.push(item);
        else return null;

        return list.length ? list : null;
    }

    function point() {
        if (!$(/^(point(\sz)?)/i)) return null;
        white();
        if (!$(/^(\()/)) return null;
        var c = coords();
        if (!c) return null;
        white();
        if (!$(/^(\))/)) return null;
        return {
            type: "Point",
            coordinates: c[0]
        };
    }

    function multipoint() {
        if (!$(/^(multipoint)/i)) return null;
        white();
        var newCoordsFormat = _
            .substring(_.indexOf("(") + 1, _.length - 1)
            .replace(/\(/g, "")
            .replace(/\)/g, "");
        _ = "MULTIPOINT (" + newCoordsFormat + ")";
        var c = multicoords();
        if (!c) return null;
        white();
        return {
            type: "MultiPoint",
            coordinates: c
        };
    }

    function multilinestring() {
        if (!$(/^(multilinestring)/i)) return null;
        white();
        var c = multicoords();
        if (!c) return null;
        white();
        return {
            type: "MultiLineString",
            coordinates: c
        };
    }

    function linestring() {
        if (!$(/^(linestring(\sz)?)/i)) return null;
        white();
        if (!$(/^(\()/)) return null;
        var c = coords();
        if (!c) return null;
        if (!$(/^(\))/)) return null;
        return {
            type: "LineString",
            coordinates: c
        };
    }

    function polygon() {
        if (!$(/^(polygon(\sz)?)/i)) return null;
        white();
        var c = multicoords();
        if (!c) return null;
        return {
            type: "Polygon",
            coordinates: c
        };
    }

    function multipolygon() {
        if (!$(/^(multipolygon)/i)) return null;
        white();
        var c = multicoords();
        if (!c) return null;
        return {
            type: "MultiPolygon",
            coordinates: c
        };
    }

    function geometrycollection() {
        var geometries = [];
        var geometry;

        if (!$(/^(geometrycollection)/i)) return null;
        white();

        if (!$(/^(\()/)) return null;
        while (geometry = root()) {
            geometries.push(geometry);
            white();
            $(/^(,)/);
            white();
        }
        if (!$(/^(\))/)) return null;

        return {
            type: "GeometryCollection",
            geometries: geometries
        };
    }

    function root() {
        return point() ||
      linestring() ||
      polygon() ||
      multipoint() ||
      multilinestring() ||
      multipolygon() ||
      geometrycollection();
    }

    return crs(root());
}

/**
 * Stringifies a GeoJSON object into WKT
 */
function stringify(gj) {
    if (gj.type === "Feature") {
        gj = gj.geometry;
    }

    function pairWKT(c) {
        return c.join(" ");
    }

    function ringWKT(r) {
        return r.map(pairWKT).join(", ");
    }

    function ringsWKT(r) {
        return r.map(ringWKT).map(wrapParens).join(", ");
    }

    function multiRingsWKT(r) {
        return r.map(ringsWKT).map(wrapParens).join(", ");
    }

    function wrapParens(s) {
        return "(" + s + ")";
    }

    switch (gj.type) {
        case "Point":
            return "POINT (" + pairWKT(gj.coordinates) + ")";
        case "LineString":
            return "LINESTRING (" + ringWKT(gj.coordinates) + ")";
        case "Polygon":
            return "POLYGON (" + ringsWKT(gj.coordinates) + ")";
        case "MultiPoint":
            return "MULTIPOINT (" + ringWKT(gj.coordinates) + ")";
        case "MultiPolygon":
            return "MULTIPOLYGON (" + multiRingsWKT(gj.coordinates) + ")";
        case "MultiLineString":
            return "MULTILINESTRING (" + ringsWKT(gj.coordinates) + ")";
        case "GeometryCollection":
            return "GEOMETRYCOLLECTION (" + gj.geometries.map(stringify).join(", ") + ")";
        default:
            throw new Error("stringify requires a valid GeoJSON Feature or geometry object as input");
    }
}