import {
  Event,
  EventTeaser, ExceptionObject,
} from '../core/typings'

import { debug } from '../logging'

import CfSender from './CfSender'
import { ContentBlock } from '../modules/Navigation/typings'

import ImpressionsDetector from './impressions'
import { ImpressionEventType } from './impressions/typings'
import { toISOLocal } from '../utils'
import CfExceptionHandler from "./CfExceptionHandler";
import {getLocalStorageUserId} from "../drivers/CfSystem";

export default class CfCore {
  private static instance: CfCore;
  private deviceId: string
  private searchId = ''
  private sender: CfSender
  private isUsingAsInternalWrapper: boolean
  private impressionDetectorClass
  private impressionsDetectors: {
    [key: string]: ImpressionsDetector
  } = {}
  private defaultBlock
  private debug
  private currentBlock


  private user_id = ''

  private constructor(
    sender: CfSender,
    impressionDetector,
    debugging: boolean,
    defaultBlock: ContentBlock,
    deviceId: string,
    isUsingAsInternalWrapper: boolean
  ) {
    this.sender = sender
    this.impressionDetectorClass = impressionDetector

    this.deviceId = deviceId
    this.defaultBlock = defaultBlock
    this.debug = debug(debugging)
    this.isUsingAsInternalWrapper = isUsingAsInternalWrapper
  }

  public static createInstance(
    sender: CfSender,
    impressionDetectorClass,
    debugging: boolean,
    defaultBlock: ContentBlock,
    deviceId: string,
    isUsingAsInternalWrapper : boolean) {
    if (!!CfCore.instance) {
      return CfCore.instance
    }

    CfCore.instance = new CfCore(sender, impressionDetectorClass, debugging, defaultBlock, deviceId, isUsingAsInternalWrapper)
    return CfCore.instance
  }

  public static getInstance(): CfCore {
    if (!CfCore.instance) {
      new CfExceptionHandler().throwAndLogError('CfLog','CfLog instance not created')
    }

    return CfCore.instance;
  }

  public login(userId: string) {
    this.user_id = userId
  }

  private resolveModule(module: ContentBlock): ContentBlock {
    // logPageEvents are triggered automatically by the system. We do not
    // know whether the web application calls setCurrentBlock before or after that:
    const delta = 50;

    if (module === ContentBlock.Core) {
      // in this case the event could be triggered by several modules
      // so the SDK must decide which one must be included in the event
      // sent to server (i.e.: page or media events)
      if (this.currentBlock) {
        return this.currentBlock
      } else {
        return this.defaultBlock
      }

    } else {
      // specific module (i.e.: triggered by the ELearning module)
      return module
    }
  }

  private resolveUserId(overWrittedUserId: string, user_id: string, device_id: string): string {
    if (overWrittedUserId && overWrittedUserId.length !== 0) {
      return overWrittedUserId
    }

    return user_id || getLocalStorageUserId() || device_id
  }

  public async trackEvent(
    eventTeaser: EventTeaser,
    moduleName: ContentBlock,
    overWrittedUserId: string,
    sendNow: boolean
  ): Promise<void> {

    const event: Event = {
      ...eventTeaser,
      block: this.resolveModule(moduleName),
      ol: true,
      ts: toISOLocal(new Date())
    }

    this.sender.add(this.resolveUserId(overWrittedUserId, this.user_id, this.deviceId), this.deviceId, event, sendNow,
        this.isUsingAsInternalWrapper)
  }

  public async trackExceptionEvent(
      exceptionList: ExceptionObject[]
  ): Promise<void> {

    this.sender.sendException(exceptionList)
  }

  public startTrackingImpressions(impressionHandler, containerClassname, itemClassname, searchId) {

    this.impressionsDetectors[containerClassname] = new this.impressionDetectorClass({ intersectionThreshold: 0.5 })

    this.impressionsDetectors[containerClassname].on(ImpressionEventType.Impression, impressionHandler)

    this.impressionsDetectors[containerClassname].start(containerClassname, itemClassname, { search_id: searchId })
  }

  public stopTrackingImpressions(containerClassname) {

    if (!containerClassname) {
      Object.values(this.impressionsDetectors).forEach(detector => detector.stop())
      return
    }

    if (!this.impressionsDetectors[containerClassname]) {
      return
    }

    this.impressionsDetectors[containerClassname].stop()
  }

  public restartTrackingImpressions(containerClassname, searchId) {
    if (!containerClassname || !this.impressionsDetectors[containerClassname]) {
      console.error('Impossible to restart impression to unknown containerClassname')
      throw new Error('unknown-container')
    }

    this.impressionsDetectors[containerClassname].restart({ search_id: searchId })
  }

  public setCurrentBlock(block: ContentBlock) {
    // the reason of this delay is that the page event is triggered
    // 10 miliseconds after it really happens to wait for the title
    setTimeout(() => {
      this.currentBlock = block
    }, 15)

  }

  public setTitle(title: string) {
    // do nothing yet
  }

}
