"use strict";

var mapnik = require('../');
var assert = require('assert');
var path = require('path');

mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins,'geojson.input'));
mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins,'shape.input'));
mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins,'csv.input'));

var trunc_6 = function(key, val) {
    return val.toFixed ? Number(val.toFixed(6)) : val;
};

function deepEqualTrunc(json1,json2) {
    var first = JSON.stringify(json1,trunc_6,1);
    var second = JSON.stringify(json2,trunc_6,1);
    if (first !== second) {
        throw new Error("JSON are not equal:\n" + first +  "\n------\n" + second);
    }
}

describe('mapnik.Feature ', function() {

    it('should throw with invalid usage', function() {
        // no 'new' keyword
        assert.throws(function() { mapnik.Feature(); });
        // invalid args
        assert.throws(function() { new mapnik.Feature(); });
        assert.throws(function() { new mapnik.Feature(1, 4, 5); });
        assert.throws(function() { new mapnik.Feature('foo'); });
    });

    it('should not be able to construct a Featureset', function() {
        assert.throws(function() { mapnik.Featureset(); });
        assert.throws(function() { new mapnik.Featureset(); });
    });

    it('should construct a feature properly', function() {
        var feature = new mapnik.Feature(1);
        assert.ok(feature);
        assert.deepEqual(feature.extent(),[1.7976931348623157e+308,1.7976931348623157e+308,-1.7976931348623157e+308,-1.7976931348623157e+308]);
        assert.throws(function() { mapnik.Feature.fromJSON(); });
        assert.throws(function() { mapnik.Feature.fromJSON(null); });
        assert.throws(function() { mapnik.Feature.fromJSON('foo'); });
        assert.throws(function() {
            var feat = mapnik.Feature.fromJSON('{"type":"Feature","id":1,"geometry":{"type":"Point","coordinates":[0,"b"]},"properties":{"feat_id":1,"name":"name"}}');
        });
    });

    it('should match known features', function() {
        var options = {
            type: 'shape',
            file: './test/data/world_merc.shp'
        };

        var ds = new mapnik.Datasource(options);
        // get one feature
        var featureset = ds.featureset();
        var feature = featureset.next();
        assert.deepEqual(feature.attributes(), {
            AREA: 44,
            FIPS: 'AC',
            ISO2: 'AG',
            ISO3: 'ATG',
            LAT: 17.078,
            LON: -61.783,
            NAME: 'Antigua and Barbuda',
            POP2005: 83039,
            REGION: 19,
            SUBREGION: 29,
            UN: 28
        });

        // loop over all of them to ensure the proper feature count
        var count = 1;
        while ((feature = featureset.next())) {
            count++;
        }
        assert.equal(count, 245);
    });

    it('should report null values as js null',function() {
        var extent = '-180,-60,180,60';
        var ds = new mapnik.MemoryDatasource({'extent': extent});
        var feat = {x:0,y:0,properties: {feat_id:1,null_val:null,name:"name",stuff: {hi:"wee"}}};
        var bad_feat = {x:'0',y:0,properties: {feat_id:1,null_val:null,name:"name",stuff: {hi:"wee"}}};
        assert.equal(ds.add(feat), true);
        assert.equal(ds.add(bad_feat), false);
        var featureset = ds.featureset();
        var feature = featureset.next();
        assert.equal(feature.id(),1);
        var attr = feature.attributes();
        assert.ok(attr.null_val === null);
        assert.equal(attr.feat_id,1);
        assert.equal(attr.name,'name');
        var describe_expected = {
            type: 'vector',
            encoding: 'utf-8',
            fields: {},
            geometry_type: 'collection'
        };
        assert.deepEqual(describe_expected, ds.describe());
    });

    it('should output the same geojson that it read', function () {
        var expected = {
                type: "Feature",
                properties: {},
                geometry: {
                    type: 'Polygon',
                    coordinates: [[[1,1],[2,1],[2,2],[1,2],[1,1]]]
                }
            };
        var ds = new mapnik.Datasource({type:'csv', 'inline': "geojson\n'" + JSON.stringify(expected.geometry) + "'"});
        var describe = ds.describe();
        var expected_describe = { type: 'vector',
                                  encoding: 'utf-8',
                                  fields: {},
                                  geometry_type: 'polygon' };
        assert.deepEqual(expected_describe,describe);
        var f = ds.featureset().next();
        var feature = JSON.parse(f.toJSON());

        assert.equal(expected.type, feature.type);
        assert.deepEqual(expected.properties, feature.properties);
        assert.equal(expected.geometry.type, feature.geometry.type);
        assert.deepEqual(expected.geometry.coordinates, feature.geometry.coordinates);
    });

    it('should output the same geojson that it read (point)', function () {
        var expected = {
                type: "Feature",
                properties: {},
                geometry: {
                    type: 'Point',
                    coordinates: [1,1]
                }
            };
        var ds = new mapnik.Datasource({type:'csv', 'inline': "geojson\n'" + JSON.stringify(expected.geometry) + "'"});
        var describe = ds.describe();
        var expected_describe = { type: 'vector',
                                  encoding: 'utf-8',
                                  fields: {},
                                  geometry_type: 'point' };
        assert.deepEqual(expected_describe,describe);
        var f = ds.featureset().next();
        var feature = JSON.parse(f.toJSON());

        assert.equal(expected.type, feature.type);
        assert.deepEqual(expected.properties, feature.properties);
        assert.equal(expected.geometry.type, feature.geometry.type);
        assert.deepEqual(expected.geometry.coordinates, feature.geometry.coordinates);
    });

    it('should output the same geojson that it read (line)', function () {
        var expected = {
                type: "Feature",
                properties: {},
                geometry: {
                    type: 'LineString',
                    coordinates: [[1,1],[2,2]]
                }
            };
        var ds = new mapnik.Datasource({type:'csv', 'inline': "geojson\n'" + JSON.stringify(expected.geometry) + "'"});
        var describe = ds.describe();
        var expected_describe = { type: 'vector',
                                  encoding: 'utf-8',
                                  fields: {},
                                  geometry_type: 'linestring' };
        assert.deepEqual(expected_describe,describe);
        var f = ds.featureset().next();
        var feature = JSON.parse(f.toJSON());

        assert.equal(expected.type, feature.type);
        assert.deepEqual(expected.properties, feature.properties);
        assert.equal(expected.geometry.type, feature.geometry.type);
        assert.deepEqual(expected.geometry.coordinates, feature.geometry.coordinates);
    });

    it('should be able to create feature from geojson and turn back into geojson', function (done) {
        var expected = {
            type: "Feature",
            properties: {},
            geometry: {
                type: 'Polygon',
                coordinates: [[[1,1],[2,1],[2,2],[1,2],[1,1]]]
            }
        };
        var f = new mapnik.Feature.fromJSON(JSON.stringify(expected));
        var feature = JSON.parse(f.toJSON());
        assert.equal(expected.type, feature.type);
        assert.deepEqual(expected.properties, feature.properties);
        assert.equal(expected.geometry.type, feature.geometry.type);
        if (mapnik.versions.mapnik_number >= 200300) {
            assert.deepEqual(expected.geometry.coordinates, feature.geometry.coordinates);
        }
        var geom = f.geometry();
        assert.deepEqual(geom.extent(),[ 1, 1, 2, 2 ]);
        var expected_geom = JSON.stringify(expected.geometry);
        assert.equal(expected_geom,geom.toJSON());
        var source_proj = new mapnik.Projection('+init=epsg:4326');
        var dest_proj = new mapnik.Projection('+init=epsg:3857');
        var trans = new mapnik.ProjTransform(source_proj,dest_proj);
        var transformed = geom.toJSON({transform:trans});
        assert.notEqual(expected_geom,transformed);
        // async toJSON
        geom.toJSON(function(err,json) {
            assert.equal(expected_geom,json);
            geom.toJSON({transform:trans},function(err,json2) {
                assert.equal(transformed,json2);
                done();
            });
        });
    });

    it('should be able to get a featureset from Memory datasource', function() {
        var mem_datasource = new mapnik.MemoryDatasource({'extent': '-180,-90,180,90'});
        var fs0 = mem_datasource.featureset();
        assert.equal(undefined, fs0);
        mem_datasource.add({ 'x': 0, 'y': 0 });
        var fs1 = mem_datasource.featureset();
        assert.equal('{"type":"Feature","id":1,"geometry":{"type":"Point","coordinates":[0,0]},"properties":{}}',
                         fs1.next().toJSON());
        assert.equal(undefined, fs1.next());
        mem_datasource.add({ 'x': 1, 'y': 1 });
        var fs2 = mem_datasource.featureset();
        assert.equal('{"type":"Feature","id":1,"geometry":{"type":"Point","coordinates":[0,0]},"properties":{}}',
                         fs2.next().toJSON());
        assert.equal('{"type":"Feature","id":2,"geometry":{"type":"Point","coordinates":[1,1]},"properties":{}}',
                         fs2.next().toJSON());
        assert.equal(undefined, fs2.next());
    });

    it('should be able to reproject geojson feature', function (done) {
        var merc_poly = {
         "type": "MultiPolygon",
         "coordinates": [
          [
           [
            [
             -6866928.47049373,
             1923670.30196653
            ],
            [
             -6878926.59653091,
             1939845.92736324
            ],
            [
             -6889254.03965028,
             1933083.00247353
            ],
            [
             -6866928.47049373,
             1923670.30196653
            ]
           ]
          ],
          [
           [
            [
             -6871659.99413039,
             1991787.21060401
            ],
            [
             -6887677.75566065,
             2002918.07047496
            ],
            [
             -6885450.92056682,
             1988802.9255689
            ],
            [
             -6871659.99413039,
             1991787.21060401
            ]
           ]
          ]
         ]
        };
        var input = {
            type: "Feature",
            properties: {},
            geometry: merc_poly
        };
        var f = new mapnik.Feature.fromJSON(JSON.stringify(input));
        var feature = JSON.parse(f.toJSON());
        assert.equal(input.type, feature.type);
        assert.deepEqual(input.properties, feature.properties);
        assert.equal(input.geometry.type, feature.geometry.type);
        if (mapnik.versions.mapnik_number >= 200300) {
            assert.deepEqual(input.geometry.coordinates, feature.geometry.coordinates);
        }
        var geom = f.geometry();
        var ext = geom.extent();
        var expected_ext = [-6889254.03965028,1923670.30196653,-6866928.47049373,2002918.07047496];
        assert.ok(Math.abs(ext[0] - expected_ext[0]) < 0.001);
        var input_geom = JSON.stringify(input.geometry);
        assert.equal(input_geom,geom.toJSON());
        var dest_proj = new mapnik.Projection('+init=epsg:4326');
        var source_proj = new mapnik.Projection('+init=epsg:3857');
        var trans = new mapnik.ProjTransform(source_proj,dest_proj);
        var expected_transformed = {
            "type": "MultiPolygon",
            "coordinates": [
              [
                [
                  [
                    -61.686668,
                    17.0244410000002
                  ],
                  [
                    -61.7944489999999,
                    17.1633300000001
                  ],
                  [
                    -61.887222,
                    17.105274
                  ],
                  [
                    -61.686668,
                    17.0244410000002
                  ]
                ]
              ],
              [
                [
                  [
                    -61.7291719999999,
                    17.608608
                  ],
                  [
                    -61.873062,
                    17.7038880000001
                  ],
                  [
                    -61.853058,
                    17.5830540000001
                  ],
                  [
                    -61.7291719999999,
                    17.608608
                  ]
                ]
              ]
            ]
        };
        var transformed = geom.toJSON({transform:trans});
        deepEqualTrunc(expected_transformed,JSON.parse(transformed));
        //assert.equal(expected_transformed,JSON.parse(transformed));
        assert.notEqual(input_geom,transformed);
        // async toJSON
        geom.toJSON(function(err,json) {
            assert.equal(input_geom,json);
            geom.toJSON({transform:trans},function(err,json2) {
                assert.equal(transformed,json2);
                done();
            });
        });
    });

    it('should output WKT', function () {
        var feature = {
                type: "Feature",
                properties: {},
                geometry: {
                    type: 'Polygon',
                    coordinates: [[[1,1],[2,1],[2,2],[1,2],[1,1]]]
                }
            };
        var expected = 'POLYGON((1 1,2 1,2 2,1 2,1 1))';
        var ds = new mapnik.Datasource({type:'csv', 'inline': "geojson\n'" + JSON.stringify(feature.geometry) + "'"});
        var f = ds.featureset().next();
        assert.equal(expected, f.geometry().toWKT());
        assert.equal(expected, f.toWKT());
    });

    it('should output WKB', function () {
        var feature = {
                type: "Feature",
                properties: {},
                geometry: {
                    type: 'Polygon',
                    coordinates: [[[1,1],[2,1],[2,2],[1,2],[1,1]]]
                }
            };
        var expected = new Buffer('01030000000100000005000000000000000000f03f000000000000f03f0000000000000040000000000000f03f00000000000000400000000000000040000000000000f03f0000000000000040000000000000f03f000000000000f03f', 'hex');
        var ds = new mapnik.Datasource({type:'csv', 'inline': "geojson\n'" + JSON.stringify(feature.geometry) + "'"});
        var f = ds.featureset().next();
        assert.deepEqual(expected, f.geometry().toWKB());
        assert.deepEqual(expected, f.toWKB());
    });

    it('should round trip a geojson property with an array', function() {
        var input = {
          "type": "Feature",
          "properties": {
            "something": []
          },
          "geometry": {
            "type": "Point",
            "coordinates": [ 1, 1 ]
          }
        };
        var ds = new mapnik.Datasource({ type:'geojson', inline: JSON.stringify(input) });
        var fs = ds.featureset()
        var feat = fs.next();
        var feature = JSON.parse(feat.toJSON());
        assert.equal(input.type, feature.type);
        assert.deepEqual(input.geometry, feature.geometry);
        // expected that the array is stringified after https://github.com/mapnik/mapnik/issues/2678
        input.properties.something = "[]";
        assert.deepEqual(input.properties, feature.properties);
    });

});
