'use strict'

const common = require('./common')
const execSync = require('child_process').execSync
const semver = require('semver')

const officialArchs = ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el']
const officialPlatforms = ['darwin', 'linux', 'mas', 'win32']
const officialPlatformArchCombos = {
  darwin: ['x64'],
  linux: ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el'],
  mas: ['x64'],
  win32: ['ia32', 'x64']
}

const minimumLinuxArchBuildVersions = {
  arm64: '1.8.0',
  mips64el: '1.8.2-beta.5'
}

// Maps to module filename for each platform (lazy-required if used)
const osModules = {
  darwin: './mac',
  linux: './linux',
  mas: './mac', // map to darwin
  win32: './win32'
}

const supported = {
  arch: new Set(officialArchs),
  platform: new Set(officialPlatforms)
}

function createPlatformArchPairs (opts, selectedPlatforms, selectedArchs, ignoreFunc) {
  let combinations = []
  for (const arch of selectedArchs) {
    for (const platform of selectedPlatforms) {
      if (usingOfficialElectronPackages(opts)) {
        if (!validOfficialPlatformArch(opts, platform, arch)) {
          warnIfAllNotSpecified(opts, `The platform/arch combination ${platform}/${arch} is not currently supported by Electron Packager`)
          continue
        } else if (platform === 'linux') {
          const minimumBuildVersion = minimumLinuxArchBuildVersions[arch]
          if (minimumBuildVersion && !officialLinuxBuildExists(opts, minimumBuildVersion)) {
            warnIfAllNotSpecified(opts, `Official linux/${arch} support only exists in Electron ${minimumBuildVersion} and above`)
            continue
          }
        }
        if (typeof ignoreFunc === 'function' && ignoreFunc(platform, arch)) continue
      }
      combinations.push([platform, arch])
    }
  }

  return combinations
}

function unsupportedListOption (name, value, supported) {
  return new Error(`Unsupported ${name}=${value} (${typeof value}); must be a string matching: ${Array.from(supported.values()).join(', ')}`)
}

function usingOfficialElectronPackages (opts) {
  return !opts.download || !opts.download.hasOwnProperty('mirror')
}

function validOfficialPlatformArch (opts, platform, arch) {
  return officialPlatformArchCombos[platform] && officialPlatformArchCombos[platform].indexOf(arch) !== -1
}

function officialLinuxBuildExists (opts, minimumBuildVersion) {
  return semver.gte(opts.electronVersion, minimumBuildVersion)
}

function allPlatformsOrArchsSpecified (opts) {
  return opts.all || opts.arch === 'all' || opts.platform === 'all'
}

function warnIfAllNotSpecified (opts, message) {
  if (!allPlatformsOrArchsSpecified(opts)) {
    common.warning(message)
  }
}

function hostArch () {
  if (process.arch === 'arm') {
    switch (process.config.variables.arm_version) {
      case '6':
        return module.exports.unameArch()
      case '7':
        return 'armv7l'
      default:
        common.warning(`Could not determine specific ARM arch. Detected ARM version: ${JSON.stringify(process.config.variables.arm_version)}`)
    }
  }

  return process.arch
}

module.exports = {
  allOfficialArchsForPlatformAndVersion: function allOfficialArchsForPlatformAndVersion (platform, electronVersion) {
    const archs = officialPlatformArchCombos[platform]
    if (platform === 'linux') {
      const excludedArchs = Object.keys(minimumLinuxArchBuildVersions)
        .filter(arch => !officialLinuxBuildExists({electronVersion: electronVersion}, minimumLinuxArchBuildVersions[arch]))
      return archs.filter(arch => excludedArchs.indexOf(arch) === -1)
    }

    return archs
  },
  createPlatformArchPairs: createPlatformArchPairs,
  hostArch: hostArch,
  officialArchs: officialArchs,
  officialPlatformArchCombos: officialPlatformArchCombos,
  officialPlatforms: officialPlatforms,
  osModules: osModules,
  supported: supported,
  /**
   * Returns the arch name from the `uname` utility.
   */
  unameArch: function unameArch () {
    /* istanbul ignore next */
    return execSync('uname -m').toString().trim()
  },
  // Validates list of architectures or platforms.
  // Returns a normalized array if successful, or throws an Error.
  validateListFromOptions: function validateListFromOptions (opts, name) {
    if (opts.all) return Array.from(supported[name].values())

    let list = opts[name]
    if (!list) {
      if (name === 'arch') {
        list = hostArch()
      } else {
        list = process[name]
      }
    } else if (list === 'all') {
      return Array.from(supported[name].values())
    }

    if (!Array.isArray(list)) {
      if (typeof list === 'string') {
        list = list.split(/,\s*/)
      } else {
        return unsupportedListOption(name, list, supported[name])
      }
    }

    const officialElectronPackages = usingOfficialElectronPackages(opts)

    for (let value of list) {
      if (officialElectronPackages && !supported[name].has(value)) {
        return unsupportedListOption(name, value, supported[name])
      }
    }

    return list
  }
}