import { Injectable } from '@angular/core'
import cloneDeep from 'lodash-es/cloneDeep'
import CryptoMD5 from 'crypto-js/md5'
import { SasService } from './sas.service'

const librariesToShow = 50

@Injectable({
  providedIn: 'root'
})
export class HelperService {
  public shownLibraries: number = librariesToShow
  public loadMoreCount: number = librariesToShow

  constructor(private sasService: SasService) {}

  /**
   * Converts a JavaScript date object to a SAS Date or Datetime, given the logic below:
   *
   * A JS Date contains the number of _milliseconds_ since 01/01/1970
   * A SAS Date contains the number of _days_ since 01/01/1960
   * A SAS Datetime contains the number of _seconds_ since 01/01/1960
   *
   * @param jsDate JS Date to be converted. The type is instance of `Date`
   * @param unit Unit in which to return the SAS Date / datetime, eg `sasdate | sasdatetime`
   * @returns SAS Date value based on `unit` param
   */
  public convertJsDateToSasDate(
    jsDate: string | Date,
    unit: string = 'days'
  ): number {
    let jsDateObject: Date
    let valueInMilliseconds: number = 0

    if (!(jsDate instanceof Date)) {
      jsDateObject = new Date(jsDate)
    } else {
      jsDateObject = jsDate
    }

    valueInMilliseconds = new Date(
      Date.UTC(
        jsDateObject.getFullYear(),
        jsDateObject.getMonth(),
        jsDateObject.getDate(),
        jsDateObject.getHours(),
        jsDateObject.getMinutes(),
        jsDateObject.getSeconds()
      )
    ).valueOf()

    const msInDay = 24 * 60 * 60 * 1000
    const msInTenYears = 315619200000 //since calculating it is not practical to get correct value, this is parsed with: new Date(Date.UTC(1960, 0, 1)).getTime()

    const sasMilliseconds = valueInMilliseconds + msInTenYears

    switch (unit) {
      case 'days': {
        let valueInDays = sasMilliseconds / msInDay

        valueInDays = Math.abs(valueInDays)
        valueInDays = Math.floor(valueInDays)

        return valueInDays
      }
      case 'seconds': {
        return sasMilliseconds / 1000
      }
    }

    return 0
  }

  /**
   * Converts a SAS Date or Datetime to a JavaScript date object, given the logic below:
   *
   * A JS Date contains the number of _milliseconds_ since 01/01/1970
   * A SAS Date contains the number of _days_ since 01/01/1960
   * A SAS Datetime contains the number of _seconds_ since 01/01/1960
   *
   * @param sasValue SAS Date or Datetime to be converted. The type could be `number` or `string.
   * @param unit Unit from which to convert the SAS Date / Datetime, eg `sasdate | sasdatetime`
   * @returns JavaScript Date object
   */
  public convertSasDaysToJsDate(
    sasValue: number | string,
    unit: string = 'days'
  ) {
    const msInDay = 24 * 60 * 60 * 1000
    const msNegativeTenYears = -315619200000 //since calculating it is not practical to get correct value, this is parsed with: new Date(Date.UTC(1960, 0, 1)).getTime()

    if (typeof sasValue !== 'number') sasValue = parseFloat(sasValue)

    if (unit === 'seconds') {
      let sasMs = sasValue * 1000

      let jsMs = msNegativeTenYears + sasMs

      let timezoneOffsetMs = new Date(jsMs).getTimezoneOffset() * 60 * 1000

      jsMs += timezoneOffsetMs

      return new Date(jsMs)
    }

    return new Date(msNegativeTenYears + sasValue * msInDay)
  }

  /**
   *
   * @param array all elements in the clarity tree
   * @param arrToFilter sub array in the tree to be filtered for example `tables`
   */
  public treeOnFilter(array: any, arrToFilter: string) {
    let search = array['searchString'] ? array['searchString'] : ''
    let arrToFilterArray = arrToFilter.split('.')[0]
    let arrToFilterField = arrToFilter.split('.')[1]

    let arrToFilterAll = arrToFilterArray + 'All'

    if (array[arrToFilterArray]) {
      if (!array[arrToFilterAll]) {
        array[arrToFilterAll] = this.deepClone(array[arrToFilterArray])
      }

      array[arrToFilterArray] = this.deepClone(array[arrToFilterAll])

      if (search.length > 0) {
        if (arrToFilterField) {
          array[arrToFilterArray] = array[arrToFilterArray].filter(
            (item: any) => {
              return item[arrToFilterField]
                .toLowerCase()
                .includes(search.toLowerCase())
            }
          )
        } else {
          array[arrToFilterArray] = array[arrToFilterArray].filter(
            (item: any) => {
              return item.toLowerCase().includes(search.toLowerCase())
            }
          )
        }
      }
    }
  }

  public libraryOnFilter(array: any, search: string, fieldToMatch: any) {
    if (search.length > 0) {
      array.forEach((item: any) => {
        if (!item[fieldToMatch].toLowerCase().includes(search.toLowerCase())) {
          item['hidden'] = true
          item['inForeground'] = false
        } else {
          item['hidden'] = false
          item['inForeground'] = true
        }
      })
    } else {
      this.resetArrayFilter(array)
      this.displayLibraries(array)
    }
  }

  public displayLibraries(array: any, loadMore?: boolean) {
    if (loadMore) {
      this.shownLibraries += this.loadMoreCount
    } else {
      this.shownLibraries = librariesToShow

      this.resetLibraryForeground(array)
    }

    for (let index = 0; index < array.length; index++) {
      if (index === this.shownLibraries) {
        break
      }

      array[index]['inForeground'] = true
    }
  }

  public resetLibraryForeground(array: any) {
    for (let index = 0; index < array.length; index++) {
      array[index]['inForeground'] = false
    }
  }

  public metaObjectOnFilter(array: any, search: string, fieldToMatch: any) {
    // array = this.deepClone(arrayAll);

    if (search.length > 0) {
      this.resetArrayFilter(array)
      array.forEach((item: any) => {
        if (!item[fieldToMatch].toLowerCase().includes(search.toLowerCase())) {
          item['hidden'] = true
        }
      })
    } else {
      this.resetArrayFilter(array)
    }
  }

  public resetArrayFilter(array: any) {
    array.forEach((item: any) => {
      item['hidden'] = false

      if (item['inForeground']) {
        delete item['inForeground']
      }
    })
  }

  public convertObjectsToArray(
    objectArray: Array<object>,
    deepClone: boolean = false
  ) {
    if (deepClone) {
      objectArray = this.deepClone(objectArray)
    }

    return objectArray.map((obj: any) => {
      return Object.keys(obj).map((key) => {
        return obj[key]
      })
    })
  }

  public addLeadingZero(value: string | number): string {
    if (typeof value !== 'string') value = value.toString()

    return value.length < 2 ? '0' + value : value
  }

  /**
   * Hashes the object, if array provided it first deletes the keys from object
   * @param data data to hash, it will be deep cloned
   * @param keysToRemove keys to remove from data object
   * @param clone selecting whether to deep clone data or not. Default is true
   * @returns hashed string
   */
  public deleteKeysAndHash(
    data: any,
    keysToRemove: string[],
    clone: boolean = true
  ): string {
    const dataToHash = clone ? this.deepClone(data) : data

    for (let key of keysToRemove) {
      delete dataToHash[key]
    }

    return CryptoMD5(JSON.stringify(dataToHash)).toString()
  }

  downloadTextFile(filename: string, text: string) {
    const element = document.createElement('a')
    element.setAttribute(
      'href',
      'data:text/plain;charset=utf-8,' + encodeURIComponent(text)
    )
    element.setAttribute('download', filename + '.txt')

    element.style.display = 'none'
    document.body.appendChild(element)

    element.click()

    document.body.removeChild(element)
  }

  public convertArrayValues(
    array: any[],
    type: 'string' | 'number'
  ): string[] | number[] {
    if (array.length < 1) return []

    switch (type) {
      case 'number': {
        return array.map((value) => value * 1)
      }
      case 'string': {
        return array.map((value) => `${value}`)
      }
    }
  }

  public isStreamingViya(): boolean {
    const serverType = this.sasService.getServerType()

    if (serverType !== 'SASVIYA') return false

    if (
      location.search.toLowerCase().includes('?_file=') &&
      location.pathname.toLowerCase().includes('/sasjobexecution')
    )
      return true

    return false
  }

  // Required type is NodeJS.Timeout
  // But NodeJS is not available in browser so we have to go with any
  private debounceTimeout: any
  public debounceCall(time: number, callback: () => void) {
    clearTimeout(this.debounceTimeout)

    this.debounceTimeout = setTimeout(callback, time)
  }

  public deepClone(item: any) {
    return cloneDeep(item)
  }
}
