import { Injectable, EventEmitter } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ViewerService {

  // tslint:disable-next-line: variable-name
  private _viewer;
  private layerManager;
  private layers = [];
  private measureHandler;

  public currentLayerNameChange: EventEmitter<string> = new EventEmitter();
  public currentLayerName: string;  // 当前的图层名

  constructor() {
    console.log('%c viewer service constructed! ', 'background-color: red; color: white; padding: 5px');
    this.handleMeasure = this.handleMeasure.bind(this);
    this.currentLayerNameChange.subscribe(name => this.currentLayerName = name);
  }

  get viewer() {
    return this._viewer;
  }

  /**
   * 初始化 Viewer 和图层管理类
   * @param container HTML 标签或标签 ID
   * @param options 见 smart3d.Viewer
   */
  initViewer(container: string | Element, options?: object): object {
    if (!this._viewer) {
      this._viewer = new smart3d.Viewer(container, options || {});
      this.layerManager = new smart3d.LayerManager(this._viewer);

      // tslint:disable-next-line
      window['viewer'] = this._viewer; window['layerManager'] = this.layerManager; window['primitives'] = this.viewer.scene.primitives._primitives; window['layers'] = this.layers;
    }
    return this._viewer;
  }

  /**
   * 添加图层
   * @param layer smart3d.Layer
   */
  addLayer(layer) {
    if (!(layer instanceof smart3d.Layer)) {
      layer = new smart3d.Layer(layer);
    }
    this.layers.push(layer);
    this.layerManager.add(layer);
  }

  removeLayerByName(name: string): void {
    const layer = this.getLayerByName(name);
    const layerIndex = this.getLayerIndexByName(name);
    if (undefined !== layer) {
      this.layerManager.remove(layer);
      this.layers.splice(layerIndex, 1);
    }
  }

  toggleLayerShow(name: string) {
    const layer = this.getLayerByName(name);
    if (undefined !== layer) {
      layer.show = !layer.show;
      this.layerManager.setVisible(layer);
    }
  }

  /**
   * 根据图层名获取图层
   * @param name 图层名
   * @returns smart3d.Layer | undefined
   */
  private getLayerByName(name: string) {
    return this.layers.find(layer => layer.name === name);
  }

  private getLayerIndexByName(name: string): number {
    return this.layers.findIndex(layer => layer.name === name);
  }

  /**
   * 获取图层底下的原始数据
   * @param name 图层名
   * @returns Cesium3DTileset | DataSource | ImageryLayer | undefined
   */
  getPrimitiveByLayerName(name: string) {
    const layer = this.getLayerByName(name);
    return layer ? this.layerManager.getInstance(layer) : undefined;
  }

  /**
   * 将相机视角设为该图层
   * @param layer smart3d.Layer
   */
  flyToLayer(layer): void {
    this.layerManager.flyTo(layer);
  }

  /**
   * 将相机视角设为图层名对应的图层
   * @param name 图层名
   */
  flyToLayerByName(name: string): void {
    const currentLayer = this.getLayerByName(name);
    if (currentLayer) {
      this.flyToLayer(currentLayer);
    }
  }

  /**
   * 开启测量
   * @param measureMode 测量模型，例如距离测量、面积测量
   * @param isGround 是否是贴地测量
   * @param showLabel 是否显示测量结果
   */
  handleMeasure(measureMode: number, isGround?: boolean, showLabel?: boolean): void {
    if (
      measureMode !== smart3d.MeasureMode.Distance &&
      measureMode !== smart3d.MeasureMode.Angle &&
      measureMode !== smart3d.MeasureMode.Area &&
      measureMode !== smart3d.MeasureMode.DVH
    ) {
      throw new Cesium.DeveloperError('measure mode must be one of smart3d.MeasureMode');
    }

    isGround = undefined === isGround ? false : isGround;
    showLabel = undefined === showLabel ? true : showLabel;

    if (this._viewer) {
      this.measureHandler = new smart3d.MeasureHandler(this.viewer, measureMode, isGround, showLabel);
      this.measureHandler.activate();
    }
  }

  /**
   * 笛卡尔坐标转角度制的经纬度高度
   * @param cartesian 笛卡尔坐标
   */
  cartesianToDegreesCartographic(cartesian: {
    x: number;
    y: number;
    z: number
  }): { longitude: number, latitude: number; height: number } {
    const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
    const longitude = Cesium.Math.toDegrees(cartographic.longitude);
    const latitude = Cesium.Math.toDegrees(cartographic.latitude);
    const height = Cesium.Math.toDegrees(cartographic.height);
    return {
      longitude,
      latitude,
      height
    };
  }
}
