text: 'use strict'
  
  function Dotgrid () {
    const defaultTheme = {
      background: '#eee',
      f_high: '#000',
      f_med: '#999',
      f_low: '#ccc',
      f_inv: '#000',
      b_high: '#000',
      b_med: '#888',
      b_low: '#aaa',
      b_inv: '#ffb545'
    }
  
    // ISU
  
    this.install = function (host) {
      console.info('Dotgrid', 'Installing..')
      this.theme = new Theme(defaultTheme)
      this.history = new History()
  
      this.source = new Source(this)
      this.manager = new Manager(this)
      this.renderer = new Renderer(this)
      this.tool = new Tool(this)
      this.interface = new Interface(this)
      this.picker = new Picker(this)
      this.cursor = new Cursor(this)
      this.listener = new Listener(this)
  
      host.appendChild(this.renderer.el)
  
      this.manager.install()
      this.interface.install(host)
      this.theme.install(host, () => { this.update() })
    }
  
    this.start = function () {
      console.info('Dotgrid', 'Starting..')
      this.theme.start()
      this.tool.start()
      this.renderer.start()
      this.interface.start()
  
      // Add events
      document.addEventListener('mousedown', function (e) { dotgrid.cursor.down(e) }, false)
      document.addEventListener('mousemove', function (e) { dotgrid.cursor.move(e) }, false)
      document.addEventListener('contextmenu', function (e) { dotgrid.cursor.alt(e) }, false)
      document.addEventListener('mouseup', function (e) { dotgrid.cursor.up(e) }, false)
      document.addEventListener('copy', function (e) { dotgrid.copy(e) }, false)
      document.addEventListener('cut', function (e) { dotgrid.cut(e) }, false)
      document.addEventListener('paste', function (e) { dotgrid.paste(e) }, false)
      window.addEventListener('resize', function (e) { dotgrid.onResize() }, false)
      window.addEventListener('dragover', function (e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy' })
      window.addEventListener('drop', dotgrid.drag)
  
      this.source.new()
      this.onResize()
  
      setTimeout(() => { document.body.className += ' ready' }, 250)
    }
  
    this.update = function () {
      this.manager.update()
      this.interface.update()
      this.renderer.update()
    }
  
    this.clear = function () {
      this.history.clear()
      this.tool.reset()
      this.reset()
      this.renderer.update()
      this.interface.update(true)
    }
  
    this.reset = function () {
      this.tool.clear()
      this.update()
    }
  
    // Methods
  
    this.modZoom = function (mod = 0, set = false) {
      try {
        const { webFrame } = require('electron')
        const currentZoomFactor = webFrame.getZoomFactor()
        webFrame.setZoomFactor(set ? mod : currentZoomFactor + mod)
        console.log(window.devicePixelRatio)
      } catch (err) {
        console.log('Cannot zoom')
      }
    }
  
    this.setZoom = function (scale) {
      try {
        webFrame.setZoomFactor(scale)
      } catch (err) {
        console.log('Cannot zoom')
      }
    }
  
    // Resize Tools
  
    this.fitSize = function () {
      if (this.requireResize() === false) { return }
      console.log('Dotgrid', `Will resize to: ${printSize(this.getRequiredSize())}`)
      this.setWindowSize(this.getRequiredSize())
      this.update()
    }
  
    this.setWindowSize = function (size) {
      console.log('Dotgrid', `Resizing to ${printSize(size)}`)
      const win = require('electron').remote.getCurrentWindow()
      win.setSize(size.width, size.height, false)
      document.title = `Dotgrid — ${size.width}x${size.height}`
      this.update()
    }
  
    this.getPadding = function () {
      return { x: 60, y: 90 }
    }
  
    this.getWindowSize = function () {
      return { width: window.innerWidth, height: window.innerHeight }
    }
  
    this.getProjectSize = function () {
      return this.tool.settings.size
    }
  
    this.getPaddedSize = function () {
      const rect = this.getWindowSize()
      const pad = this.getPadding()
      return { width: step(rect.width - pad.x, 15), height: step(rect.height - pad.y, 15) }
    }
  
    this.getRequiredSize = function () {
      const rect = this.getProjectSize()
      const pad = this.getPadding()
      return { width: step(rect.width, 15) + pad.x, height: step(rect.height, 15) + pad.y }
    }
  
    this.requireResize = function () {
      const _window = this.getWindowSize()
      const _required = this.getRequiredSize()
      const offset = sizeOffset(_window, _required)
      if (offset.width !== 0 || offset.height !== 0) {
        console.log(`Dotgrid`, `Require ${printSize(_required)}, but window is ${printSize(_window)}(${printSize(offset)})`)
        return true
      }
      return false
    }
  
    this.onResize = function () {
      const _project = this.getProjectSize()
      const _padded = this.getPaddedSize()
      const offset = sizeOffset(_padded, _project)
      if (offset.width !== 0 || offset.height !== 0) {
        console.log(`Dotgrid`, `Resize project to ${printSize(_padded)}`)
        this.tool.settings.size = _padded
      }
      this.update()
    }
  
    // Events
  
    this.drag = function (e) {
      e.preventDefault()
      e.stopPropagation()
  
      const file = e.dataTransfer.files[0]
      const filename = file.path ? file.path : file.name ? file.name : ''
  
      if (filename.indexOf('.grid') < 0) { console.warn('Dotgrid', 'Not a .grid file'); return }
  
      const reader = new FileReader()
  
      reader.onload = function (e) {
        const data = e.target && e.target.result ? e.target.result : ''
        dotgrid.source.load(filename, data)
        dotgrid.fitSize()
      }
      reader.readAsText(file)
    }
  
    this.copy = function (e) {
      dotgrid.renderer.update()
  
      if (e.target !== this.picker.input) {
        e.clipboardData.setData('text/source', dotgrid.tool.export(dotgrid.tool.layer()))
        e.clipboardData.setData('text/plain', dotgrid.tool.path())
        e.clipboardData.setData('text/html', dotgrid.manager.el.outerHTML)
        e.clipboardData.setData('text/svg+xml', dotgrid.manager.el.outerHTML)
        e.preventDefault()
      }
  
      dotgrid.renderer.update()
    }
  
    this.cut = function (e) {
      dotgrid.renderer.update()
  
      if (e.target !== this.picker.input) {
        e.clipboardData.setData('text/source', dotgrid.tool.export(dotgrid.tool.layer()))
        e.clipboardData.setData('text/plain', dotgrid.tool.export(dotgrid.tool.layer()))
        e.clipboardData.setData('text/html', dotgrid.manager.el.outerHTML)
        e.clipboardData.setData('text/svg+xml', dotgrid.manager.el.outerHTML)
        dotgrid.tool.layers[dotgrid.tool.index] = []
        e.preventDefault()
      }
  
      dotgrid.renderer.update()
    }
  
    this.paste = function (e) {
      if (e.target !== this.picker.el) {
        let data = e.clipboardData.getData('text/source')
        if (isJson(data)) {
          data = JSON.parse(data.trim())
          dotgrid.tool.import(data)
        }
        e.preventDefault()
      }
  
      dotgrid.renderer.update()
    }
  }
  
  String.prototype.capitalize = function () {
    return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase()
  }
  
  function sizeOffset (a, b) { return { width: a.width - b.width, height: a.height - b.height } }
  function printSize (size) { return `${size.width}x${size.height}` }
  function isJson (text) { try { JSON.parse(text); return true } catch (error) { return false } }
  function isEqual (a, b) { return a && b && a.x === b.x && a.y === b.y }
  function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
  function step (v, s) { return Math.round(v / s) * s }
  