export class MasterPlaylist {
  version = 0
  streams = [] // MasterStream
  /**
   * @readonly
   */
  isMaster = true
}

const MediaType = {
  Audio: 'AUDIO',
  Video: 'VIDEO',
  SubTitle: 'SUBTITLE',
  ClosedCaptions: 'CLOSED-CAPTIONS'
}

// #EXT-X-KEY KEYFORMAT values
// urn:uuid: https://dashif.org/identifiers/content_protection/
const KeySystems = {
  CLEAR_KEY: 'org.w3.clearkey',
  FAIRPLAY: ['urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2', 'com.apple.streamingkeydelivery'],
  WIDEVINE: ['urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed', 'com.widevine.alpha', 'com.widevine'],
  PLAYREADY: ['urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95', 'com.microsoft.playready']
}

function flatArray (arr) {
  let ret = []

  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      ret = ret.concat(flatArray(arr[i]))
    } else {
      ret.push(arr[i])
    }
  }

  return ret
}

export class MediaStream {
  id = 0
  url = ''
  default = false
  autoSelect = false
  forced = false
  group = ''
  name = ''
  lang = ''
  segments = []
  endSN = 0
}

export class AudioStream extends MediaStream {
  mediaType = MediaType.Audio
  channels = 0
}

export class VideoStream extends MediaStream {
  mediaType = MediaType.Video
}

export class SubTitleStream extends MediaStream {
  mediaType = MediaType.SubTitle
}

export class ClosedCaptionsStream extends MediaStream {
  mediaType = MediaType.ClosedCaptions
}

export class MasterStream {
  id = 0
  bitrate = 0
  width = 0
  height = 0
  name = ''
  url = ''
  audioCodec = ''
  videoCodec = ''
  textCodec = ''
  audioGroup = ''

  /** @type {AudioStream[]} */
  audioStreams = []

  /** @type {SubTitleStream[]} */
  subtitleStreams = []

  /** @type {ClosedCaptionsStream[]} */
  closedCaptionsStream = []
}

export class MediaPlaylist {
  version = 0
  url = ''
  type = '' // upper case
  startCC = 0
  endCC = 0
  startSN = 0
  endSN = 0
  totalDuration = 0
  targetDuration = 0
  partTargetDuration = 0
  canSkipUntil = 0
  canSkipDateRanges = false
  skippedSegments = 0
  canBlockReload = false
  partHoldBack = 0
  live = true
  lowLatency = false
  endPartIndex = 0
  /** @type {Array.<MediaSegment>} */
  segments = []
  dateRanges = {}
  skippedSegments = 0
}

export class MediaSegment {
  sn = 0 // Media Sequence Number
  cc = 0
  url = ''
  parentUrl = ''
  title = ''
  start = 0
  duration = 0
  dataTime = ''
  /** @type {?MediaSegmentKey} */
  key = null
  byteRange = null // [start, end]
  isInitSegment = false
  /** @type {?MediaSegment} */
  initSegment = null
  isLast = false
  hasAudio = false
  hasVideo = false

  independent = false
  partIndex = 0

  constructor (parentUrl) {
    this.parentUrl = parentUrl
  }

  get end () {
    return this.start + this.duration
  }

  setTrackExist (v, a) {
    this.hasVideo = v
    this.hasAudio = a
  }

  setByteRange (data, prevSegment) {
    this.byteRange = [0]
    const bytes = data.split('@')
    if (bytes.length === 1 && prevSegment && prevSegment.byteRange) {
      this.byteRange[0] = prevSegment.byteRange[1] || 0
      if (this.byteRange[0]) this.byteRange[0] += 1
    } else {
      this.byteRange[0] = parseInt(bytes[1])
    }
    this.byteRange[1] = this.byteRange[0] + parseInt(bytes[0]) - 1
  }
}

export class MediaSegmentKey {
  method = ''
  url = ''
  /** @type {?Uint8Array} */
  iv = null
  keyFormat = ''
  keyFormatVersions = ''

  constructor (segKey) {
    if (segKey instanceof MediaSegmentKey) {
      this.method = segKey.method
      this.url = segKey.url
      this.keyFormat = segKey.keyFormat
      this.keyFormatVersions = segKey.keyFormatVersions
      if (segKey.iv) this.iv = new Uint8Array(segKey.iv)
    }
  }

  clone (sn) {
    const key = new MediaSegmentKey(this)
    if (sn !== null && sn !== undefined) key.setIVFromSN(sn)
    return key
  }

  setIVFromSN (sn) {
    if (!this.iv && this.method === 'AES-128' && typeof sn === 'number' && this.url) {
      this.iv = new Uint8Array(16)
      for (let i = 12; i < 16; i++) {
        this.iv[i] = (sn >> (8 * (15 - i))) & 0xff
      }
    }
  }

  isSegmentEncrypted () {
    const { method } = this
    return method === 'AES-128' // || method === 'AES-256' || method === 'AES-256-CTR'
  }

  isValidKeySystem () {
    const isKeyFormatValid =
    flatArray([
      KeySystems.CLEAR_KEY,
      KeySystems.FAIRPLAY,
      KeySystems.WIDEVINE,
      KeySystems.PLAYREADY
    ]).indexOf(this.keyFormat) > -1
    if (!isKeyFormatValid) {
      return false
    }

    const isMethodValid =
      ['SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) > -1
    if (!isMethodValid) {
      return false
    }
    return true
  }

  isSupported () {
    if (!this.method) {
      return false
    }
    if (this.isSegmentEncrypted()) {
      return true
    } else if (this.isValidKeySystem()) {
      return true
    }
    return false
  }
}

export class HlsUrlParameters {
  constructor (msn, part, skip) {
    this.msn = msn
    this.part = part
    this.skip = skip
  }

  addDirectives (uri) {
    const url = new self.URL(uri)
    if (this.msn !== undefined) {
      url.searchParams.set('_HLS_msn', this.msn.toString())
    }
    if (this.part) {
      url.searchParams.set('_HLS_part', this.part.toString())
    }
    if (this.skip) {
      url.searchParams.set('_HLS_skip', this.skip)
    }
    return url.href
  }
}
