const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
