import Dom from './libs/dom'
import Painter from './libs/painter'
import util from './libs/util'
import diff from './libs/diff'
import Store from './libs/store'
import Event from './libs/event'
import Config from './config'
import elementResizeEvent from 'element-resize-event'

/**
 * 根据容器大小，更新画布的位置和大小
 */
function _updatePosition() {
  let config = this.config.getConfig()
  let scale = util.getScale(
    this._container,
    this._options.scale || config.scale,
    config.width,
    config.height
  )
  let canvasWidth = this._container.offsetWidth
  let canvasHeight = this._container.offsetHeight
  let visualWidth = config.width * scale.scaleX
  let visualHeight = config.height * scale.scaleY
  let paddingX = 0
  let paddingY = 0
  paddingX =
    canvasHeight - visualHeight > 0 ? (canvasHeight - visualHeight) / 2 : 0
  paddingY = canvasWidth - visualWidth > 0 ? (canvasWidth - visualWidth) / 2 : 0
  Dom.css(this._container.getElementsByClassName('geoeye-canvas')[0], {
    width: config.width + 'px',
    height: config.height + 'px',
    transform: 'scale(' + scale.scaleX + ',' + scale.scaleY + ')',
    margin: paddingX + 'px ' + paddingY + 'px'
  })
  let overflowX =
    (config.width * scale.scaleX - canvasWidth) > 2 ? 'auto' : 'hidden'
  let overflowY =
    (config.height * scale.scaleY - canvasHeight) > 2 ? 'auto' : 'hidden'
  Dom.css(this._container.getElementsByClassName('geoeye-preview')[0], {
    width: canvasWidth + 'px',
    height: canvasHeight + 'px',
    'overflow-x': overflowX,
    'overflow-y': overflowY
  })
  Dom.css(this._container.getElementsByClassName('geoeye-screenshot')[0], {
    width: config.width * scale.scaleX + 'px',
    height: config.height * scale.scaleY + 'px'
  })
}

function _updateBackground() {
  let config = this.config.getConfig()
  if (!config.background) {
    return
  }
  let backgroundContainer = this._container.querySelector(
    '.background-container'
  )
  Dom.css(backgroundContainer, {
    opacity:
      config.background.opacity === undefined ? 1 : config.background.opacity,
    filter: `blur(${config.background.blur || 0}px)`
  })
  let backgroundColor =
    (config.background && config.background.color) || 'transparent'
  if (Array.isArray(backgroundColor)) {
    backgroundColor = util.gradient(
      'css',
      config.background.gradientDirection || 'top',
      ...backgroundColor
    )
    Dom.css(backgroundContainer, {
      background: backgroundColor
    })
  } else {
    Dom.css(backgroundContainer, {
      backgroundImage: config.background.image
        ? `url(${config.background.image})`
        : 'none',
      backgroundColor
    })
  }
}
export class Screen extends Event {
  /**
   * 构造函数，传入配置文件和容器id
   * @config 大屏的配置文件
   * @options 选项，包含容器id
   */
  constructor(options) {
    super()
    this._options = options
    this._components = {}
    this.painter = new Painter(this) // 组件绘制工具
    let {config, container} = this._options
    if (typeof container === 'string') {
      this._container = document.getElementById(container)
    } else if (util.isDOM(container)) {
      this._container = container
    }
    if (!this._container) {
      throw new Error(`Can't get the contaniner of geoeye screen`)
    }
    var previewContainer = Dom.createElement(
      'div',
      'geoeye-preview',
      this._container
    )
    var shotContainer = Dom.createElement(
      'div',
      'geoeye-screenshot',
      previewContainer
    )
    var canvasContainer = Dom.createElement(
      'div',
      'geoeye-canvas',
      shotContainer
    )
    var backgroundContainer = Dom.createElement(
      'div',
      'background-container',
      canvasContainer
    )
    this.el = canvasContainer
    this.backgroundEl = backgroundContainer
    elementResizeEvent(this._container, () => {
      // 监听容器的resize事件
      this.resize()
    })
    this.setConfig(config) // 根据配置文件绘制大屏
  }

  /**
   * 初始化全局变量域
   * @config {Object} 大屏的配置文件
   */
  setupStore(config) {
    this.store = new Store(this) // 管理全局变量
    let initStore = components => {
      components.forEach(c => {
        if (c.events) {
          for (let key in c.events) {
            if (c.events[key].alias) {
              this.store.patchState(c.events[key].alias, c.events[key].default)
            } else {
              this.store.patchState(key, c.events[key].default)
            }
          }
        }
        if (c.paint && c.paint.components) {
          initStore(c.paint.components)
        }
      })
    }
    initStore(config.components)
  }
  /**
   * 绘制画布，更新画布大小，然后根据配置文件绘制各组件图层
   */
  setConfig(config) {
    if (typeof(config) === 'string') {
      fetch(config)
        .then(res => res.json())
        .then(data => {
          this._setConfig(data)
        })
    } else {
      this._setConfig(config)
    }
  }

  _setConfig(config) { // config json
    let newConfig = JSON.parse(JSON.stringify(config))
    let oldConfig = this.config ? this.config.getConfig() : {}
    let cmds = diff(oldConfig, newConfig)
    if (cmds.length === 1 && cmds[0].command === 'setConfig') {
      this.destroy()
      this.setupStore(newConfig)
      this.config = new Config(newConfig)
      _updatePosition.call(this)
      _updateBackground.call(this)
      newConfig.components.forEach(component => {
        this.addComponent(component)
      })
      setTimeout(() => {
        this.fire('load', this)
      }, 0)
      return
    }
    for (let i = 0; i < cmds.length; i++) {
      if (this[cmds[i].command]) {
        this[cmds[i].command](...cmds[i].args)
      }
    }
  }

  /**
   * 添加新的图表
   * @component {Object} 图表的配置项，应包含图表的id
   * @beforeId  {String}  图表插入的位置，将目标图表移动到该图表的前面（上面）
   * 注：这里的前后是相对于Dom上的显示顺序，其对应的配置文件中的顺序于此相反
   */
  addComponent(component, beforeId) {
    if (!component.id) {
      throw new Error('Add component faild, component id is not provided!')
    }
    if (util.isIdExist(component.id, this.config.getConfig().components)) {
      throw new Error(
        `Error in component "${component.name}(${component.id})": Component id has already exist!`
      )
    }
    if (component.events) {
      for (let key in component.events) {
        let eventname = component.events[key].alias || key
        if (!this.store.get(eventname)) {
          this.store.patchState(
            eventname,
            component.events[key].default
          )
        }
      }
    }
    let com = this.painter.draw(component)
    if (com) {
      let before = this.getComponent(beforeId)
      if (before) {
        Dom.insertBefore(com.el, before.el)
      } else {
        this.el.appendChild(com.el)
      }
      this.config.insertComponent(component, beforeId)
      this._components[com.id] = com
    }
  }

  /**
   * 移除图表
   * @id {String} 需要删除的图表id
   */
  removeComponent(id) {
    let component = this.getComponent(id)
    if (!component) {
      return
    }
    let eventConfig = this.config.getEvent(id)
    if (eventConfig) {
      // 如果该组件有全局状态，需要从store中移除
      for (let key in eventConfig) {
        this.store.remove(eventConfig[key].alias)
      }
    }
    component.remove()
    this.config.removeComponent(id)
    delete this._components[id]
  }

  /**
   * 移动图表的z方向顺序
   * @id {String} 需要移动的图表id
   * @beforeId  {String}  移动图表的位置，将目标图表移动到该图表的前面（上面）
   * 注：这里的前后是相对于Dom上的显示顺序，其对应的配置文件中的顺序于此相反
   */
  moveComponent(id, beforeId) {
    let tagetComponent = this.getComponent(id)
    let beforeComponent = this.getComponent(beforeId)
    if (beforeComponent) {
      Dom.insertAfter(tagetComponent.el, beforeComponent.el)
      this.config.moveComponent(id, beforeId)
    }
  }

  /**
   * 获取指定id的图表组件
   */
  getComponent(id) {
    return this._components[id]
  }

  /**
   * 获取大屏中的所有图表组件
   */
  getComponents() {
    return this._components
  }

  /**
   * 获取大屏的配置文件
   */
  getConfig() {
    return this.config.getConfig()
  }
  /**
   * 设置画布宽度
   * @width {Number} 画布的宽度值
   */
  setWidth(width) {
    this.config.setProperty('width', width)
    this.resize()
  }

  /**
   * 设置画布高度
   * @height {Number} 画布的高度值
   */
  setHeight(height) {
    this.config.setProperty('height', height)
    this.resize()
  }

  /**
   * 设置画布的缩放模式
   * @mode {String} 画布的缩放模式：width（按宽度缩放）、height（按高度缩放）、full（全屏铺满）
   */
  setZoomMode(mode) {
    this.config.setProperty('scale', mode)
    this.resize()
  }
  /**
   * 设置画布背景
   * @background {Object} 对象
   */
  setBackground(background) {
    this.config.setProperty('background', background)
    // container的背景
    _updateBackground.call(this)
    // canvas的背景
    Dom.css(this._container.getElementsByClassName('geoeye-canvas')[0], {
      backgroundColor: background.color
    })
  }

  /**
   * 设置图表组件的位置属性
   * @id {String} 目标组件的id
   * @name {String} 要设置的属性名
   * @value {Any} 属性值
   */
  setLayoutProperty(id, name, value) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    this.config.updateLayoutProperty(id, name, value)
    component.setLayout()
    if (name === 'width' || name === 'height') {
      component.fire('resize')
    }
  }

  setPaintProperty(id, name, value) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    this.config.updatePaintProperty(id, name, value)
    component.setPaint()
  }

  setLayout(id, layout) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    // 检查组件大小有没有变化，有就触发resize事件
    let sizeChange = false
    let width = this.config.getLayoutProperty(id, 'width')
    let height = this.config.getLayoutProperty(id, 'height')
    if (layout.width !== width || layout.height !== height) {
      sizeChange = true
    }
    this.config.updateLayout(id, JSON.parse(JSON.stringify(layout)))
    component.setLayout()
    if (sizeChange) {
      component.fire('resize')
    }
  }
  setPaint(id, paint) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    this.config.updatePaint(id, paint)
    component.setPaint()
  }
  setSource(id, source) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    this.config.updateSource(id, source)
    component.setSource()
  }
  setEvent(id, event) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    let oldEvent = this.config.getEvent(id)
    for (let key in oldEvent) {
      if (!event[key] || event[key].alias !== oldEvent[key].alias) {
        this.store.remove(oldEvent[key].alias)
      }
    }
    for (let key in event) {
      this.store.patchState(event[key].alias, event[key].default)
    }
    this.config.updateEvent(id, event)
    component.setPaint() // event改变后需要更新视图上显示的默认值
  }

  /**
   * 获取图表组件的位置属性值
   * @id {String} 目标组件的id
   * @name {String} 要设置的属性名
   */
  getLayoutProperty(id, name) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    return this.config.getLayoutProperty(id, name)
  }

  getPaintProperty(id, name) {
    let component = this.getComponent(id)
    if (!component) {
      throw new Error(`Component Not Found: Component id "${id}" is not exist!`)
    }
    return this.config.getPaintProperty(id, name)
  }

  /**
   * 获取画布的宽度
   */
  getWidth() {
    return this.config.getWidth()
  }

  /**
   * 获取画布的高度
   */
  getHeight() {
    return this.config.getHeight()
  }

  /**
   * 获取画布的缩放方式
   */
  getZoomMode() {
    return this.config.getZoomMode()
  }

  getScale() {
    let config = this.config.getConfig()
    return util.getScale(
      this._container,
      config.scale,
      config.width,
      config.height
    )
  }

  /**
   * 重设画布位置
   */
  resize() {
    _updatePosition.call(this)
  }

  /**
   * 销毁大屏，移除所有组件
   */
  destroy() {
    if (this.store) {
      this.store.removeAll()
    }
    let components = this._components
    for (let id in components) {
      this.removeComponent(id)
    }
  }

  /**
   * 注册外部组件
   * @type {String} 组件的类型，如果与已有组件同名，则会覆盖已有组件
   * @chartClass {Class} 组件类
   */
  component(type, chartClass) {
    let painter = this.painter._painter
    painter[type] = chartClass
  }

  /**
   * 批量注册外部组件
   * @chartClasses {Object} 组件类
   */
  use(chartClasses) {
    for (let key in chartClasses) {
      this.component(key, chartClasses[key])
    }
  }
}
