const Gi         = imports._gi
const System     = imports.system
const GObject    = imports.gi.GObject
const Clutter    = imports.gi.Clutter
const Shell      = imports.gi.Shell
const AppSystem  = imports.gi.Shell.AppSystem.get_default()
const WinTracker = imports.gi.Shell.WindowTracker.get_default()
const Main       = imports.ui.main
const Unite      = imports.misc.extensionUtils.getCurrentExtension()
const AppMenu    = Main.panel.statusArea.appMenu
const Activities = Main.panel.statusArea.activities
const Buttons    = Unite.imports.buttons
const Handlers   = Unite.imports.handlers

var PanelExtension = class PanelExtension {
  constructor(settings, key, callback) {
    this.activated = false

    const isActive = () => {
      return callback.call(null, settings.get(key))
    }

    const onChange = () => {
      const active = isActive()

      if (active && !this.activated) {
        this.activated = true
        return this._init()
      }

      if (!active && this.activated) {
        this.activated = false
        return this._destroy()
      }
    }

    this.activate = () => {
      settings.connect(key, onChange.bind(this))
      onChange()
    }

    this.destroy = () => {
      if (this.activated) {
        this._destroy()
        this.activated = false
      }
    }
  }
}

var WindowButtons = class WindowButtons extends PanelExtension {
  constructor({ settings }) {
    const active = val => val != 'never'
    super(settings, 'show-window-buttons', active)
  }

  _init() {
    this.theme    = 'default-dark'
    this.signals  = new Handlers.Signals()
    this.settings = new Handlers.Settings()
    this.styles   = new Handlers.Styles()
    this.controls = new Buttons.WindowControls()

    this.signals.connect(
      Main.overview, 'showing', this._onOverviewShowing.bind(this)
    )

    this.signals.connect(
      Main.overview, 'hiding', this._onOverviewHiding.bind(this)
    )

    this.signals.connect(
      WinTracker, 'notify::focus-app', this._onFocusAppChange.bind(this)
    )

    this.settings.connect(
      'window-buttons-layout', this._onLayoutChange.bind(this)
    )

    this.settings.connect(
      'window-buttons-position', this._onPositionChange.bind(this)
    )

    this.settings.connect(
      'window-buttons-placement', this._onPositionChange.bind(this)
    )

    this.settings.connect(
      'window-buttons-theme', this._onThemeChange.bind(this)
    )

    Main.panel.addToStatusArea(
      'uniteWindowControls', this.controls, this.index, this.side
    )

    this.controls.setVisible(false)

    this._onLayoutChange()
    this._onPositionChange()
    this._onThemeChange()
  }

  get position() {
    return this.settings.get('window-buttons-position')
  }

  get placement() {
    return this.settings.get('window-buttons-placement')
  }

  get side() {
    const sides = { first: 'left', last: 'right', auto: this.position }
    return sides[this.placement] || this.placement
  }

  get index() {
    if (this.placement == 'first') return 0
    if (this.placement == 'last') return -1

    return null
  }

  get sibling() {
    if (this.side == 'left') {
      return Main.panel.statusArea.appMenu.get_parent()
    } else {
      return Main.panel.statusArea.aggregateMenu.get_parent()
    }
  }

  get container() {
    if (this.side == 'left') {
      return Main.panel._leftBox
    } else {
      return Main.panel._rightBox
    }
  }

  _onOverviewShowing() {
    this.controls.setVisible(false)
  }

  _onOverviewHiding() {
    const focused = global.unite.focusWindow
    this.controls.setVisible(focused && focused.showButtons)
  }

  _onFocusAppChange() {
    const focused = AppMenu._targetApp

    if (focused == null || focused.state != Shell.AppState.RUNNING) {
      this.controls.setVisible(false)
    }
  }

  _onLayoutChange() {
    const buttons = this.settings.get('window-buttons-layout')

    if (this.side == 'right' && this.position == 'left') {
      buttons.reverse()
    }

    this.controls.addButtons(buttons)
  }

  _onPositionChange() {
    const controls = this.controls.container
    controls.reparent(this.container)

    if (this.index != null) {
      this.container.set_child_at_index(controls, this.index)
    } else {
      this.container.set_child_below_sibling(controls, this.sibling)
    }

    this._onLayoutChange()
  }

  _onThemeChange() {
    this.controls.remove_style_class_name(this.theme)

    this.theme = this.settings.get('window-buttons-theme')
    const path = `themes/${this.theme}/stylesheet.css`

    this.styles.addShellStyle('windowButtons', path)
    this.controls.add_style_class_name(this.theme)
  }

  _destroy() {
    this.controls.destroy()

    this.signals.disconnectAll()
    this.settings.disconnectAll()
    this.styles.removeAll()
  }
}

var ExtendLeftBox = class ExtendLeftBox extends PanelExtension {
  constructor({ settings }) {
    const active = val => val == true
    super(settings, 'extend-left-box', active)
  }

  _init() {
    this._default = Main.panel.__proto__.vfunc_allocate

    Main.panel.__proto__[Gi.hook_up_vfunc_symbol]('allocate', (box, flags) => {
      Main.panel.vfunc_allocate.call(Main.panel, box, flags)
      this._allocate(Main.panel, box, flags)
    })

    Main.panel.queue_relayout()
  }

  _allocate(actor, box, flags) {
    let leftBox   = Main.panel._leftBox
    let centerBox = Main.panel._centerBox
    let rightBox  = Main.panel._rightBox

    let allocWidth  = box.x2 - box.x1
    let allocHeight = box.y2 - box.y1

    let [leftMinWidth, leftNaturalWidth]     = leftBox.get_preferred_width(-1)
    let [centerMinWidth, centerNaturalWidth] = centerBox.get_preferred_width(-1)
    let [rightMinWidth, rightNaturalWidth]   = rightBox.get_preferred_width(-1)

    let sideWidth = allocWidth - rightNaturalWidth - centerNaturalWidth
    let childBox  = new Clutter.ActorBox()

    childBox.y1 = 0
    childBox.y2 = allocHeight

    if (actor.get_text_direction() == Clutter.TextDirection.RTL) {
      childBox.x1 = allocWidth - Math.min(Math.floor(sideWidth), leftNaturalWidth)
      childBox.x2 = allocWidth
    } else {
      childBox.x1 = 0
      childBox.x2 = Math.min(Math.floor(sideWidth), leftNaturalWidth)
    }

    leftBox.allocate(childBox, flags)

    childBox.y1 = 0
    childBox.y2 = allocHeight

    if (actor.get_text_direction() == Clutter.TextDirection.RTL) {
      childBox.x1 = rightNaturalWidth
      childBox.x2 = childBox.x1 + centerNaturalWidth
    } else {
      childBox.x1 = allocWidth - centerNaturalWidth - rightNaturalWidth
      childBox.x2 = childBox.x1 + centerNaturalWidth
    }

    centerBox.allocate(childBox, flags)

    childBox.y1 = 0
    childBox.y2 = allocHeight

    if (actor.get_text_direction() == Clutter.TextDirection.RTL) {
      childBox.x1 = 0
      childBox.x2 = rightNaturalWidth
    } else {
      childBox.x1 = allocWidth - rightNaturalWidth
      childBox.x2 = allocWidth
    }

    rightBox.allocate(childBox, flags)
  }

  _destroy() {
    Main.panel.__proto__[Gi.hook_up_vfunc_symbol]('allocate', this._default)
    this._default = null

    Main.panel.queue_relayout()
  }
}

var ActivitiesButton = class ActivitiesButton extends PanelExtension {
  constructor({ settings }) {
    const active = val => val != 'never'
    super(settings, 'hide-activities-button', active)
  }

  _init() {
    this.signals  = new Handlers.Signals()
    this.settings = new Handlers.Settings()

    this.signals.connect(
      Main.overview, 'showing', this._syncVisible.bind(this)
    )

    this.signals.connect(
      Main.overview, 'hiding', this._syncVisible.bind(this)
    )

    this.signals.connect(
      AppSystem, 'app-state-changed', this._syncVisible.bind(this)
    )

    this.signals.connect(
      WinTracker, 'notify::focus-app', this._syncVisible.bind(this)
    )

    this.settings.connect(
      'show-desktop-name', this._syncVisible.bind(this)
    )

    this._syncVisible()
  }

  get hideButton() {
    return this.settings.get('hide-activities-button')
  }

  get showDesktop() {
    return this.settings.get('show-desktop-name')
  }

  _syncVisible() {
    const button   = Activities.container
    const overview = Main.overview.visibleTarget

    if (this.hideButton == 'always') {
      return button.hide()
    }

    if (this.showDesktop) {
      button.visible = overview
    } else {
      button.visible = overview || AppMenu._targetApp == null
    }
  }

  _destroy() {
    Activities.container.show()

    this.signals.disconnectAll()
    this.settings.disconnectAll()
  }
}

var DesktopName = class DesktopName extends PanelExtension {
  constructor({ settings }) {
    const active = val => val == true
    super(settings, 'show-desktop-name', active)
  }

  _init() {
    this.signals  = new Handlers.Signals()
    this.settings = new Handlers.Settings()
    this.label    = new Buttons.DesktopLabel()

    this.signals.connect(
      Main.overview, 'showing', this._syncVisible.bind(this)
    )

    this.signals.connect(
      Main.overview, 'hiding', this._syncVisible.bind(this)
    )

    this.signals.connect(
      AppSystem, 'app-state-changed', this._syncVisible.bind(this)
    )

    this.signals.connect(
      WinTracker, 'notify::focus-app', this._syncVisible.bind(this)
    )

    this.settings.connect(
      'desktop-name-text', this._onTextChanged.bind(this)
    )

    Main.panel.addToStatusArea(
      'uniteDesktopLabel', this.label, 1, 'left'
    )

    this._onTextChanged()
    this._syncVisible()
  }

  get visibleWindows() {
    const actors = global.get_window_actors()

    return actors.some(({ metaWindow }) => {
      const visible = metaWindow.showing_on_its_workspace()
      return visible && !metaWindow.skip_taskbar
    })
  }

  _syncVisible() {
    const overview = Main.overview.visibleTarget
    const visible  = !overview && AppMenu._targetApp == null

    this.label.setVisible(visible && !this.visibleWindows)
  }

  _onTextChanged() {
    const text = this.settings.get('desktop-name-text')
    this.label.setText(text)
  }

  _destroy() {
    this.label.destroy()

    this.signals.disconnectAll()
    this.settings.disconnectAll()
  }
}

var TrayIcons = class TrayIcons extends PanelExtension {
  constructor({ settings }) {
    const active = val => val == true
    super(settings, 'show-legacy-tray', active)
  }

  _init() {
    this.tray       = new Shell.TrayManager()
    this.settings   = new Handlers.Settings()
    this.indicators = new Buttons.TrayIndicator()

    this.tray.connect(
      'tray-icon-added', this._onIconAdded.bind(this)
    )

    this.tray.connect(
      'tray-icon-removed', this._onIconRemoved.bind(this)
    )

    this.settings.connect(
      'greyscale-tray-icons', this._onGreyscaleChange.bind(this)
    )

    Main.panel.addToStatusArea(
      'uniteTrayIndicator', this.indicators, 0, 'right'
    )

    this.tray.manage_screen(Main.panel)
  }

  _desaturateIcon(icon) {
    const greyscale = this.settings.get('greyscale-tray-icons')
    icon.clear_effects()

    if (greyscale) {
      const desEffect = new Clutter.DesaturateEffect({ factor : 1.0 })
      const briEffect = new Clutter.BrightnessContrastEffect({})

      briEffect.set_brightness(0.2)
      briEffect.set_contrast(0.3)

      icon.add_effect_with_name('desaturate', desEffect)
      icon.add_effect_with_name('brightness-contrast', briEffect)
    }
  }

  _onIconAdded(trayManager, icon) {
    this.indicators.addIcon(icon)
    this._desaturateIcon(icon)
  }

  _onIconRemoved(trayManager, icon) {
    this.indicators.removeIcon(icon)
  }

  _onGreyscaleChange() {
    this.indicators.forEach(this._desaturateIcon.bind(this))
  }

  _destroy() {
    this.tray = null
    System.gc()

    this.indicators.destroy()
    this.settings.disconnectAll()
  }
}

var PanelManager = GObject.registerClass(
  class UnitePanelManager extends GObject.Object {
    _init() {
      this.settings   = new Handlers.Settings()
      this.buttons    = new WindowButtons(this)
      this.extender   = new ExtendLeftBox(this)
      this.activities = new ActivitiesButton(this)
      this.desktop    = new DesktopName(this)
      this.tray       = new TrayIcons(this)
    }

    activate() {
      this.buttons.activate()
      this.extender.activate()
      this.activities.activate()
      this.desktop.activate()
      this.tray.activate()
    }

    destroy() {
      this.buttons.destroy()
      this.extender.destroy()
      this.activities.destroy()
      this.desktop.destroy()
      this.tray.destroy()

      this.settings.disconnectAll()
    }
  }
)
