OmegaTargetCurrent = Object.create(OmegaTargetChromium)
Promise = OmegaTargetCurrent.Promise
Promise.longStackTraces()

OmegaTargetCurrent.Log = Object.create(OmegaTargetCurrent.Log)
Log = OmegaTargetCurrent.Log
Log.log = (args...) ->
  console.log(args...)
  localStorage['log'] += args.map(Log.str.bind(Log)).join(' ') + '\n'
Log.error = (args...) ->
  console.error(args...)
  content = args.map(Log.str.bind(Log)).join(' ')
  localStorage['logLastError'] = content
  localStorage['log'] += 'ERROR: ' + content + '\n'

unhandledPromises = []
unhandledPromisesId = []
unhandledPromisesNextId = 1
Promise.onPossiblyUnhandledRejection (reason, promise) ->
  Log.error("[#{unhandledPromisesNextId}] Unhandled rejection:\n", reason)
  unhandledPromises.push(promise)
  unhandledPromisesId.push(unhandledPromisesNextId)
  unhandledPromisesNextId++
Promise.onUnhandledRejectionHandled (promise) ->
  index = unhandledPromises.indexOf(promise)
  Log.log("[#{unhandledPromisesId[index]}] Rejection handled!", promise)
  unhandledPromises.splice(index, 1)
  unhandledPromisesId.splice(index, 1)

iconCache = {}
drawIcon = (resultColor, profileColor) ->
  cacheKey = "omega+#{resultColor ? ''}+#{profileColor}"
  icon = iconCache[cacheKey]
  return icon if icon
  ctx = document.getElementById('canvas-icon').getContext('2d')
  ctx2x = document.getElementById('canvas-icon-2x').getContext('2d')
  if resultColor?
    drawOmega ctx, resultColor, profileColor
    drawOmega2x ctx2x, resultColor, profileColor
  else
    drawOmega ctx, profileColor
    drawOmega2x ctx2x, profileColor
  icon =
    19: ctx.getImageData(0, 0, 19, 19)
    38: ctx2x.getImageData(0, 0, 38, 38)
  return iconCache[cacheKey] = icon

charCodeUnderscore = '_'.charCodeAt(0)
isHidden = (name) -> (name.charCodeAt(0) == charCodeUnderscore and
  name.charCodeAt(1) == charCodeUnderscore)

dispName = (name) -> chrome.i18n.getMessage('profile_' + name) || name

actionForUrl = (url) ->
  options.ready.then(->
    request = OmegaPac.Conditions.requestFromUrl(url)
    options.matchProfile(request)
  ).then ({profile, results}) ->
    current = options.currentProfile()
    currentName = dispName(current.name)
    if current.profileType == 'VirtualProfile'
      realCurrentName = current.defaultProfileName
      currentName += " [#{dispName(realCurrentName)}]"
      current = options.profile(realCurrentName)
    details = ''
    direct = false
    attached = false
    condition2Str = (condition) ->
      condition.pattern || OmegaPac.Conditions.str(condition)
    for result in results
      if Array.isArray(result)
        if not result[1]?
          attached = false
          name = result[0]
          if name[0] == '+'
            name = name.substr(1)
          if isHidden(name)
            attached = true
          else if name != realCurrentName
            details += chrome.i18n.getMessage 'browserAction_defaultRuleDetails'
            details += " => #{dispName(name)}\n"
        else if result[1].length == 0
          if result[0] == 'DIRECT'
            details += chrome.i18n.getMessage('browserAction_directResult')
            details += '\n'
            direct = true
          else
            details += "#{result[0]}\n"
        else if typeof result[1] == 'string'
          details += "#{result[1]} => #{result[0]}\n"
        else
          condition = condition2Str(result[1].condition ? result[1])
          details += "#{condition} => "
          if result[0] == 'DIRECT'
            details += chrome.i18n.getMessage('browserAction_directResult')
            details += '\n'
            direct = true
          else
            details += "#{result[0]}\n"
      else if result.profileName
        if result.isTempRule
          details += chrome.i18n.getMessage('browserAction_tempRulePrefix')
        else if attached
          details += chrome.i18n.getMessage('browserAction_attachedPrefix')
          attached = false
        condition = result.source ? condition2Str(result.condition)
        details += "#{condition} => #{dispName(result.profileName)}\n"

    if not details
      details = options.printProfile(current)

    resultColor = profile.color
    profileColor = current.color

    icon = null
    if direct
      resultColor = options.profile('direct').color
      profileColor = profile.color
    else if profile.name == current.name and options.isCurrentProfileStatic()
      resultColor = profileColor = profile.color
      icon = drawIcon(profile.color)
    else
      resultColor = profile.color
      profileColor = current.color

    icon ?= drawIcon(resultColor, profileColor)
    return {
      title: chrome.i18n.getMessage('browserAction_titleWithResult', [
        currentName
        dispName(profile.name)
        details
      ])
      icon: icon
      resultColor: resultColor
      profileColor: profileColor
    }


storage = new OmegaTargetCurrent.Storage(chrome.storage.local, 'local')
state = new OmegaTargetCurrent.BrowserStorage(localStorage, 'omega.local.')

if chrome.storage.sync
  syncStorage = new OmegaTargetCurrent.Storage(chrome.storage.sync, 'sync')
  sync = new OmegaTargetCurrent.OptionsSync(syncStorage)
  if localStorage['omega.local.syncOptions'] != '"sync"'
    sync.enabled = false
  sync.transformValue = OmegaTargetCurrent.Options.transformValueForSync

options = new OmegaTargetCurrent.Options(null, storage, state, Log, sync)
options.externalApi = new OmegaTargetCurrent.ExternalApi(options)
options.externalApi.listen()

if chrome.runtime.id != OmegaTargetCurrent.SwitchySharp.extId
  options.switchySharp = new OmegaTargetCurrent.SwitchySharp()
  options.switchySharp.monitor()

tabs = new OmegaTargetCurrent.ChromeTabs(actionForUrl)
tabs.watch()

options._inspect = new OmegaTargetCurrent.Inspect (url, tab) ->
  if url == tab.url
    options.clearBadge()
    tabs.processTab(tab)
    state.remove('inspectUrl')
    return

  state.set({inspectUrl: url})

  actionForUrl(url).then (action) ->
    parsedUrl = OmegaTargetCurrent.Url.parse(url)
    if parsedUrl.hostname == OmegaTargetCurrent.Url.parse(tab.url).hostname
      urlDisp = parsedUrl.path
    else
      urlDisp = parsedUrl.hostname

    title = chrome.i18n.getMessage('browserAction_titleInspect', urlDisp) + '\n'
    title += action.title
    chrome.browserAction.setTitle(title: title, tabId: tab.id)
    tabs.setTabBadge(tab, {
      text: '#'
      color: action.resultColor
    })

options.setProxyNotControllable(null)
timeout = null

options.watchProxyChange (details) ->
  return if options.externalApi.disabled
  return unless details
  notControllableBefore = options.proxyNotControllable()
  internal = false
  noRevert = false
  switch details['levelOfControl']
    when "controlled_by_other_extensions", "not_controllable"
      reason =
        if details['levelOfControl'] == 'not_controllable'
          'policy'
        else
          'app'
      options.setProxyNotControllable(reason)
      noRevert = true
    else
      options.setProxyNotControllable(null)

  if details['levelOfControl'] == 'controlled_by_this_extension'
    internal = true
    return if not notControllableBefore
  Log.log('external proxy: ', details)

  # Chromium will send chrome.proxy.settings.onChange on extension unload,
  # just after the current extension has lost control of the proxy settings.
  # This is just annoying, and may change the currentProfileName state
  # suprisingly.
  # To workaround this issue, wait for some time before setting the proxy.
  # However this will cause some delay before the settings are processed.
  clearTimeout(timeout) if timeout?
  parsed = null
  timeout = setTimeout (->
    options.setExternalProfile(parsed, {noRevert: noRevert, internal: internal})
  ), 500

  parsed = options.parseExternalProfile(details)
  return

external = false
options.currentProfileChanged = (reason) ->
  iconCache = {}

  if reason == 'external'
    external = true
  else if reason != 'clearBadge'
    external = false

  current = options.currentProfile()
  currentName = ''
  if current
    currentName = dispName(current.name)
    if current.profileType == 'VirtualProfile'
      realCurrentName = current.defaultProfileName
      currentName += " [#{dispName(realCurrentName)}]"
      current = options.profile(realCurrentName)

  details = options.printProfile(current)
  if currentName
    title = chrome.i18n.getMessage('browserAction_titleWithResult', [
      currentName, '', details])
  else
    title = details

  if external and current.profileType != 'SystemProfile'
    message = chrome.i18n.getMessage('browserAction_titleExternalProxy')
    title = message + '\n' + title
    options.setBadge()

  if not current.name or not OmegaPac.Profiles.isInclusive(current)
    icon = drawIcon(current.color)
  else
    icon = drawIcon(options.profile('direct').color, current.color)

  tabs.resetAll(
    icon: icon
    title: title
  )

encodeError = (obj) ->
  if obj instanceof Error
    {
      _error: 'error'
      name: obj.name
      message: obj.message
      stack: obj.stack
      original: obj
    }
  else
    obj

chrome.runtime.onMessage.addListener (request, sender, respond) ->
  options.ready.then ->
    target = options
    method = target[request.method]
    if typeof method != 'function'
      Log.error("No such method #{request.method}!")
      respond(
        error:
          reason: 'noSuchMethod'
      )
      return

    promise = Promise.resolve().then -> method.apply(target, request.args)
    return if request.noReply

    promise.then (result) ->
      if request.method == 'updateProfile'
        for own key, value of result
          result[key] = encodeError(value)
      respond(result: result)

    promise.catch (error) ->
      Log.error(request.method + ' ==>', error)
      respond(error: encodeError(error))

  # Wait for my response!
  return true unless request.noReply