'use strict';

var Bucket = require('../bucket');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var earcut = require('earcut');
var classifyRings = require('../../util/classify_rings');
var EARCUT_MAX_RINGS = 500;

module.exports = FillBucket;

var VERTEX_DISTANCE_SCALE = 2;


function FillBucket() {
    Bucket.apply(this, arguments);
}

FillBucket.prototype = util.inherit(Bucket, {});

FillBucket.prototype.programInterfaces = {
    fill: {
        layoutVertexArrayType: new Bucket.VertexArrayType([{
            name: 'a_pos',
            components: 2,
            type: 'Int16'
        }]),
        elementArrayType: new Bucket.ElementArrayType(1),
        elementArrayType2: new Bucket.ElementArrayType(2),

        paintAttributes: [{
            name: 'a_color',
            components: 4,
            type: 'Uint8',
            getValue: function(layer, globalProperties, featureProperties) {
                return layer.getPaintValue("fill-color", globalProperties, featureProperties);
            },
            multiplier: 255,
            paintProperty: 'fill-color'
        }, {
            name: 'a_outline_color',
            components: 4,
            type: 'Uint8',
            getValue: function(layer, globalProperties, featureProperties) {
                return layer.getPaintValue("fill-outline-color", globalProperties, featureProperties);
            },
            multiplier: 255,
            paintProperty: 'fill-outline-color'
        }, {
            name: 'a_opacity',
            components: 1,
            type: 'Uint8',
            getValue: function(layer, globalProperties, featureProperties) {
                return [layer.getPaintValue("fill-opacity", globalProperties, featureProperties)];
            },
            multiplier: 255,
            paintProperty: 'fill-opacity'
        }]
    }
};

FillBucket.prototype.addFeatures = function() {
    var features = this.features;
    for (var i = 0; i < features.length; i++) {
        var feature = features[i];
        this.addFeature(feature);
    }
};

FillBucket.prototype.addFeature = function(feature) {
    var lines = feature.loadGeometry();
    if (typeof feature.loadTriangulation === 'undefined') { //client-side triangulation
        var lines = loadGeometry(feature);
        var polygons = classifyRings(lines, EARCUT_MAX_RINGS);
        var startGroup = this.prepareArrayGroup('fill', 0);
        var startIndex = startGroup.layoutVertexArray.length;
        for (var i = 0; i < polygons.length; i++) {
            this.addPolygon(polygons[i]);
        }
        this.populatePaintArrays('fill', {zoom: this.zoom}, feature.properties, startGroup, startIndex);
    } else {
        var triangleIndices = feature.loadTriangulation();
        var isConvexPolygon = ((!triangleIndices) || (triangleIndices.length === 0));
        var polygons = classifyRings(/*convertCoords(*/lines/*)*/);
        if (polygons.length > 0) {
            var startGroup = this.prepareArrayGroup('fill', 0);
            var startIndex = startGroup.layoutVertexArray.length;
            var mergedPolygon = polygons[0];
            for (var i = 1; i < polygons.length; i++) {
                var polygon = polygons[i];
                for (var j=0;j<polygon.length;++j) {
                    mergedPolygon.push(polygon[j]);
                }
            }
            if (isConvexPolygon) {
                triangleIndices = [];
                if (mergedPolygon.length !== 1) {
                    console.warn('No triangulation information but not simple polygon. Ring count: ' + mergedPolygon.length + '.');
                } else {
                    var ring = mergedPolygon[0];
                    if (ring.length < 3) {
                        console.warn('Polygon ring must have at least 3 points, but it has only ' + ring.length + '.');
                    } else {
                        for (var k=0;k<ring.length-2;++k) {
                            triangleIndices.push(0);
                            triangleIndices.push(k + 1);
                            triangleIndices.push(k + 2);
                        }
                    }
                }
            }
            this.addPolygon(mergedPolygon, triangleIndices);
            this.populatePaintArrays('fill', {zoom: this.zoom}, feature.properties, startGroup, startIndex);
        }
    }
};

FillBucket.prototype.addPolygon = function(polygon, triangleIndices) {
    var numVertices = 0;
    for (var k = 0; k < polygon.length; k++) {
        numVertices += polygon[k].length;
    }
    if (numVertices < 3) {
        return;
    }
    var group = this.prepareArrayGroup('fill', numVertices);
    var flattened = [];
    var holeIndices = [];
    var startIndex = group.layoutVertexArray.length;
    var needsTriangulation = !triangleIndices;
    for (var r = 0; r < polygon.length; r++) {
        var ring = polygon[r];
        if ((needsTriangulation) && (r > 0)) {
            holeIndices.push(flattened.length / 2);
        }
        for (var v = 0; v < ring.length; v++) {
            var vertex = ring[v];
            var x = vertex.x;
            var y = vertex.y;
            if (!needsTriangulation) {
                x *= VERTEX_DISTANCE_SCALE;
                y *= VERTEX_DISTANCE_SCALE;
            }
            var index = group.layoutVertexArray.emplaceBack(x, y);
            if (v >= 1) {
                group.elementArray2.emplaceBack(index - 1, index);
            }
            if (needsTriangulation) { //not pre-triangulated
                flattened.push(vertex.x);
                flattened.push(vertex.y);
            }
        }
    }
    if (needsTriangulation) {
        triangleIndices = earcut(flattened, holeIndices);
    }
    for (var i = 0; i < triangleIndices.length; i++) {
        group.elementArray.emplaceBack(triangleIndices[i] + startIndex);
    }
};

function convertCoords(rings) {
    var result = [];
    for (var i = 0; i < rings.length; i++) {
        var ring = [];
        for (var j = 0; j < rings[i].length; j++) {
            var p = rings[i][j];
            ring.push([p.x, p.y]);
        }
        result.push(ring);
    }
    return result;
}
