'use strict'

const UUID_REG = /^[\da-f]{8}\b-[\da-f]{4}\b-[\da-f]{4}\b-[\da-f]{4}\b-[\da-f]{12}$/iu
const URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu

function isSecure (wsComponents) {
  return typeof wsComponents.secure === 'boolean' ? wsComponents.secure : String(wsComponents.scheme).toLowerCase() === 'wss'
}

function httpParse (components) {
  if (!components.host) {
    components.error = components.error || 'HTTP URIs must have a host.'
  }

  return components
}

function httpSerialize (components) {
  const secure = String(components.scheme).toLowerCase() === 'https'

  // normalize the default port
  if (components.port === (secure ? 443 : 80) || components.port === '') {
    components.port = undefined
  }

  // normalize the empty path
  if (!components.path) {
    components.path = '/'
  }

  // NOTE: We do not parse query strings for HTTP URIs
  // as WWW Form Url Encoded query strings are part of the HTML4+ spec,
  // and not the HTTP spec.

  return components
}

function wsParse (wsComponents) {
// indicate if the secure flag is set
  wsComponents.secure = isSecure(wsComponents)

  // construct resouce name
  wsComponents.resourceName = (wsComponents.path || '/') + (wsComponents.query ? '?' + wsComponents.query : '')
  wsComponents.path = undefined
  wsComponents.query = undefined

  return wsComponents
}

function wsSerialize (wsComponents) {
// normalize the default port
  if (wsComponents.port === (isSecure(wsComponents) ? 443 : 80) || wsComponents.port === '') {
    wsComponents.port = undefined
  }

  // ensure scheme matches secure flag
  if (typeof wsComponents.secure === 'boolean') {
    wsComponents.scheme = (wsComponents.secure ? 'wss' : 'ws')
    wsComponents.secure = undefined
  }

  // reconstruct path from resource name
  if (wsComponents.resourceName) {
    const [path, query] = wsComponents.resourceName.split('?')
    wsComponents.path = (path && path !== '/' ? path : undefined)
    wsComponents.query = query
    wsComponents.resourceName = undefined
  }

  // forbid fragment component
  wsComponents.fragment = undefined

  return wsComponents
}

function urnParse (urnComponents, options) {
  if (!urnComponents.path) {
    urnComponents.error = 'URN can not be parsed'
    return urnComponents
  }
  const matches = urnComponents.path.match(URN_REG)
  if (matches) {
    const scheme = options.scheme || urnComponents.scheme || 'urn'
    urnComponents.nid = matches[1].toLowerCase()
    urnComponents.nss = matches[2]
    const urnScheme = `${scheme}:${options.nid || urnComponents.nid}`
    const schemeHandler = SCHEMES[urnScheme]
    urnComponents.path = undefined

    if (schemeHandler) {
      urnComponents = schemeHandler.parse(urnComponents, options)
    }
  } else {
    urnComponents.error = urnComponents.error || 'URN can not be parsed.'
  }

  return urnComponents
}

function urnSerialize (urnComponents, options) {
  const scheme = options.scheme || urnComponents.scheme || 'urn'
  const nid = urnComponents.nid.toLowerCase()
  const urnScheme = `${scheme}:${options.nid || nid}`
  const schemeHandler = SCHEMES[urnScheme]

  if (schemeHandler) {
    urnComponents = schemeHandler.serialize(urnComponents, options)
  }

  const uriComponents = urnComponents
  const nss = urnComponents.nss
  uriComponents.path = `${nid || options.nid}:${nss}`

  options.skipEscape = true
  return uriComponents
}

function urnuuidParse (urnComponents, options) {
  const uuidComponents = urnComponents
  uuidComponents.uuid = uuidComponents.nss
  uuidComponents.nss = undefined

  if (!options.tolerant && (!uuidComponents.uuid || !UUID_REG.test(uuidComponents.uuid))) {
    uuidComponents.error = uuidComponents.error || 'UUID is not valid.'
  }

  return uuidComponents
}

function urnuuidSerialize (uuidComponents) {
  const urnComponents = uuidComponents
  // normalize UUID
  urnComponents.nss = (uuidComponents.uuid || '').toLowerCase()
  return urnComponents
}

const http = {
  scheme: 'http',
  domainHost: true,
  parse: httpParse,
  serialize: httpSerialize
}

const https = {
  scheme: 'https',
  domainHost: http.domainHost,
  parse: httpParse,
  serialize: httpSerialize
}

const ws = {
  scheme: 'ws',
  domainHost: true,
  parse: wsParse,
  serialize: wsSerialize
}

const wss = {
  scheme: 'wss',
  domainHost: ws.domainHost,
  parse: ws.parse,
  serialize: ws.serialize
}

const urn = {
  scheme: 'urn',
  parse: urnParse,
  serialize: urnSerialize,
  skipNormalize: true
}

const urnuuid = {
  scheme: 'urn:uuid',
  parse: urnuuidParse,
  serialize: urnuuidSerialize,
  skipNormalize: true
}

const SCHEMES = {
  http,
  https,
  ws,
  wss,
  urn,
  'urn:uuid': urnuuid
}

module.exports = SCHEMES
