import * as echarts from 'echarts/lib/echarts';
import Vector3 from 'claygl/src/math/Vector3';
import glmatrix from 'claygl/src/dep/glmatrix';
import cartesian3DLayout from './cartesian3DLayout';
import evaluateBarSparseness from './evaluateBarSparseness';

var vec3 = glmatrix.vec3;
var isDimensionStacked = echarts.helper.dataStack.isDimensionStacked;

function globeLayout(seriesModel, coordSys) {
    var data = seriesModel.getData();
    var barMinHeight = seriesModel.get('minHeight') || 0;
    var barSize = seriesModel.get('barSize');
    var dims = ['lng', 'lat', 'alt'].map(function (coordDimName) {
        return seriesModel.coordDimToDataDim(coordDimName)[0];
    });
    if (barSize == null) {
        var perimeter = coordSys.radius * Math.PI;
        var fillRatio = evaluateBarSparseness(data, dims[0], dims[1]);
        barSize = [
            perimeter / Math.sqrt(data.count() / fillRatio),
            perimeter / Math.sqrt(data.count() / fillRatio)
        ];
    }
    else if (!echarts.util.isArray(barSize)) {
        barSize = [barSize, barSize];
    }

    var valueDim = getValueDimension(data, dims);

    data.each(dims, function (lng, lat, val, idx) {
        var stackedValue = data.get(valueDim.dimension, idx);
        var baseValue = valueDim.isStacked ? (stackedValue - val) : coordSys.altitudeAxis.scale.getExtent()[0];
        // TODO Stacked with minHeight.
        var height = Math.max(coordSys.altitudeAxis.dataToCoord(val), barMinHeight);
        var start = coordSys.dataToPoint([lng, lat, baseValue]);
        var end = coordSys.dataToPoint([lng, lat, stackedValue]);
        var dir = vec3.sub([], end, start);
        vec3.normalize(dir, dir);
        var size = [barSize[0], height, barSize[1]];
        data.setItemLayout(idx, [start, dir, size]);
    });

    data.setLayout('orient', Vector3.UP.array);
}

function geo3DLayout(seriesModel, coordSys) {
    var data = seriesModel.getData();
    var barSize = seriesModel.get('barSize');
    var barMinHeight = seriesModel.get('minHeight') || 0;
    var dims = ['lng', 'lat', 'alt'].map(function (coordDimName) {
        return seriesModel.coordDimToDataDim(coordDimName)[0];
    });
    if (barSize == null) {
        var size = Math.min(coordSys.size[0], coordSys.size[2]);

        var fillRatio = evaluateBarSparseness(data, dims[0], dims[1]);
        barSize = [
            size / Math.sqrt(data.count() / fillRatio),
            size / Math.sqrt(data.count() / fillRatio)
        ];
    }
    else if (!echarts.util.isArray(barSize)) {
        barSize = [barSize, barSize];
    }
    var dir = [0, 1, 0];

    var valueDim = getValueDimension(data, dims);

    data.each(dims, function (lng, lat, val, idx) {
        var stackedValue = data.get(valueDim.dimension, idx);
        var baseValue = valueDim.isStacked ? (stackedValue - val) : coordSys.altitudeAxis.scale.getExtent()[0];

        var height = Math.max(coordSys.altitudeAxis.dataToCoord(val), barMinHeight);
        var start = coordSys.dataToPoint([lng, lat, baseValue]);
        var size = [barSize[0], height, barSize[1]];
        data.setItemLayout(idx, [start, dir, size]);
    });

    data.setLayout('orient', [1, 0, 0]);
}

function mapService3DLayout(seriesModel, coordSys) {
    var data = seriesModel.getData();
    var dimLng = seriesModel.coordDimToDataDim('lng')[0];
    var dimLat = seriesModel.coordDimToDataDim('lat')[0];
    var dimAlt = seriesModel.coordDimToDataDim('alt')[0];
    var barSize = seriesModel.get('barSize');
    var barMinHeight = seriesModel.get('minHeight') || 0;

    if (barSize == null) {
        var xExtent = data.getDataExtent(dimLng);
        var yExtent = data.getDataExtent(dimLat);
        var corner0 = coordSys.dataToPoint([xExtent[0], yExtent[0]]);
        var corner1 = coordSys.dataToPoint([xExtent[1], yExtent[1]]);

        var size = Math.min(
            Math.abs(corner0[0] - corner1[0]),
            Math.abs(corner0[1] - corner1[1])
        ) || 1;

        var fillRatio = evaluateBarSparseness(data, dimLng, dimLat);
        // PENDING, data density
        barSize = [
            size / Math.sqrt(data.count() / fillRatio),
            size / Math.sqrt(data.count() / fillRatio)
        ];
    }
    else {
        if (!echarts.util.isArray(barSize)) {
            barSize = [barSize, barSize];
        }
        barSize[0] /= coordSys.getScale() / 16;
        barSize[1] /= coordSys.getScale() / 16;
    }

    var dir = [0, 0, 1];
    var dims = [dimLng, dimLat, dimAlt];

    var valueDim = getValueDimension(data, dims);

    data.each(dims, function (lng, lat, val, idx) {
        var stackedValue = data.get(valueDim.dimension, idx);
        var baseValue = valueDim.isStacked ? (stackedValue - val) : 0;

        var start = coordSys.dataToPoint([lng, lat, baseValue]);
        var end = coordSys.dataToPoint([lng, lat, stackedValue]);
        var height = Math.max(end[2] - start[2], barMinHeight);
        var size = [barSize[0], height, barSize[1]];
        data.setItemLayout(idx, [start, dir, size]);
    });

    data.setLayout('orient', [1, 0, 0]);
}

function getValueDimension(data, dataDims) {
    var isStacked = isDimensionStacked(data, dataDims[2]);
    return {
        dimension: isStacked
            ? data.getCalculationInfo('stackResultDimension')
            : dataDims[2],
        isStacked: isStacked
    };
}


export default function registerBarLayout(registers) {
    registers.registerLayout(function (ecModel, api) {
        ecModel.eachSeriesByType('bar3D', function (seriesModel) {
            var coordSys = seriesModel.coordinateSystem;
            var coordSysType = coordSys && coordSys.type;
            if (coordSysType === 'globe') {
                globeLayout(seriesModel, coordSys);
            }
            else if (coordSysType === 'cartesian3D') {
                cartesian3DLayout(seriesModel, coordSys);
            }
            else if (coordSysType === 'geo3D') {
                geo3DLayout(seriesModel, coordSys);
            }
            else if (coordSysType === 'mapbox3D' || coordSysType === 'maptalks3D') {
                mapService3DLayout(seriesModel, coordSys);
            }
            else {
                if (process.env.NODE_ENV !== 'production') {
                    if (!coordSys) {
                        throw new Error('bar3D doesn\'t have coordinate system.');
                    }
                    else {
                        throw new Error('bar3D doesn\'t support coordinate system ' + coordSys.type);
                    }
                }
            }
        });
    });
}