var _ = require('lodash')

var filterOps = {
  '<': function(a, filterValue) {
    return a < filterValue
  }
  , '<=': function(a, filterValue) {
    return a <= filterValue
  }
  , '>': function(a, filterValue) {
    return a > filterValue
  }
  , '>=': function(a, filterValue) {
    return a >= filterValue
  }
  , '=': function(a, filterValue) {
    return a === filterValue
  }
}

module.exports = function DeviceColumnService($filter, gettext) {
  // Definitions for all possible values.
  return {
    state: DeviceStatusCell({
      title: gettext('Status')
    , value: function(device) {
        return $filter('translate')(device.enhancedStateAction)
      }
    })
  , model: DeviceModelCell({
      title: gettext('Model')
    , value: function(device) {
        return device.model || device.serial
      }
    })
  , name: DeviceNameCell({
      title: gettext('Product')
    , value: function(device) {
        return device.name || ''
      }
    })
  , operator: TextCell({
      title: gettext('Carrier')
    , value: function(device) {
        return device.operator || ''
      }
    })
  , releasedAt: DateCell({
      title: gettext('Released')
    , value: function(device) {
        return device.releasedAt ? new Date(device.releasedAt) : null
      }
    })
  , version: TextCell({
      title: gettext('OS')
    , value: function(device) {
        return device.version || ''
      }
    , compare: function(deviceA, deviceB) {
        var va = (deviceA.version || '0').split('.')
          , vb = (deviceB.version || '0').split('.')
          , la = va.length
          , lb = vb.length

        for (var i = 0, l = Math.max(la, lb); i < l; ++i) {
          var a = i < la ? parseInt(va[i], 10) : 0
            , b = i < lb ? parseInt(vb[i], 10) : 0
            , diff = a - b

          // One of the values might be something like 'M'. If so, do a string
          // comparison instead.
          if (isNaN(diff)) {
            diff = compareRespectCase(va[i], vb[i])
          }

          if (diff !== 0) {
            return diff
          }
        }

        return 0
      }
    , filter: function(device, filter) {
        var va = (device.version || '0').split('.')
          , vb = (filter.query || '0').split('.')
          , la = va.length
          , lb = vb.length
          , op = filterOps[filter.op || '=']

        // We have a single value and no operator or field. It matches
        // too easily, let's wait for a dot (e.g. '5.'). An example of a
        // bad match would be an unquoted query for 'Nexus 5', which targets
        // a very specific device but may easily match every Nexus device
        // as the two terms are handled separately.
        if (filter.op === null && filter.field === null && lb === 1) {
          return false
        }

        if (vb[lb - 1] === '') {
          // This means that the query is not complete yet, and we're
          // looking at something like "4.", which means that the last part
          // should be ignored.
          vb.pop()
          lb -= 1
        }

        for (var i = 0, l = Math.min(la, lb); i < l; ++i) {
          var a = parseInt(va[i], 10)
            , b = parseInt(vb[i], 10)

          // One of the values might be non-numeric, e.g. 'M'. In that case
          // filter by string value instead.
          if (isNaN(a) || isNaN(b)) {
            if (!op(va[i], vb[i])) {
              return false
            }
          }
          else {
            if (!op(a, b)) {
              return false
            }
          }
        }

        return true
      }
    })
  , network: TextCell({
      title: gettext('Network')
    , value: function(device) {
        return device.phone ? device.phone.network : ''
      }
    })
  , display: TextCell({
      title: gettext('Screen')
    , defaultOrder: 'desc'
    , value: function(device) {
        return device.display && device.display.width
          ? device.display.width + 'x' + device.display.height
          : ''
      }
    , compare: function(deviceA, deviceB) {
        var va = deviceA.display && deviceA.display.width
          ? deviceA.display.width * deviceA.display.height
          : 0
        var vb = deviceB.display && deviceB.display.width
          ? deviceB.display.width * deviceB.display.height
          : 0
        return va - vb
      }
    })
  , browser: DeviceBrowserCell({
      title: gettext('Browser')
    , value: function(device) {
        return device.browser || {apps: []}
      }
    })
  , serial: TextCell({
      title: gettext('Serial')
    , value: function(device) {
        return device.serial || ''
      }
    })
  , manufacturer: TextCell({
      title: gettext('Manufacturer')
    , value: function(device) {
        return device.manufacturer || ''
      }
    })
  , sdk: NumberCell({
      title: gettext('SDK')
    , defaultOrder: 'desc'
    , value: function(device) {
        return device.sdk || ''
      }
    })
  , abi: TextCell({
      title: gettext('ABI')
    , value: function(device) {
        return device.abi || ''
      }
    })
  , phone: TextCell({
      title: gettext('Phone')
    , value: function(device) {
        return device.phone ? device.phone.phoneNumber : ''
      }
    })
  , imei: TextCell({
      title: gettext('Phone IMEI')
    , value: function(device) {
        return device.phone ? device.phone.imei : ''
      }
    })
  , iccid: TextCell({
      title: gettext('Phone ICCID')
    , value: function(device) {
        return device.phone ? device.phone.iccid : ''
      }
    })
  , batteryHealth: TextCell({
      title: gettext('Battery Health')
    , value: function(device) {
        return device.battery
          ? $filter('translate')(device.enhancedBatteryHealth)
          : ''
      }
    })
  , batterySource: TextCell({
      title: gettext('Battery Source')
    , value: function(device) {
        return device.battery
          ? $filter('translate')(device.enhancedBatterySource)
          : ''
      }
    })
  , batteryStatus: TextCell({
      title: gettext('Battery Status')
    , value: function(device) {
        return device.battery
          ? $filter('translate')(device.enhancedBatteryStatus)
          : ''
      }
    })
  , batteryLevel: TextCell({
      title: gettext('Battery Level')
    , value: function(device) {
        return device.battery
          ? Math.floor(device.battery.level / device.battery.scale * 100) + '%'
          : ''
      }
    , compare: function(deviceA, deviceB) {
        var va = deviceA.battery ? deviceA.battery.level : 0
          , vb = deviceB.battery ? deviceB.battery.level : 0
        return va - vb
      }
    })
  , batteryTemp: TextCell({
      title: gettext('Battery Temp')
    , value: function(device) {
        return device.battery ? device.battery.temp + '°C' : ''
      }
    , compare: function(deviceA, deviceB) {
        var va = deviceA.battery ? deviceA.battery.temp : 0
          , vb = deviceB.battery ? deviceB.battery.temp : 0
        return va - vb
      }
    })
  , provider: TextCell({
      title: gettext('Location')
    , value: function(device) {
        return device.provider ? device.provider.name : ''
      }
    })
  , notes: TextCell({
      title: gettext('Notes')
    , value: function(device) {
        return device.notes || ''
      }
    })
  , owner: LinkCell({
      title: gettext('User')
    , target: '_blank'
    , value: function(device) {
        return device.owner ? device.owner.name : ''
      }
    , link: function(device) {
        return device.owner ? device.enhancedUserProfileUrl : ''
      }
    })
  }
}

function zeroPadTwoDigit(digit) {
  return digit < 10 ? '0' + digit : '' + digit
}

function compareIgnoreCase(a, b) {
  var la = (a || '').toLowerCase()
    , lb = (b || '').toLowerCase()
  return la === lb ? 0 : (la < lb ? -1 : 1)
}

function filterIgnoreCase(a, filterValue) {
  var va = (a || '').toLowerCase()
    , vb = filterValue.toLowerCase()
  return va.indexOf(vb) !== -1
}

function compareRespectCase(a, b) {
  return a === b ? 0 : (a < b ? -1 : 1)
}



function TextCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function () {
      var td = document.createElement('td')
      td.appendChild(document.createTextNode(''))
      return td
    }
  , update: function(td, item) {
      var t = td.firstChild
      t.nodeValue = options.value(item)
      return td
    }
  , compare: function(a, b) {
      return compareIgnoreCase(options.value(a), options.value(b))
    }
  , filter: function(item, filter) {
      return filterIgnoreCase(options.value(item), filter.query)
    }
  })
}

function NumberCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function () {
      var td = document.createElement('td')
      td.appendChild(document.createTextNode(''))
      return td
    }
  , update: function(td, item) {
      var t = td.firstChild
      t.nodeValue = options.value(item)
      return td
    }
  , compare: function(a, b) {
      return options.value(a) - options.value(b)
    }
  , filter: (function() {
      return function(item, filter) {
        return filterOps[filter.op || '='](
          options.value(item)
        , +filter.query
        )
      }
    })()
  })
}

function DateCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'desc'
  , build: function () {
      var td = document.createElement('td')
      td.appendChild(document.createTextNode(''))
      return td
    }
  , update: function(td, item) {
      var t = td.firstChild
        , date = options.value(item)
      if (date) {
        t.nodeValue = date.getFullYear()
          + '-'
          + zeroPadTwoDigit(date.getMonth() + 1)
          + '-'
          + zeroPadTwoDigit(date.getDate())
      }
      else {
        t.nodeValue = ''
      }
      return td
    }
  , compare: function(a, b) {
      var va = options.value(a) || 0
        , vb = options.value(b) || 0
      return va - vb
    }
  , filter: (function() {
      function dateNumber(d) {
        return d
          ? d.getFullYear() * 10000 + d.getMonth() * 100 + d.getDate()
          : 0
      }
      return function(item, filter) {
        var filterDate = new Date(filter.query)
          , va = dateNumber(options.value(item))
          , vb = dateNumber(filterDate)
        return filterOps[filter.op || '='](va, vb)
      }
    })()
  })
}

function LinkCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function () {
      var td = document.createElement('td')
        , a = document.createElement('a')
      a.appendChild(document.createTextNode(''))
      td.appendChild(a)
      return td
    }
  , update: function(td, item) {
      var a = td.firstChild
        , t = a.firstChild
        , href = options.link(item)
      if (href) {
        a.setAttribute('href', href)
      }
      else {
        a.removeAttribute('href')
      }
      a.target = options.target || ''
      t.nodeValue = options.value(item)
      return td
    }
  , compare: function(a, b) {
      return compareIgnoreCase(options.value(a), options.value(b))
    }
  , filter: function(item, filter) {
      return filterIgnoreCase(options.value(item), filter.query)
    }
  })
}

function DeviceBrowserCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function() {
      var td = document.createElement('td')
        , span = document.createElement('span')
      span.className = 'device-browser-list'
      td.appendChild(span)
      return td
    }
  , update: function(td, device) {
      var span = td.firstChild
        , browser = options.value(device)
        , apps = browser.apps.slice().sort(function(appA, appB) {
            return compareIgnoreCase(appA.name, appB.name)
          })

      for (var i = 0, l = apps.length; i < l; ++i) {
        var app = apps[i]
          , img = span.childNodes[i] || span.appendChild(document.createElement('img'))
          , src = '/static/app/browsers/icon/36x36/' + (app.type || '_default') + '.png'

        // Only change if necessary so that we don't trigger a download
        if (img.getAttribute('src') !== src) {
          img.setAttribute('src', src)
        }

        img.title = app.name + ' (' + app.developer + ')'
      }

      while (span.childNodes.length > browser.apps.length) {
        span.removeChild(span.lastChild)
      }

      return td
    }
  , compare: function(a, b) {
      return options.value(a).apps.length - options.value(b).apps.length
    }
  , filter: function(device, filter) {
      return options.value(device).apps.some(function(app) {
        return filterIgnoreCase(app.type, filter.query)
      })
    }
  })
}

function DeviceModelCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function() {
      var td = document.createElement('td')
        , span = document.createElement('span')
        , image = document.createElement('img')
      span.className = 'device-small-image'
      image.className = 'device-small-image-img pointer'
      span.appendChild(image)
      td.appendChild(span)
      td.appendChild(document.createTextNode(''))
      return td
    }
  , update: function(td, device) {
      var span = td.firstChild
        , image = span.firstChild
        , t = span.nextSibling
        , src = '/static/app/devices/icon/x24/' +
            (device.image || '_default.jpg')

      // Only change if necessary so that we don't trigger a download
      if (image.getAttribute('src') !== src) {
        image.setAttribute('src', src)
      }

      t.nodeValue = options.value(device)

      return td
    }
  , compare: function(a, b) {
      return compareRespectCase(options.value(a), options.value(b))
    }
  , filter: function(device, filter) {
      return filterIgnoreCase(options.value(device), filter.query)
    }
  })
}

function DeviceNameCell(options) {
  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function() {
      var td = document.createElement('td')
        , a = document.createElement('a')
      a.appendChild(document.createTextNode(''))
      td.appendChild(a)
      return td
    }
  , update: function(td, device) {
      var a = td.firstChild
        , t = a.firstChild

      if (device.using) {
        a.className = 'device-product-name-using'
        a.href = '#!/control/' + device.serial
      }
      else if (device.usable) {
        a.className = 'device-product-name-usable'
        a.href = '#!/control/' + device.serial
      }
      else {
        a.className = 'device-product-name-unusable'
        a.removeAttribute('href')
      }

      t.nodeValue = options.value(device)

      return td
    }
  , compare: function(a, b) {
      return compareIgnoreCase(options.value(a), options.value(b))
    }
  , filter: function(device, filter) {
      return filterIgnoreCase(options.value(device), filter.query)
    }
  })
}

function DeviceStatusCell(options) {
  var stateClasses = {
    using: 'state-using btn-primary'
  , busy: 'state-busy btn-warning'
  , available: 'state-available btn-primary-outline'
  , ready: 'state-ready btn-primary-outline'
  , present: 'state-present btn-primary-outline'
  , preparing: 'state-preparing btn-primary-outline btn-success-outline'
  , unauthorized: 'state-unauthorized btn-danger-outline'
  , offline: 'state-offline btn-warning-outline'
  }

  return _.defaults(options, {
    title: options.title
  , defaultOrder: 'asc'
  , build: function() {
      var td = document.createElement('td')
        , a = document.createElement('a')
      a.appendChild(document.createTextNode(''))
      td.appendChild(a)
      return td
    }
  , update: function(td, device) {
      var a = td.firstChild
        , t = a.firstChild

      a.className = 'btn btn-xs device-status ' +
        (stateClasses[device.state] || 'btn-default-outline')

      if (device.usable && !device.using) {
        a.href = '#!/control/' + device.serial
      }
      else {
        a.removeAttribute('href')
      }

      t.nodeValue = options.value(device)

      return td
    }
  , compare: (function() {
      var order = {
        using: 10
      , available: 20
      , busy: 30
      , ready: 40
      , preparing: 50
      , unauthorized: 60
      , offline: 70
      , present: 80
      , absent: 90
      }
      return function(deviceA, deviceB) {
        return order[deviceA.state] - order[deviceB.state]
      }
    })()
  , filter: function(device, filter) {
      return device.state === filter.query
    }
  })
}
