import { Injectable, Inject, PLATFORM_ID } from '@angular/core'
import { Location, isPlatformBrowser } from '@angular/common'
import { Router } from '@angular/router'

import { Subscription, Subject, Observable } from 'rxjs'
import { of } from 'rxjs/observable/of' // TODO: Move to correct location once it's available

import { CookieService } from 'ng2-cookies'
import { isObject } from 'util'

// Settings
import { AppHelper }            from '@k-settings/app-helper'

// Local services
import { ConfiguratorService } from './svc.configurator'
import { LocalstorageService } from '@k-core/services/general/storage'
import { ICurtain, ICurtainData } from '@k-core/interfaces/configurator/interface.curtain'

@Injectable()
export class ConfiguratorProductLoaderService {
    
    
    // ---- Variables ---- \\

    // Product
    productElement: ICurtain

    base: string = AppHelper.configurator
    url: string
    isCopy: boolean = false
    isModify: boolean = false
    setPassed: boolean
    public stopPreset: boolean = false


    // Timeouts
    applicationtimeout: ReturnType<typeof setTimeout>

    public storedId: string = ''
    cookie: string = 'viHuskerDinKonfiguration'


    // ---- Observables ---- \\
    presetValue: ICurtainData

    presetSource = new Subject<ICurtainData>()
    preset$: Observable<ICurtainData> = this.presetSource.asObservable()

    subscriptions: Subscription = new Subscription()

    constructor(
        @Inject(PLATFORM_ID) private platformId: Object,
        public localStorage: LocalstorageService,
        private _router: Router,
        private _location: Location,
        private _service: ConfiguratorService,
        private _cookie: CookieService
    ) {

        this.subscriptions.add(

            _router.events.subscribe((e: any) => {
    
                // Stops function from executing on any page but `base`
                if(!!e.url && e.url.includes(this.base)) {
    
                    this.isCopy = false
    
                    // Only read query if query exists
                    this.triggerQuery()
                }
            })
        )


        // Subscribe to Observables
        this.subscriptions.add(

            this.preset$.subscribe((response) => {
                this.presetValue = response
                this.productElement = {data: response}
            })
        )
    }




    // ---- Lifecycle hooks ---- \\
    ngOnDestroy() {
        this.setPassed = false
        this.presetSource.complete()
        this.subscriptions.unsubscribe()
    }


    public get preset(): ICurtainData {
        return this.presetValue
    }
    

    // ---- GETs ---- \\
    public GETPreset(): ICurtainData {
        return this.preset
    }


    public triggerQuery(): void {
        if(this._router.url.includes(AppHelper.configurator) && this._router.url.split('/').length > 2) {
            this.readQuery()
        } else {
            this.clearStealthySelection()
        }
    }



    // ---- Functions ---- \\

    /**
     * Creates a query for the product, and puts it in the URL string as a "pretty"
     * URL path
     *
     * @param product
     * @returns void
     */
    private _addChangesToQuery(product: ICurtain): void {

        let specs: string = this.base
        let i: number = 0

        for(let element of Object.keys(product)) {

            if(isObject(product[element])) {
                for(let value of Object.keys(product[element])) {
                    let item: string = product[element]
                    specs += '/' + value + '/' + item[value]
                }
            } else {

                specs += '/' + element + '/' + product[element]
            }

            i++

            if(i == Object.keys(product).length) {
                this._cookie.set(this.cookie, specs, 1, '/')
                this._location.go(specs)
            }
        }
    }


    public addChangesToQuery$(product: ICurtain): Observable<void> {
        return of(this._addChangesToQuery(product))
    }

    public readQuery$(): Observable<void> {
        return of(this.readQuery())
    }

    /**
     * Reads the string, and builds a product based on the string
     * Protects against posting empty data
     * 
     * @returns void
     */
    public readQuery(): void {

        if(isPlatformBrowser(this.platformId)) {


            this._translateQuery().then((result) => {

                this.productElement = result
                this.presetSource.next(this.productElement.data)
                this.storedId = result.id

            })
        }
    }


    private async _translateQuery(): Promise<ICurtain> {

        // Clear states on request
        this.isCopy = false
        this.isModify = false

        let i: number = 0
        let query: string[] = []
        let queryMap: Map <string, any> = new Map
        let result: ICurtain | undefined

        // Removes path name from URL (eg. /mypath/)
        this.url = window.location.href.split('/' + this.base + '/')[1] || '/'


        // if base is equal to blank, then get values from cookie
        if(!!this.url && this.url.replace('/', '') == this.base) {
            this.url = this._cookie.get(this.cookie).replace(this.base + '/', '')
        }

        // Split the URL string to be a workable element within JS
        query = decodeURIComponent(this.url).split('/')


        // Isolate last query param and check if it's copy/modify.
        // TODO: rewrite this to a single if
        if(query[query.length-1] == 'copy') {
            this.isCopy = query.pop() == 'copy'
        }

        if(query[query.length-1] == 'modify') {
            this.isModify = query.pop() == 'modify'
        }

        // Isolate pair of key/value in a map
        while(i < query.length) {

            // ignore price and quantity for the comparator
            if(query[i] !== 'quantity' && query[i] !== 'price') {
                queryMap.set(query[i], query[i+1])
            }

            i += 2
        }

                        // Dont set the stored ID if it's a copy, as copies should not delete parents
                        //if(results.every((result) => result) && !this.isCopy) {
                        //    this.storedId = item.id
                        //}
                    //}

        // Isolate configurable basket products
        let basket: ICurtain[] = await this.localStorage.getItem(AppHelper.basketIdentifier) ? JSON.parse(this.localStorage.getItem(AppHelper.basketIdentifier)).filter((product) => product.id.includes('-conf-')) : []

        // Flattens object, to remove measurement and detail groups
        const flattenObject: Function = (obj: Object): Object => {
            const flattened: Object = {}

            Object.keys(obj).forEach((key) => {
                if (typeof obj[key] === 'object' && obj[key] !== null) {
                    Object.assign(flattened, flattenObject(obj[key]))
                } else {
                    flattened[key] = obj[key]
                }
            })
            return flattened
        }

        // Compare two objects with keys+values, making a soft compare (==) because typing may vary
        const compareObjects: Function = (o1: Object, o2: Object): boolean => {

            for(let p in o1) {
                if(o1.hasOwnProperty(p)) {
                    if(o1[p] != o2[p]) {
                        return false
                    }
                }
            }
            
            for(let p in o2) {
                if(o2.hasOwnProperty(p)) {
                    if(o1[p] != o2[p]) {
                        return false
                    }
                }
            }

            return true
        }

        // Iterate products in cart and find a comparator that matches
        for await(let product of basket) {

            // Remove surplus fields
            delete product.data['price']
            delete product.data['quantity']

            if(compareObjects(flattenObject(product.data), (Object as any).fromEntries(queryMap))) {
                
                result = product

                break
            }
        }

        // If no result is found, generate a new product from the parameters
        // TODO: Issue with mapping of where data belongs that isn't default-route data (measurements, details)
        if(!result) {
            console.warn('Product was not found, generating a new product')
            result = {data: (Object as any).fromEntries(queryMap)}
        }

        return result
    }


    /**
     * Re-exposes the data, protects against re-exposing data on a clean configurator
     */
    public reExpose(type): void {

        if(this.applicationtimeout)
            clearTimeout(this.applicationtimeout)

        this.applicationtimeout = setTimeout(() => {

            if(!this.productElement) {
                this.reExpose(type)
            } else {

                if(!this.stopPreset) {
                    this.presetSource.next(this.productElement.data)
                }
            }
        }, 200)
    }


    /**
     * Clears choices made
     */
    public clearSelection(): void {

        this.clearStealthySelection()

        // Refreshes the actual component page to clear all input values and put you back to pre-selection
        this._router.navigateByUrl('/reloader', {skipLocationChange: true}).then(() =>
        this._router.navigate(['/' + this.base]))
    }


    /**
     * Clears the selection without reloading.
     * When no array is given, all selections are cleared
     * 
     * @param clearArray - Array containing selections to clear
     */
    public clearStealthySelection(clearArray: string[] = []): void {

        const clearTemplate: ICurtain = {
            data: {
                type_id: '',
                dessin: '',
                measurements: {},
                details: {},
                mountHeights: null,
                marking: ''
            },
            id: undefined,
            quantity: undefined
        }
        
        // Bad juju about this one...
        if(isPlatformBrowser(this.platformId)) {

            if(clearArray.length > 0) {
                for(let clearType of clearArray) {
                    if(clearTemplate[clearType] !== undefined)
                        this.productElement[clearType] = clearTemplate[clearType]
                }
            }
            else {
                this.productElement = clearTemplate
            }

            this._cookie.delete(this.cookie, '/')

            this.presetSource.next(clearTemplate.data)
            this._service.populateProduct(clearTemplate.data)
        }
    }


    public clearPreviousBuild(): void {
        this.storedId = ''
    }
}