import Crypto from 'crypto'
import Xml2js from 'xml2js'

export function URLWithEnforcedPort(xaddr: string, port: number) {
  const badURL = new URL(xaddr)
  if (badURL.origin.endsWith(`:${port}`)) return badURL
  const idx = badURL.origin.indexOf(':', 6)
  const originWithoutPort = badURL.origin.slice(0, idx < 0 ? badURL.origin.length : idx)
  return new URL(xaddr.replace(badURL.origin, `${originWithoutPort}:${port}`))
}

export function createUuidV4() {
  const clist = Crypto.randomBytes(16).toString('hex').toLowerCase().split('')
  clist[12] = '4'
  clist[16] = ((parseInt(clist[16], 16) & 3) | 8).toString(16)
  const m = clist.join('').match(/^(.{8})(.{4})(.{4})(.{4})(.{12})/)
  const uuid = [m[1], m[2], m[3], m[4], m[5]].join('-')
  return uuid
}

export function getTypeOfValue(value: any) {
  if (value === undefined) {
    return 'undefined'
  } else if (value === null) {
    return 'null'
  } else if (Array.isArray(value)) {
    return 'array'
  }

  const t = typeof value

  if (t === 'boolean') {
    return 'boolean'
  } else if (t === 'string') {
    return 'string'
  } else if (t === 'number') {
    if (value % 1 === 0) {
      return 'integer'
    } else {
      return 'float'
    }
  } else if (t === 'object') {
    if (Object.prototype.toString.call(value) === '[object Object]') {
      return 'object'
    } else {
      return 'unknown'
    }
  } else if (t === 'function') {
    return 'function'
  } else {
    return 'unknown'
  }
}

export function isValidCallback(callback) {
  return !!(callback && typeof callback === 'function')
}

export function execCallback(callback, arg1, arg2) {
  if (isValidCallback(callback)) {
    callback(arg1, arg2)
  }
}

export function isXml(xml: string) {
  if (!xml) {
    return false
  }

  const opts = {
    explicitRoot: false,
    explicitArray: false,
    // 'ignoreAttrs'      : true,
    ignoreAttrs: false,
    tagNameProcessors: [
      function (name: string) {
        // strip namespaces
        /* eslint-disable no-useless-escape */
        const m = name.match(/^([^\:]+)\:([^\:]+)$/)
        /* eslint-enable no-useless-escape */
        return m ? m[2] : name
      }
    ]
  }

  let retVal = false
  Xml2js.parseString(xml, opts, (error, results) => {
    if (error) {
      console.log(`isXml: ${xml} is not xml`, error)
      retVal = false
    } else {
      retVal = true
    }
  })

  return retVal
}

export function isInvalidValue(value: any, type: string, allowEmpty?: boolean) {
  const vt = getTypeOfValue(value)
  // special handling for type 'xml' as typeof will be 'string'
  if (type === 'xml') {
    if (!isXml(value)) {
      return ' The type of the value must be a"' + type + '".'
    }
  }
  if (type === 'float') {
    /* eslint-disable no-useless-escape */
    if (!vt.match(/^(float|integer)$/)) {
      /* eslint-enable no-useless-escape */
      return ' The type of the value must be a "' + type + '".'
    }
  } else {
    if (vt !== type && type !== 'xml') {
      return ' The type of the value must be a "' + type + '".'
    }
  }

  if (!allowEmpty) {
    if (vt === 'array' && value.length === 0) {
      return ' The value must not be an empty array.'
    } else if (vt === 'string' && value === '') {
      return ' The value must not be an empty string.'
    }
  }

  if (typeof value === 'string') {
    /* eslint-disable no-useless-escape */
    if (value.match(/[^\x20-\x7e]/)) {
      /* eslint-enable no-useless-escape */
      return ' The value must consist of ascii characters.'
    } else if (type !== 'xml' && value.match(/[\<\>]/)) {
      /* eslint-disable no-useless-escape */
      /* eslint-enable no-useless-escape */
      return ' Invalid characters were found in the value ("<", ">")'
    } else if (type === 'xml' && !value.match(/[\<\>]/)) {
      /* eslint-disable no-useless-escape */
      /* eslint-enable no-useless-escape */
      return ' Valid characters were found in the value for xml ("<", ">")'
    }
  }

  return ''
}
