import * as echarts from 'echarts/lib/echarts';
import glmatrix from 'claygl/src/dep/glmatrix';
var vec3 = glmatrix.vec3;
var vec2 = glmatrix.vec2;
var normalize = vec3.normalize;
var cross = vec3.cross;
var sub = vec3.sub;
var add = vec3.add;
var create = vec3.create;
var normal = create();
var tangent = create();
var bitangent = create();
var halfVector = create();
var coord0 = [];
var coord1 = [];

function getCubicPointsOnGlobe(coords, coordSys) {
  vec2.copy(coord0, coords[0]);
  vec2.copy(coord1, coords[1]);
  var pts = [];
  var p0 = pts[0] = create();
  var p1 = pts[1] = create();
  var p2 = pts[2] = create();
  var p3 = pts[3] = create();
  coordSys.dataToPoint(coord0, p0);
  coordSys.dataToPoint(coord1, p3); // Get p1

  normalize(normal, p0); // TODO p0-p3 is parallel with normal

  sub(tangent, p3, p0);
  normalize(tangent, tangent);
  cross(bitangent, tangent, normal);
  normalize(bitangent, bitangent);
  cross(tangent, normal, bitangent); // p1 is half vector of p0 and tangent on p0

  add(p1, normal, tangent);
  normalize(p1, p1); // Get p2

  normalize(normal, p3);
  sub(tangent, p0, p3);
  normalize(tangent, tangent);
  cross(bitangent, tangent, normal);
  normalize(bitangent, bitangent);
  cross(tangent, normal, bitangent); // p2 is half vector of p3 and tangent on p3

  add(p2, normal, tangent);
  normalize(p2, p2); // Project distance of p0 on halfVector

  add(halfVector, p0, p3);
  normalize(halfVector, halfVector);
  var projDist = vec3.dot(p0, halfVector); // Angle of halfVector and p1

  var cosTheta = vec3.dot(halfVector, p1);
  var len = (Math.max(vec3.len(p0), vec3.len(p3)) - projDist) / cosTheta * 2;
  vec3.scaleAndAdd(p1, p0, p1, len);
  vec3.scaleAndAdd(p2, p3, p2, len);
  return pts;
}

function getCubicPointsOnPlane(coords, coordSys, up) {
  var pts = [];
  var p0 = pts[0] = vec3.create();
  var p1 = pts[1] = vec3.create();
  var p2 = pts[2] = vec3.create();
  var p3 = pts[3] = vec3.create();
  coordSys.dataToPoint(coords[0], p0);
  coordSys.dataToPoint(coords[1], p3);
  var len = vec3.dist(p0, p3);
  vec3.lerp(p1, p0, p3, 0.3);
  vec3.lerp(p2, p0, p3, 0.3);
  vec3.scaleAndAdd(p1, p1, up, Math.min(len * 0.1, 10));
  vec3.scaleAndAdd(p2, p2, up, Math.min(len * 0.1, 10));
  return pts;
}

function getPolylinePoints(coords, coordSys) {
  var pts = new Float32Array(coords.length * 3);
  var off = 0;
  var pt = [];

  for (var i = 0; i < coords.length; i++) {
    coordSys.dataToPoint(coords[i], pt);
    pts[off++] = pt[0];
    pts[off++] = pt[1];
    pts[off++] = pt[2];
  }

  return pts;
}

function prepareCoords(data) {
  var coordsList = [];
  data.each(function (idx) {
    var itemModel = data.getItemModel(idx);
    var coords = itemModel.option instanceof Array ? itemModel.option : itemModel.getShallow('coords', true);

    if (process.env.NODE_ENV !== 'production') {
      if (!(coords instanceof Array && coords.length > 0 && coords[0] instanceof Array)) {
        throw new Error('Invalid coords ' + JSON.stringify(coords) + '. Lines must have 2d coords array in data item.');
      }
    }

    coordsList.push(coords);
  });
  return {
    coordsList: coordsList
  };
}

function layoutGlobe(seriesModel, coordSys) {
  var data = seriesModel.getData();
  var isPolyline = seriesModel.get('polyline');
  data.setLayout('lineType', isPolyline ? 'polyline' : 'cubicBezier');
  var res = prepareCoords(data);
  data.each(function (idx) {
    var coords = res.coordsList[idx];
    var getPointsMethod = isPolyline ? getPolylinePoints : getCubicPointsOnGlobe;
    data.setItemLayout(idx, getPointsMethod(coords, coordSys));
  });
}

function layoutOnPlane(seriesModel, coordSys, normal) {
  var data = seriesModel.getData();
  var isPolyline = seriesModel.get('polyline');
  var res = prepareCoords(data);
  data.setLayout('lineType', isPolyline ? 'polyline' : 'cubicBezier');
  data.each(function (idx) {
    var coords = res.coordsList[idx];
    var pts = isPolyline ? getPolylinePoints(coords, coordSys) : getCubicPointsOnPlane(coords, coordSys, normal);
    data.setItemLayout(idx, pts);
  });
}

export default function lines3DLayout(ecModel, api) {
  ecModel.eachSeriesByType('lines3D', function (seriesModel) {
    var coordSys = seriesModel.coordinateSystem;

    if (coordSys.type === 'globe') {
      layoutGlobe(seriesModel, coordSys);
    } else if (coordSys.type === 'geo3D') {
      layoutOnPlane(seriesModel, coordSys, [0, 1, 0]);
    } else if (coordSys.type === 'mapbox3D' || coordSys.type === 'maptalks3D') {
      layoutOnPlane(seriesModel, coordSys, [0, 0, 1]);
    }
  });
}
;