const ADType = {
  RewardedVideo: 'RewardedVideo',
  FullScreenVideo: 'FullScreenVideo',
  Interstitial: 'Interstitial'
}

const EventType = {
  Load: 'load',
  Close: 'close',
  Error: 'error'
}

const EXPIRED_TIME = 1000 * 60 * 30
const ProviderType = {
  CSJ: 'csj',
  GDT: 'gdt'
}

const RETRY_COUNT = 1

class AdBase {
  constructor (adInstance, options = {}) {
    this._isLoad = false
    this._isLoading = false
    this._isPlaying = false
    this._lastLoadTime = 0
    this._lastError = null
    this._retryCount = 0
    if (options.retry !== undefined) {
      this._retry = options.retry
    } else {
      this._retry = true
    }

    this._loadCallback = null
    this._closeCallback = null
    this._errorCallback = null

    const ad = this._ad = adInstance
    ad.onLoad((e) => {
      this._isLoading = false
      this._isLoad = true
      this._lastLoadTime = Date.now()

      this.onLoad()
    })
    ad.onClose((e) => {
      this._isLoad = false
      this._isPlaying = false
      this.onClose(e)
    })
    ad.onVerify && ad.onVerify((e) => {
      // e.isValid
    })
    ad.onError(({
      code,
      message,
      detail
    }) => {
      this._isLoading = false
      const data = {
        code: code,
        errMsg: message,
        detail: detail
      }

      if (this._retry && code === -5008) {
        this._loadAd()
        return
      }

      if (this._retry && this._retryCount < RETRY_COUNT) {
        this._retryCount += 1
        this._loadAd()
        return
      }

      this._lastError = data
      this.onError(data)
    })
  }

  get isExpired () {
    return (this._lastLoadTime !== 0 && (Math.abs(Date.now() - this._lastLoadTime) > EXPIRED_TIME))
  }

  get isLoad () {
    return this._isLoad
  }

  get isLoading () {
    return this._isLoading
  }

  getProvider () {
    return this._ad.getProvider()
  }

  load (onload, onerror) {
    this._loadCallback = onload
    this._errorCallback = onerror

    if (this._isPlaying) {
      onerror && onerror()
      return
    }

    if (this._isLoading) {
      return
    }

    if (this._isLoad) {
      this.onLoad()
      return
    }

    this._retryCount = 0

    this._loadAd()
  }

  show (onclose, onshow) {
    this._closeCallback = onclose

    if (this._isLoading || this._isPlaying || !this._isLoad) {
      return
    }

    if (this._lastError !== null) {
      this.onError(this._lastError)
      return
    }

    const provider = this.getProvider()
    if (provider === ProviderType.CSJ && this.isExpired) {
      if (this._retry) {
        this._loadAd()
      } else {
        this.onError(this._lastError)
      }
      return
    }

    this._isPlaying = true
    this._ad.show()
    onshow && onshow()
  }

  onLoad (e) {
    if (this._loadCallback != null) {
      this._loadCallback()
    }
  }

  onClose (e) {
    if (this._closeCallback != null) {
      this._closeCallback({
        isEnded: e.isEnded
      })
    }
  }

  onError (e) {
    if (this._errorCallback != null) {
      this._errorCallback(e)
    }
  }

  destroy () {
    this._ad.destroy()
  }

  _loadAd () {
    this._isLoad = false
    this._isLoading = true
    this._lastError = null
    this._ad.load()
  }
}

class RewardedVideo extends AdBase {
  constructor (options = {}) {
    super(plus.ad.createRewardedVideoAd(options), options)
  }
}

class FullScreenVideo extends AdBase {
  constructor (options = {}) {
    super(plus.ad.createFullScreenVideoAd(options), options)
  }
}

class Interstitial extends AdBase {
  constructor (options = {}) {
    super(plus.ad.createInterstitialAd(options), options)
  }
}

class AdHelper {
  constructor (adType) {
    this._ads = {}
    this._adType = adType
    this._lastWaterfallIndex = -1
  }

  load (options, onload, onerror) {
    if (!options.adpid || this.isBusy(options.adpid)) {
      return
    }

    this.get(options).load(onload, onerror)
  }

  show (options, onload, onerror, onclose, onshow) {
    const ad = this.get(options)

    if (ad.isLoad) {
      this._lastWaterfallIndex = -1
      ad.show((e) => {
        onclose && onclose(e)
      }, () => {
        onshow && onshow()
      })
    } else {
      ad.load(() => {
        this._lastWaterfallIndex = -1
        onload && onload()
        ad.show((e) => {
          onclose && onclose(e)
        }, () => {
          onshow && onshow()
        })
      }, (err) => {
        onerror && onerror(err)
      })
    }
  }

  // 底价预载逻辑
  loadWaterfall (options, onload, onfail, index = 0) {
    const {
      adpid,
      urlCallback
    } = options
    if (!Array.isArray(adpid)) {
      return
    }

    const options2 = {
      adpid: adpid[index],
      urlCallback,
      retry: false
    }

    console.log('ad.loadWaterfall::index=' + index)

    this.load(options2, (res) => {
      this._lastWaterfallIndex = index
      onload(options2)
    }, (err) => {
      index++
      if (index >= adpid.length) {
        onfail(err)
      } else {
        this.loadWaterfall(options, onload, onfail, index)
      }
    })
  }

  // 底价逻辑，失败后下一个，无重试机制
  showWaterfall (options, onload, onfail, onclose, onshow, index = 0) {
    const {
      adpid,
      urlCallback
    } = options
    if (!Array.isArray(adpid)) {
      return
    }

    if (this._lastWaterfallIndex > -1) {
      index = this._lastWaterfallIndex
    }

    const options2 = {
      adpid: adpid[index],
      urlCallback,
      retry: false
    }

    console.log('ad.showWaterfall::index=' + index)

    this.show(options2, () => {
      onload()
    }, (err) => {
      index++
      if (index >= adpid.length) {
        onfail(err)
      } else {
        this.showWaterfall(options, onload, onfail, onclose, onshow, index)
      }
    }, (res) => {
      onclose(res)
    }, () => {
      onshow()
    })
  }

  // 预载底价瀑布流
  preloadWaterfall (options, index = 0, step = 1) {
    if (step === 1) {
      this.loadWaterfall(options, (res) => {
        console.log('preloadWaterfall.success::', res)
      }, (err) => {
        console.log('loadWaterfall.fail', err)
      })
      return
    }

    const {
      adpid,
      urlCallback
    } = options
    for (let i = 0; i < step; i++) {
      if (index < adpid.length) {
        const options2 = {
          adpid: adpid[index],
          urlCallback
        }
        this.loadWaterfall(options2, (res) => {
          console.log('preloadWaterfall.success::', res)
        }, (err) => {
          console.log('loadWaterfall.fail', err)
          this.preloadWaterfall(options, index, step)
        })
        index++
      } else {
        break
      }
    }
  }

  isBusy (adpid) {
    return (this._ads[adpid] && this._ads[adpid].isLoading)
  }

  get (options) {
    const {
      adpid
    } = options

    if (!this._ads[adpid]) {
      this._ads[adpid] = this._createInstance(options)
    }

    return this._ads[adpid]
  }

  getProvider (adpid) {
    if (this._ads[adpid]) {
      return this._ads[adpid].getProvider()
    }
    return null
  }

  remove (adpid) {
    if (this._ads[adpid]) {
      this._ads[adpid].destroy()
      delete this._ads[adpid]
    }
  }

  _createInstance (options) {
    const adType = options.adType || this._adType
    delete options.adType

    let ad = null
    if (adType === ADType.RewardedVideo) {
      ad = new RewardedVideo(options)
    } else if (adType === ADType.FullScreenVideo) {
      ad = new FullScreenVideo(options)
    } else if (adType === ADType.Interstitial) {
      ad = new Interstitial(options, true)
    }

    return ad
  }
}

export default {
  props: {
    options: {
      type: [Object, Array],
      default () {
        return {}
      }
    },
    disabled: {
      type: [Boolean, String],
      default: false
    },
    adpid: {
      type: [Number, String, Array],
      default: ''
    },
    preload: {
      type: [Boolean, String],
      default: true
    },
    loadnext: {
      type: [Boolean, String],
      default: false
    },
    urlCallback: {
      type: Object,
      default () {
        return {}
      }
    }
  },
  data () {
    return {
      loading: false,
      errorMessage: null
    }
  },
  watch: {
    adpid (newValue, oldValue) {
      this._removeInstance(oldValue)
      if (this.preload) {
        this._loadAd()
      }
    },
    // 服务器回调透传参数，仅在创建广告实例时可传递参数，如果发生变化需要重新创建广告实例
    urlCallback () {
      this._removeInstance()
    }
  },
  created () {
    this._adHelper = new AdHelper(this.adType)

    setTimeout(() => {
      if (this.preload) {
        this._loadAd()
      }
    }, 100)
  },
  methods: {
    load () {
      if (this.isLoading) {
        return
      }
      this._startLoading()
      const invoke = this._isWaterfall() ? 'loadWaterfall' : 'load'
      this._adHelper[invoke](this._getAdOptions(), () => {
        this._onLoad()
      }, (err) => {
        this._onLoadFail(err)
      })
    },

    show () {
      if (this.isLoading) {
        return
      }
      this._startLoading()
      const invoke = this._isWaterfall() ? 'showWaterfall' : 'show'
      this._adHelper[invoke](this._getAdOptions(), () => {
        this._onLoad()
      }, (err) => {
        this._onLoadFail(err)
      }, (res) => {
        this._dispatchEvent(EventType.Close, res)

        if (this.loadnext) {
          this.load()
        }
      }, () => {
        // show
        this.loading = false
      })
    },

    getProvider () {
      if (Array.isArray(this.adpid)) {
        return null
      }
      return this._adHelper.getProvider(this.adpid)
    },

    _loadAd () {
      if (this._canCreateAd()) {
        this.load()
      }
    },

    _onclick () {
      if (!this.disabled) {
        this.show()
      }
    },

    _getAdOptions () {
      return {
        adpid: this.adpid,
        urlCallback: this.urlCallback
      }
    },

    _isWaterfall () {
      return (Array.isArray(this.adpid) && this.adpid.length > 0)
    },

    _canCreateAd () {
      let result = false
      if (Array.isArray(this.adpid) && this.adpid.length > 0) {
        result = true
      } else if (typeof this.adpid === 'string' && this.adpid.length > 0) {
        result = true
      } else if (typeof this.adpid === 'number') {
        result = true
      }
      return result
    },

    _removeInstance (adpid) {
      const id = adpid || this.adpid
      if (Array.isArray(id)) {
        id.forEach((item) => {
          this._adHelper.remove(item)
        })
      } else if (id) {
        this._adHelper.remove(id)
      }
    },

    _startLoading () {
      this.loading = true
      this.errorMessage = null
    },

    _onLoad () {
      this.loading = false
      this._dispatchEvent(EventType.Load, {})
    },

    _onLoadFail (err) {
      this.loading = false
      this.errorMessage = JSON.stringify(err)
      this._dispatchEvent(EventType.Error, err)
    },

    _dispatchEvent (type, data) {
      this.$emit(type, {
        detail: data
      })
    }
  }
}
