import { makeError, makeWarning, guid } from '../common.js'
import { locale as L } from '../lang/zh_TW.js'
import Page from './ADPage.js'
import Block from './ADBlock.js'
import Collection from './ADCollection.js'
import { constants as C } from '../constants'
import $ from 'jquery'

import './ADLayout.css'

const _properties = new WeakMap()

const domTemplate = (
  `<div id="adlayout" class="adlayout">
    <div id="docktop" class="dock-top"></div>
    <div id="dockcenter" class="dock-center">
      <div id="dockleft" class="dock-left"></div>
      <div id="layoutWrapper" class="layout-wrapper">
        <div id="canvas" class="canvas"></div>
      </div>
      <div id="dock-right" class="dock-right"></div>
    </div>
    <div id="dock-bottom" class="dock-bottom"></div>
  </div>`
)

/**
 * 建立Layout 編輯器
 * @memberof AD
 * @class Layout
 * @constructor Layout
 * @returns {Layout}
**/

function Layout () {
  /// Private Variable ///
  // 初始化期標

  /**
   * Layout 某些定義的函式的this無法參考到Layout，使用_this替代
   * @private { Layout }
   * @alias this
   */
  const _this = this

  /**
   * Layout實體DOM
   * @private { jQuery }
   */
  let _$this = $(domTemplate)

  /**
   * 編輯器的狀態
   * @private { Object }
   */
  const _state = {
    tool: 'select',
    mode: '',
    status: {}
  }

  /// Private Function ///

  /**
   * 綁定ADLayout相關事件
   * @private
   */
  function _bindEvents () {
    _$this
      .on('mousemove', '.canvas', function (e) {
        _this.triggerEvent(C`ADLEVENT_CANVAS_MOUSEMOVE`, arguments)
      })
      .on('mouseup', '.canvas', function (e) {
        _this.triggerEvent(C`ADLEVENT_CANVAS_MOUSEUP`, arguments)
      })
      .on('click', '.canvas', function (e) {
        _this.triggerEvent(C`ADLEVENT_CANVAS_CLICK`, arguments)
      })
      .on('mousedown', '.canvas', function (e) {
        _this.triggerEvent(C`ADLEVENT_CANVAS_MOUSEDOWN`, arguments)
      })
      .on('click', '.page', function (e) {
        _this.triggerEvent(C`ADLEVENT_PAGE_CLICK`, arguments)
      })
      .on('mousedown', '.page', function (e) {
        _this.triggerEvent(C`ADLEVENT_PAGE_MOUSEDOWN`, arguments)
      })
      .on('mouseup', '.page', function (e) {
        _this.triggerEvent(C`ADLEVENT_PAGE_MOUSEUP`, arguments)
      })
      .on('mousemove', '.page', function (e) {
        _this.triggerEvent(C`ADLEVENT_PAGE_MOUSEMOVE`, arguments)
      })
      .on('click', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_CLICK`, arguments)
      })
      .on('mousedown', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_MOUSEDOWN`, arguments)
      })
      .on('mouseup', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_MOUSEUP`, arguments)
      })
      .on('mousemove', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_MOUSEMOVE`, arguments)
      })
      .on('mouseenter', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_MOUSEENTER`, arguments)
      })
      .on('mouseleave', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_MOUSELEAVE`, arguments)
      })
      .on('dblclick', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_DBLCLICK`, arguments)
      })
      .on('dragstart', '.block', function (e) {
        e.preventDefault()
        _this.triggerEvent(C`ADLEVENT_BLOCK_DRAGSTART`, arguments)
      })
      .on('dragend', '.block', function (e) {
        e.preventDefault()
        _this.triggerEvent(C`ADLEVENT_BLOCK_DRAGEND`, arguments)
      })
      .on('paste', '.block', function (e) {
        _this.triggerEvent(C`ADLEVENT_BLOCK_PASTE`, arguments)
      })
  }

  /// Public Function ///
  /**
   * 將組件附加至編輯器中
   * @public
   * @param {string} position 可以是'top', 'bottom', 'left', 'right'，決定模組在編輯器中停靠的位置
   * @param {jQuery} content 組件的jQuery
   * @returns {AD.Layout}
   */
  this.append = function append (position, content) {
    var dTop = _$this.find('.dock-top')
    var dLeft = _$this.find('.dock-left')
    var dCenter = _$this.find('.layout_wrapper')
    var dRight = _$this.find('.dock-right')
    var dBottom = _$this.find('.dock-bottom')

    switch (position) {
      case 'top':
        dTop.append(content)
        break
      case 'bottom':
        dBottom.append(content)
        break
      case 'center':
        dCenter.append(content)
        break
      case 'left':
        dLeft.append(content)
        break
      case 'right':
        dRight.append(content)
        break
    }

    return this
  }

  /**
   * 將AD.Layout編輯器附加至已存在的Dom上
   * @public
   * @param {(string|jQuery)} selector 要被附加的Dom，型態為jQuery物件，如果selector是string，則用以建立$(selector)的jQuery物件
   */

  this.attachTo = function attachTo (selector) {
    if (selector === null) {
      if (_$this !== null) {
        _$this.remove()
        _$this = null
      }
    } else {
      var container = selector instanceof $ ? selector : $(selector)
      if (container.length === 1) {
        container.append(_$this)
        _$this[0].adl = this
        var style = '<style type=\'text/css\' id=\'ad_style\'></style>'
        $('head').append(style)
        this.triggerEvent(C`ADLEVENT_ATTACHED`, container)
      }
    }
  }

  /// Variable ///

  // this.document.body.page.count = function count () {
  //   return _$this.find('.page').size()
  // }

  // this.document.body.page.add = function add (a, b) {
  //   if (a instanceof Page) {
  //     a.appendTo(_$this.find('.canvas'))
  //     return _this.document.body.page
  //   } else {
  //     makeWarning(C`NO_PAGE_CREATED`, L`_STR_ERR_NO_PAGE_CREATED)`)
  //     return false
  //   }
  // }

  /**
   * 文件狀態物件，包含相關於文件狀態的函式與設定值
   * @type {Object}
   * @public
   */
  this.state = {
    /**
     * 取得或設定已選取的區塊物件
     * @function
     * @param {(string|AD.Block)} [value] 指定要選取的區塊物件，若傳入空字串，則代表取消選取所有區塊
     * @public
     * @returns {(AD.Collection<Block>|this.state)} 若是無指定參數，則回傳已選取的區塊物件，若是有指定，則回傳state本身物件
     */
    select: function (value) {
      if (value !== undefined) {
        if (value === '' || value === null) {
          _this.document.body.block().deselect()
        } else if (value instanceof Block) {
          value.select(true)
        }
        return _this.state
      } else {
        var blocks = new Collection(Block)
        _$this.find('.block.selected').each(function () {
          blocks.push(new Block($(this)))
        })
        return blocks
      }
    },
    /**
     * 取得或設定目前的編輯模式
     * @function
     * @param {string} [value] 指定目前的編輯模式
     * @public
     * @returns {string} 若是無指定參數，則回傳目前的編輯模式，若是有指定，則回傳state本身物件
     */
    mode: function (value) {
      if (value) {
        _state.mode = value
        _this.triggerEvent(C`ADLEVENT_MODE_CHANGED`, [value])
        return _this.state
      } else {
        return _state.mode
      }
    },
    /**
     * 取得或設定目前的編輯工具
     * @function
     * @param {string} [value] 指定目前的編輯工具
     * @public
     * @returns {string} 若是無指定參數，則回傳目前的編輯工具，若是有指定，則回傳state本身物件
     */
    tool: function (value) {
      if (value) {
        _state.tool = value
        _this.triggerEvent(C`ADLEVENT_AD_LAYOUT_TOOL_CHANGED`, [value])
        return _this.state
      } else {
        return _state.tool
      }
    },

    /**
     * 取得或設定目前的文件狀態
     * @function
     * @param {string} [value] 指定目前的文件狀態
     * @public
     * @returns {string} 若是無指定參數，則回傳目前的文件狀態，若是有指定，則回傳state本身物件
     */
    status: function (key, value) {
      switch (arguments.length) {
        case 2:
          _state.status[key] = value
          _this.triggerEvent(C`ADLEVENT_AD_LAYOUT_STATUS_CHANGED`, [key, value])
          return _this.state
        case 1:
          return _state.status[key]
        case 0:
        default:
          return _state.status
      }
    }
  }

  /**
   * 已註冊的事件callback函式，若向AD.Layout編輯器註冊callback函式，callback函式則會儲存在此。儲存的方式為：event.{event} : array
   * @example
   * //a = AD.Layout
   * a.registerEvent(C`ADLEVENT_AD_LAYOUT_CANVAS_CLICK`, function(e,param) {
   *    //TODO here
   * }
   * console.log(a.events)
   * //輸出Object {
   * // C`ADLEVENT_AD_LAYOUT_CANVAS_CLICK` : function(e,param){...}
   * //}
   * @type {object}
   * @public
   */
  _this.events = {}

  _properties.set(this, {
    $this: _$this,
    grid: 1
  })

  _bindEvents()
  _this.triggerEvent(C`ADLEVENT_AD_LAYOUT_ON_LOAD`)

  return _this
}

const prototype = Layout.prototype

prototype.add = function add (obj) {
  const _ = _properties.get(this) || {}
  if (obj instanceof Page) {
    obj.appendTo(_.$this.find('.canvas'))
    return this
  } else {
    makeWarning(C`NO_PAGE_CREATED`, L`_STR_ERR_NO_PAGE_CREATED)`)
    return this
  }
}

/**
 * 取得包含目前編輯器的母jQuery物件
 * @function
 * @public
 * @readonly
 * @returns {jQuery} 包含目前編輯器的母jQuery物件
 */
prototype.container = function () {
  const _ = _properties.get(this) || {}
  return _.container
}

prototype.block = function (pageId, index) {
  const _ = _properties.get(this) || {}
  const blocks = new Collection(Block)
  switch (arguments.length) {
    case 0 :
      $('.block', _.$this).each(function () {
        blocks.push(new Block(this))
      })
      return blocks
    case 1 :
      $('.page:eq(' + pageId + ') .block').each(function () {
        blocks.push(new Block(this))
      })
      return blocks
  }
}

/**
 * 向AD.Layout註冊事件callback函式
 * @function
 * @public
 * @param {string} event 事件名稱
 * @param {function} fun callback函式
 * @param {string} [method=append] 事件的註冊方式
 * @param {boolean} [returnID=false] 是否回傳已註冊事件的event id，event id用已未來刪除該事件使用
 * @returns {(AD.Layout|string)} 若returnID為true，則回傳該事件的event id，否則回傳AD.Layout本身
 */
prototype.registerEvent = function (event, func, method = 'append', returnID) {
  if (event === undefined) makeWarning(C`EMPTY_INPUT`, L`_STR_ERR_BAD_PARAMETERS`)
  var id = 'event' + guid()

  if ((this.events[event] instanceof Array) || !(this.events[event] instanceof Object)) {
    this.events[event] = {}
  }
  switch (method) {
    case '/':
    case 'replace':
      this.events[event] = {}
      break
    case '+':
    case 'append':
      this.events[event][id] = func
      break
    default:
      return false
  }
  return returnID ? id : this
}

/**
 * 觸發向AD.Layout註冊事件callback函式
 * @function
 * @public
 * @param {string} event 事件名稱
 * @param {...*} parament 要送至callback函式的參數
 */
prototype.triggerEvent = function (event, parament) {
  if (this.events[event] !== undefined) {
    for (var k in this.events[event]) {
      const result = this.events[event][k].apply(this, parament)
      if (result === C`EVENT_INTERRUPTED`) break
    }
  }
  return this
}

prototype.grid = function grid (val) {
  const _ = _properties.get(this) || {}
  const _$this = _.$this
  if (arguments.length === 0) {
    return _.grid
  } else {
    _.grid = val
    _$this.css('--grid-size', val + 'px')
    _$this.css('--grid-thickness', val ? '1px' : 0)
    _$this.toggleClass('grid', val !== 0)
    return this
  }
}

export default Layout
