/*!
 * DevExtreme (dx.vectormaputils.debug.js)
 * Version: 17.1.5
 * Build date: Tue Aug 01 2017
 *
 * Copyright (c) 2012 - 2017 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
"use strict";
! function(wnd) {
    var exports = wnd.DevExpress = wnd.DevExpress || {};
    exports = exports.viz = exports.viz || {};
    exports = exports.vectormaputils = {};

    function noop() {}

    function eigen(x) {
        return x
    }

    function isFunction(target) {
        return "function" === typeof target
    }

    function wrapSource(source) {
        var stream, buffer = wrapBuffer(source),
            position = 0;
        stream = {
            pos: function() {
                return position
            },
            skip: function(count) {
                position += count;
                return stream
            },
            ui8arr: function(length) {
                var i = 0,
                    list = [];
                list.length = length;
                for (; i < length; ++i) {
                    list[i] = stream.ui8()
                }
                return list
            },
            ui8: function() {
                var val = ui8(buffer, position);
                position += 1;
                return val
            },
            ui16LE: function() {
                var val = ui16LE(buffer, position);
                position += 2;
                return val
            },
            ui32LE: function() {
                var val = ui32LE(buffer, position);
                position += 4;
                return val
            },
            ui32BE: function() {
                var val = ui32BE(buffer, position);
                position += 4;
                return val
            },
            f64LE: function() {
                var val = f64LE(buffer, position);
                position += 8;
                return val
            }
        };
        return stream
    }

    function parseCore(source, roundCoordinates, errors) {
        var result, shapeData = source[0] ? parseShape(wrapSource(source[0]), errors) : {},
            dataBaseFileData = source[1] ? parseDBF(wrapSource(source[1]), errors) : {},
            features = buildFeatures(shapeData.shapes || [], dataBaseFileData.records || [], roundCoordinates);
        if (features.length) {
            result = {
                type: "FeatureCollection",
                features: features
            };
            result.bbox = shapeData.bBox
        } else {
            result = null
        }
        return result
    }

    function buildFeatures(shapeData, dataBaseFileData, roundCoordinates) {
        var i, shape, features = [],
            ii = features.length = ii = Math.max(shapeData.length, dataBaseFileData.length);
        for (i = 0; i < ii; ++i) {
            shape = shapeData[i] || {};
            features[i] = {
                type: "Feature",
                geometry: {
                    type: shape.geoJSON_type || null,
                    coordinates: shape.coordinates ? roundCoordinates(shape.coordinates) : []
                },
                properties: dataBaseFileData[i] || null
            }
        }
        return features
    }

    function createCoordinatesRounder(precision) {
        var factor = Number("1E" + precision);

        function round(x) {
            return Math.round(x * factor) / factor
        }

        function process(values) {
            return values.map(values[0].length ? process : round)
        }
        return process
    }

    function buildParseArgs(source) {
        source = source || {};
        return ["shp", "dbf"].map(function(key) {
            return function(done) {
                if (source.substr) {
                    key = "." + key;
                    sendRequest(source + (source.substr(-key.length).toLowerCase() === key ? "" : key), function(e, response) {
                        done(e, response)
                    })
                } else {
                    done(null, source[key] || null)
                }
            }
        })
    }

    function parse(source, parameters, callback) {
        var result;
        when(buildParseArgs(source), function(errorArray, dataArray) {
            callback = isFunction(parameters) && parameters || isFunction(callback) && callback || noop;
            parameters = !isFunction(parameters) && parameters || {};
            var errors = [];
            errorArray.forEach(function(e) {
                e && errors.push(e)
            });
            result = parseCore(dataArray, parameters.precision >= 0 ? createCoordinatesRounder(parameters.precision) : eigen, errors);
            callback(result, errors.length ? errors : null)
        });
        return result
    }
    exports.parse = parse;

    function when(actions, callback) {
        var errorArray = [],
            dataArray = [],
            counter = 1,
            lock = true;
        actions.forEach(function(action, i) {
            ++counter;
            action(function(e, data) {
                errorArray[i] = e;
                dataArray[i] = data;
                massDone()
            })
        });
        lock = false;
        massDone();

        function massDone() {
            --counter;
            if (0 === counter && !lock) {
                callback(errorArray, dataArray)
            }
        }
    }

    function parseShape(stream, errors) {
        var timeStart, timeEnd, header, record, records = [];
        try {
            timeStart = new Date;
            header = parseShapeHeader(stream)
        } catch (e) {
            errors.push("shp: header parsing error: " + e.message + " / " + e.description);
            return
        }
        if (9994 !== header.fileCode) {
            errors.push("shp: file code: " + header.fileCode + " / expected: 9994")
        }
        if (1e3 !== header.version) {
            errors.push("shp: file version: " + header.version + " / expected: 1000")
        }
        try {
            while (stream.pos() < header.fileLength) {
                record = parseShapeRecord(stream, header.type, errors);
                if (record) {
                    records.push(record)
                } else {
                    break
                }
            }
            if (stream.pos() !== header.fileLength) {
                errors.push("shp: file length: " + header.fileLength + " / actual: " + stream.pos())
            }
            timeEnd = new Date
        } catch (e) {
            errors.push("shp: records parsing error: " + e.message + " / " + e.description)
        } finally {
            return {
                bBox: header.bBox_XY,
                type: header.shapeType,
                shapes: records,
                errors: errors,
                time: timeEnd - timeStart
            }
        }
    }

    function readPointShape(stream, record) {
        record.coordinates = readPointArray(stream, 1)[0]
    }

    function readPolyLineShape(stream, record) {
        var i, bBox = readBBox(stream),
            numParts = readInteger(stream),
            numPoints = readInteger(stream),
            parts = readIntegerArray(stream, numParts),
            points = readPointArray(stream, numPoints),
            rings = [];
        rings.length = numParts;
        for (i = 0; i < numParts; ++i) {
            rings[i] = points.slice(parts[i], parts[i + 1] || numPoints)
        }
        record.bBox = bBox;
        record.coordinates = rings
    }

    function readMultiPointShape(stream, record) {
        record.bBox = readBBox(stream);
        record.coordinates = readPointArray(stream, readInteger(stream))
    }

    function readPointMShape(stream, record) {
        record.coordinates = readPointArray(stream, 1)[0];
        record.coordinates.push(readDoubleArray(stream, 1)[0])
    }

    function readMultiPointMShape(stream, record) {
        var bBox = readBBox(stream),
            numPoints = readInteger(stream),
            points = readPointArray(stream, numPoints),
            mBox = readPair(stream),
            mValues = readDoubleArray(stream, numPoints);
        record.bBox = bBox;
        record.mBox = mBox;
        record.coordinates = merge_XYM(points, mValues, numPoints)
    }

    function readPolyLineMShape(stream, record) {
        var i, from, to, bBox = readBBox(stream),
            numParts = readInteger(stream),
            numPoints = readInteger(stream),
            parts = readIntegerArray(stream, numParts),
            points = readPointArray(stream, numPoints),
            mBox = readPair(stream),
            mValues = readDoubleArray(stream, numPoints),
            rings = [];
        rings.length = numParts;
        for (i = 0; i < numParts; ++i) {
            from = parts[i];
            to = parts[i + 1] || numPoints;
            rings[i] = merge_XYM(points.slice(from, to), mValues.slice(from, to), to - from)
        }
        record.bBox = bBox;
        record.mBox = mBox;
        record.coordinates = rings
    }

    function readPointZShape(stream, record) {
        record.coordinates = readPointArray(stream, 1)[0];
        record.push(readDoubleArray(stream, 1)[0], readDoubleArray(stream, 1)[0])
    }

    function readMultiPointZShape(stream, record) {
        var bBox = readBBox(stream),
            numPoints = readInteger(stream),
            points = readPointArray(stream, numPoints),
            zBox = readPair(stream),
            zValues = readDoubleArray(stream, numPoints),
            mBox = readPair(stream),
            mValue = readDoubleArray(stream, numPoints);
        record.bBox = bBox;
        record.zBox = zBox;
        record.mBox = mBox;
        record.coordinates = merge_XYZM(points, zValues, mValue, numPoints)
    }

    function readPolyLineZShape(stream, record) {
        var i, from, to, bBox = readBBox(stream),
            numParts = readInteger(stream),
            numPoints = readInteger(stream),
            parts = readIntegerArray(stream, numParts),
            points = readPointArray(stream, numPoints),
            zBox = readPair(stream),
            zValues = readDoubleArray(stream, numPoints),
            mBox = readPair(stream),
            mValues = readDoubleArray(stream, numPoints),
            rings = [];
        rings.length = numParts;
        for (i = 0; i < numParts; ++i) {
            from = parts[i];
            to = parts[i + 1] || numPoints;
            rings[i] = merge_XYZM(points.slice(from, to), zValues.slice(from, to), mValues.slice(from, to), to - from)
        }
        record.bBox = bBox;
        record.zBox = zBox;
        record.mBox = mBox;
        record.coordinates = rings
    }

    function readMultiPatchShape(stream, record) {
        var i, from, to, bBox = readBBox(stream),
            numParts = readInteger(stream),
            numPoints = readInteger(stream),
            parts = readIntegerArray(stream, numParts),
            partTypes = readIntegerArray(stream, numParts),
            points = readPointArray(stream, numPoints),
            zBox = readPair(stream),
            zValues = readDoubleArray(stream, numPoints),
            mBox = readPair(stream),
            rings = [];
        rings.length = numParts;
        for (i = 0; i < numParts; ++i) {
            from = parts[i];
            to = parts[i + 1] || numPoints;
            rings[i] = merge_XYZM(points.slice(from, to), zValues.slice(from, to), mValues.slice(from, to), to - from)
        }
        record.bBox = bBox;
        record.zBox = zBox;
        record.mBox = mBox;
        record.types = partTypes;
        record.coordinates = rings
    }
    var SHP_TYPES = {
        0: "Null",
        1: "Point",
        3: "PolyLine",
        5: "Polygon",
        8: "MultiPoint",
        11: "PointZ",
        13: "PolyLineZ",
        15: "PolygonZ",
        18: "MultiPointZ",
        21: "PointM",
        23: "PolyLineM",
        25: "PolygonM",
        28: "MultiPointM",
        31: "MultiPatch"
    };
    var SHP_RECORD_PARSERS = {
        0: noop,
        1: readPointShape,
        3: readPolyLineShape,
        5: readPolyLineShape,
        8: readMultiPointShape,
        11: readPointZShape,
        13: readPolyLineZShape,
        15: readPolyLineZShape,
        18: readMultiPointZShape,
        21: readPointMShape,
        23: readPolyLineMShape,
        25: readPolyLineMShape,
        28: readMultiPointMShape,
        31: readMultiPatchShape
    };
    var SHP_TYPE_TO_GEOJSON_TYPE_MAP = {
        Null: "Null",
        Point: "Point",
        PolyLine: "MultiLineString",
        Polygon: "Polygon",
        MultiPoint: "MultiPoint",
        PointZ: "Point",
        PolyLineZ: "MultiLineString",
        PolygonZ: "Polygon",
        MultiPointZ: "MultiPoint",
        PointM: "Point",
        PolyLineM: "MultiLineString",
        PolygonM: "Polygon",
        MultiPointM: "MultiPoint",
        MultiPatch: "MultiPatch"
    };

    function parseShapeHeader(stream) {
        var header = {};
        header.fileCode = stream.ui32BE();
        stream.skip(20);
        header.fileLength = stream.ui32BE() << 1;
        header.version = stream.ui32LE();
        header.type_number = stream.ui32LE();
        header.type = SHP_TYPES[header.type_number];
        header.bBox_XY = readBBox(stream);
        header.bBox_ZM = readPointArray(stream, 2);
        return header
    }

    function readInteger(stream) {
        return stream.ui32LE()
    }

    function readIntegerArray(stream, length) {
        var i, array = [];
        array.length = length;
        for (i = 0; i < length; ++i) {
            array[i] = readInteger(stream)
        }
        return array
    }

    function readDoubleArray(stream, length) {
        var i, array = [];
        array.length = length;
        for (i = 0; i < length; ++i) {
            array[i] = stream.f64LE()
        }
        return array
    }

    function readBBox(stream) {
        return readDoubleArray(stream, 4)
    }

    function readPair(stream) {
        return [stream.f64LE(), stream.f64LE()]
    }

    function readPointArray(stream, count) {
        var i, points = [];
        points.length = count;
        for (i = 0; i < count; ++i) {
            points[i] = readPair(stream)
        }
        return points
    }

    function merge_XYM(xy, m, length) {
        var i, array = [];
        array.length = length;
        for (i = 0; i < length; ++i) {
            array[i] = [xy[i][0], xy[i][1], m[i]]
        }
        return array
    }

    function merge_XYZM(xy, z, m, length) {
        var i, array = [];
        array.length = length;
        for (i = 0; i < length; ++i) {
            array[i] = [xy[i][0], xy[i][1], z[i], m[i]]
        }
        return array
    }

    function parseShapeRecord(stream, generalType, errors) {
        var record = {
                number: stream.ui32BE()
            },
            length = stream.ui32BE() << 1,
            pos = stream.pos(),
            type = stream.ui32LE();
        record.type_number = type;
        record.type = SHP_TYPES[type];
        record.geoJSON_type = SHP_TYPE_TO_GEOJSON_TYPE_MAP[record.type];
        if (record.type) {
            if (record.type !== generalType) {
                errors.push("shp: shape #" + record.number + " type: " + record.type + " / expected: " + generalType)
            }
            SHP_RECORD_PARSERS[type](stream, record);
            pos = stream.pos() - pos;
            if (pos !== length) {
                errors.push("shp: shape #" + record.number + " length: " + length + " / actual: " + pos)
            }
        } else {
            errors.push("shp: shape #" + record.number + " type: " + type + " / unknown");
            record = null
        }
        return record
    }

    function parseDBF(stream, errors) {
        var timeStart, timeEnd, header, parseData, records;
        try {
            timeStart = new Date;
            header = parseDataBaseFileHeader(stream, errors);
            parseData = prepareDataBaseFileRecordParseData(header, errors);
            records = parseDataBaseFileRecords(stream, header.numberOfRecords, header.recordLength, parseData, errors);
            timeEnd = new Date
        } catch (e) {
            errors.push("dbf: parsing error: " + e.message + " / " + e.description)
        } finally {
            return {
                records: records,
                errors: errors,
                time: timeEnd - timeStart
            }
        }
    }

    function parseDataBaseFileHeader(stream, errors) {
        var i, term, header = {
            versionNumber: stream.ui8(),
            lastUpdate: new Date(1900 + stream.ui8(), stream.ui8() - 1, stream.ui8()),
            numberOfRecords: stream.ui32LE(),
            headerLength: stream.ui16LE(),
            recordLength: stream.ui16LE(),
            fields: []
        };
        stream.skip(20);
        for (i = (header.headerLength - stream.pos() - 1) / 32; i > 0; --i) {
            header.fields.push(parseFieldDescriptor(stream))
        }
        term = stream.ui8();
        if (13 !== term) {
            errors.push("dbf: header terminator: " + term + " / expected: 13")
        }
        return header
    }
    var _fromCharCode = String.fromCharCode;

    function getAsciiString(stream, length) {
        return _fromCharCode.apply(null, stream.ui8arr(length))
    }

    function parseFieldDescriptor(stream) {
        var desc = {
            name: getAsciiString(stream, 11).replace(/\0*$/gi, ""),
            type: _fromCharCode(stream.ui8()),
            length: stream.skip(4).ui8(),
            count: stream.ui8()
        };
        stream.skip(14);
        return desc
    }
    var DBF_FIELD_PARSERS = {
        C: function(stream, length) {
            var str = getAsciiString(stream, length);
            try {
                str = decodeURIComponent(escape(str))
            } catch (e) {}
            return str.trim()
        },
        N: function(stream, length) {
            var str = getAsciiString(stream, length);
            return parseFloat(str, 10)
        },
        D: function(stream, length) {
            var str = getAsciiString(stream, length);
            return new Date(str.substring(0, 4), str.substring(4, 6) - 1, str.substring(6, 8))
        }
    };

    function DBF_FIELD_PARSER_DEFAULT(stream, length) {
        stream.skip(length);
        return null
    }

    function prepareDataBaseFileRecordParseData(header, errors) {
        var item, field, list = [],
            i = 0,
            ii = header.fields.length,
            totalLength = 0;
        for (i = 0; i < ii; ++i) {
            field = header.fields[i];
            item = {
                name: field.name,
                parser: DBF_FIELD_PARSERS[field.type],
                length: field.length
            };
            if (!item.parser) {
                item.parser = DBF_FIELD_PARSER_DEFAULT;
                errors.push("dbf: field " + field.name + " type: " + field.type + " / unknown")
            }
            totalLength += field.length;
            list.push(item)
        }
        if (totalLength + 1 !== header.recordLength) {
            errors.push("dbf: record length: " + header.recordLength + " / actual: " + (totalLength + 1))
        }
        return list
    }

    function parseDataBaseFileRecords(stream, recordCount, recordLength, parseData, errors) {
        var i, j, pos, record, pd, jj = parseData.length,
            records = [];
        for (i = 0; i < recordCount; ++i) {
            record = {};
            pos = stream.pos();
            stream.skip(1);
            for (j = 0; j < jj; ++j) {
                pd = parseData[j];
                record[pd.name] = pd.parser(stream, pd.length)
            }
            pos = stream.pos() - pos;
            if (pos !== recordLength) {
                errors.push("dbf: record #" + (i + 1) + " length: " + recordLength + " / actual: " + pos)
            }
            records.push(record)
        }
        return records
    }

    function wrapBuffer(arrayBuffer) {
        return new DataView(arrayBuffer)
    }

    function ui8(stream, position) {
        return stream.getUint8(position)
    }

    function ui16LE(stream, position) {
        return stream.getUint16(position, true)
    }

    function ui32LE(stream, position) {
        return stream.getUint32(position, true)
    }

    function ui32BE(stream, position) {
        return stream.getUint32(position, false)
    }

    function f64LE(stream, position) {
        return stream.getFloat64(position, true)
    }

    function sendRequest(url, callback) {
        var request = new XMLHttpRequest;
        request.onreadystatechange = function() {
            if (4 === this.readyState) {
                if ("OK" === this.statusText) {
                    callback(null, this.response)
                } else {
                    callback(this.statusText, null)
                }
            }
        };
        request.open("GET", url);
        request.responseType = "arraybuffer";
        request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        request.send(null)
    }
}(window);