// NG imports
import { Component, OnInit, OnDestroy, Injectable, Inject, ApplicationRef, PLATFORM_ID } from '@angular/core'
import { Location, isPlatformBrowser } from '@angular/common'
import { Router } from '@angular/router'
import { HttpClient } from '@angular/common/http'

// Additional Libs
import { ISubscription } from 'rxjs/Subscription'

// Settings
import { AppHelper } from '@k-settings/app-helper'
import { website, store } from '@k-settings/app-setup'

// Services
import { DiscountsService } from '@k-common/discount/services/svc.discounts'
import { BasketService }    from '@k-services/svc.basket'
import { HelperService }    from '@k-services/svc.helper'
import { PageService }      from '@k-services/svc.page'
import { SeoService }       from '@k-services/svc.seo'

// Types
import { ProductType } from '@k-types/product'
import { CheckFactory } from '@k-services/factories/fac.check';





/**
 * @since > Mon Dec 17 2018
 * @author Kasper Hansen - Klean
 * @description Handles basket items, discounts and total price calculations...
 * ______________________________________________
 * @since Mon Dec 17 2018
 * @author Charles Gouldmann - Klean
 */

@Component({
    moduleId: module.id+ '',
    selector: 'basket',
    templateUrl: './tpl.basket.pug',
    styleUrls: ['sty.basket.scss']
})

export class BasketComponent implements OnInit, OnDestroy {

    // ---- Variables ---- \\

    // TODO: This needs a clean up
    basketItems: ProductType[] = []
    total:number = 0
    plainTotal = 0
    toBasket: any
    addingToCart: boolean = false
    Object = Object
    orderMap = new Map
    loadPriceChanges: boolean = false
    hasCoupons: boolean = false


    attributeOrderMap = new Map
    priceMap = new Map
    
    
    // SetTimeout flag and container
    cartTimeoutFlag: boolean = false
    run: any
    
    // Map to contain staging a removal of a product
    stageProduct = new Map
    
    // Receives a `Map` through subscription with translated strings
    attributeTranslations = new Map
    outputValues = new Map
    
    // Discount stuff
    discountTimeout: any
    discount: number = 0
    discountPercentage: number = 0
    discountItems = []
    

    // ---- Login behaviour
    loginState: any


    // ---- Validation
    noItemsFlag: boolean = false
    disable: string
    showError: boolean = ((this.basketItems.length > 0) ? true : false )


    // ---- Layout
    image_path = this.helper.mediaServer + 'i/type/'
    website: string = website
    store: string = store

    // Affixes
    attributesWithAffixes = [
    {
        name: 'height',
        affix: 'cm'
    },
    {
        name: 'width',
        affix: 'cm'
    },
    {
        name: 'a',
        affix: 'cm'
    },
    {
        name: 'b',
        affix: 'cm'
    },
    {
        name: 'c',
        affix: 'cm'
    },
    {
        name: 'd',
        affix: 'cm'
    },
    {
        name: 'angle',
        affix: '°'
    },
    {
        name: 'mountHeights',
        affix: 'cm'
    },
    {
        name: 'mountPosition',
        affix: 'cm'
    }]

    attr = this._basketService.attr
    draw: boolean = false
    
    // Subscriptions
    basketSubscription: ISubscription
    attributeSubsciption: ISubscription

    // ---- Constructor ---- \\
    constructor(
        @Inject(PLATFORM_ID) public platformId: Object,
        private _router: Router,
        public _basketService: BasketService,
        public helper: HelperService,
        private _page: PageService,
        private _seo: SeoService,
        public _http: HttpClient,
        private _discount: DiscountsService,
        private _checkFactory: CheckFactory
    ) {
        // Subscribe to translations
        this.attributeSubsciption = _basketService.attribute$.subscribe(result => {
            this.attributeTranslations = result
        })

        // Get page SEO
        this._page.getPageContent(AppHelper.basket)
            .then((content) => {
                this._seo.setTitle(content.meta.title)

                if(!!content.meta && !!content.meta.description)
                    this._seo.updateTag('description', content.meta.description)
            })
    }



    // ---- Lifecycle Hooks ---- \\

    ngOnInit() {

        console.log(' *** INIT BASKET')

        // Listen to observable
        this.basketSubscription = this._basketService.basket$.subscribe(result => {

            this.basketItems = []
            let i = 0

            for(let item of result) {
                let iterator = i
                this._basketService.getBasketItem(item).then((response) => {

                    if(response.type == 'SKU') {
                        this.getSkuPrice(response.id)
                    }

                    this.orderMap.set(response.id, iterator)
                    this.basketItems.push(response)
                    this.calculateTotals(this.basketItems, null, null)
                })

                i++
            }

            // Don't run this in SSR, it crashes the server.
            if(isPlatformBrowser(this.platformId)) {
                let interval = setInterval(() => {
            
                    if(this.basketItems.length > 0) {

                        this.loadDiscounts()

                        clearInterval(interval)
                    }
                }, 200)
            
                interval
            }
            
            if(!this.basketItems.length && result.length < 1) {
                this.noItemsFlag = true
            }

            // Don't draw until subscribe is done
            this.draw = true
        })

        this._checkFactory.checkLogin().subscribe({
            next: (response: any) => {

                this.loginState = response.has_login
            }
        })
        
        for(let order of this.attr) {
            this.attributeOrderMap.set(order.name, order.order)
        }

        // Force Cookie or observable to set basket
        this._basketService.setObject(this._basketService.getBasketProducts())
    }

    
    ngOnDestroy() {

        // TODO: If these are not needed next time you stumble across them, then go ahead and delete them
        //this._basketService.destroyObservable()
        //this.basketItems = []


        // Unsubscribe from observables
        this.basketSubscription.unsubscribe()
        this.attributeSubsciption.unsubscribe()
    }





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



    /**
     * Sets the spinner flag, and loads the discounts from the service as a promise
     */
    loadDiscounts() {

        if(!!this.discountTimeout)
            clearTimeout(this.discountTimeout)

        this.discountTimeout = setTimeout(() => {

            this.loadPriceChanges = true
            console.log(JSON.stringify(this.basketItems))
            this._discount.request(this.basketItems).then((response: number) => {
    
                this.discount = response
                this.loadPriceChanges = false
            })
        }, 100)
    }

    /**
     * Expose `isNaN` to the template
     * 
     * @param value string | number
     * @returns boolean
     */
    isNaN(value) {
        return isNaN(value)
    }


    /**
     * Gets the sort order
     *
     * @param code
     */
    getOrder(code) {

        if(this.attributeOrderMap.has(code))
            return this.attributeOrderMap.get(code)
        else
            return 99

    }


    /**
     * Changes quantity based on input. This is done through Inputs and `UpdownComponent`
     *
     * @param {Number} quantity Should arrive as a number, but parseInt in place, to force string
     * @param {Number} id nummeric sku for product
     * @param {Object} items All items within the basketItems
     */
    changeQuantity(quantity: any, id: number, items: any[]): void {

        this._basketService.updateBasketQuantity(id, parseInt(quantity))
        this.calculateTotals(items, id, quantity)
    }




    /**
     * Finds quantity and multiplies it with price, then fixes the look of price and returns it
     *
     * @param {Number} id number
     * @param {Number} price number
     * @returns {Number} quantity * price - Total product price
     */
    getTotalProductPrice(id: number, price: number): number {


        return (this._basketService.getQuantityInBasket(id) * price);
    }


    
    /**
     * Returns quantity based on SKU
     *
     * @param id: number
     */
    getQuantity(id:number) {
        return this._basketService.getQuantityInBasket(id);
    }



    /**
     * Staging the product for removal by adding it to the `stageProduct` Map
     *
     * @param {string} id Product ID
     */
    prepareRemoval(id: string) {
        this.stageProduct.set(id, true)
    }


    /**
     * Disables product by class, then removes it from checkout page
     * @param id: string
     */
    removeProduct(id, item) {
        this.disable = id;
        this._basketService.removeProductFromBasket(id)
    }



    /**
     * Sets cart into PHP session, doubled down on a Promise to ensure data exists before
     * rendering, then makes sure rendering is done before posting
     * 
     * @param basketItems
     * 
     * TODO: Add type to basketItems
     */
    setCart(basketItems) {
        let customValues = {}


        new Promise(resolve => {
            resolve(basketItems)
        }).then(() => {
            this.addingToCart = true


            if(this.outputValues.size > 0) {
                this.outputValues.forEach((value, key) => {
                    customValues[key] = value
                })
            }


            for(let item of basketItems) {

                switch(item.type) {
                    case 'diverse':
                        item = this.getOverwrites(item)
                    break

                    case 'individual-discount':
                        item = this.getOverwrites(item)
                    break
                }
            }


        }).then(() => {

            this._basketService.setCart(basketItems, customValues).subscribe(result => {

                this.toBasket = ''

                if(result.status == 'success') {
                    window.location.href = `/${result.data.checkoutUrl}`
                } else {
                    this.toBasket = result
                }
                this.addingToCart = false
            })
        })
    }


    /**
     * Sets cart into PHP session, doubled down on a Promise to ensure data exists before
     * rendering, then makes sure rendering is done before posting
     * 
     * @param basketItems
     * 
     * TODO: Add type to basketItems
     */
    setSentCart(basketItems) {

        let customValues = {}

        new Promise(resolve => {
            resolve(basketItems)
        }).then(() => {
            this.addingToCart = true;

            if(this.outputValues.size > 0) {
                this.outputValues.forEach((value, key) => {
                    customValues[key] = value
                })
            }

            for(let item of basketItems) {

                switch(item.type) {
                    case 'diverse':
                        item = this.getOverwrites(item)
                    break

                    case 'individual-discount':
                        item = this.getOverwrites(item)
                    break
                }
            }

        }).then(() => {

            this._basketService.setCart(basketItems, customValues).subscribe(result => {

                this.toBasket = ''

                if(result.status == 'success') {
                    this._router.navigate([AppHelper['sendCart']])
                } else {
                    this.toBasket = result
                }
                this.addingToCart = false
            })
        })
    }



    /**
     * Sets overwrites for requested item
     *
     * @param item
     */
    getOverwrites(item) {

        item.overwrites = []

        for(let option of item.options) {

            switch(option.code) {
                case 'price':
                    item.overwrites.push({'code': option.code, 'value': option.value})
                break

                case 'title':
                    item.overwrites.push({'code': 'name', 'value': option.value})

                break
                case 'type':
                    item.overwrites.push({'code': option.code, 'value': option.value})

                break
            }
        }

        return item

    }


    /**
     * Sends the user to configurator page with the same configuration currently available
     * NB: This is hardcoded to point to a configurator page, and will only load on those
     *
     * @param item
     */
    editConfigurableProduct(item) {

        let url: string = ''

        for(let option of Object.keys(item.options)) {
            let code: string = item.options[option].code
            let value: any = item.options[option].value

            if(value !== "")
                url += '/' + code + '/' + value
                
        }

        console.log('...url', url)

        let configPath = AppHelper['configurator']
        this._router.navigate(['/' + configPath + url])
    }


    /**
     * Clone the item, use the same functionality as edit, but add `/copy` to the string
     * 
     * @param item 
     */
    cloneConfigurableProduct(item) {

        let url: string = ''

        for(let option of Object.keys(item.options)) {
            let code: string = item.options[option].code
            let value: any = item.options[option].value

            url += '/' + code + '/' + value
        }

        let configPath = AppHelper['configurator']
        this._router.navigate(['/' + configPath + url + '/copy'])
    }

    updateProductData(event, id, attribute) {
        this._basketService.updateBasketItemValue(id, attribute, event)
    }


    /**
     * Receives event
     *
     * @param event
     * @param title
     */
    receiveEvent(event, title) {

        this.outputValues.set(title, event)
    }


    /**
     * Gets SKU image
     */
    getSkuImage(options) {

        for(let key of Object.keys(options)) {
            
            let element = options[key]

            if(element.code == 'image')
                return element.value
        }
    }

    /**
     * Gets the price, if it exists within the option (custom configurations like misc product and individual discount functionalities)
     *
     * @param options
     */
    getPrice(options) {

        let result = 0

        for(let option of options) {
            if(option.code == 'price') {
                result = option.value
            }
        }

        return result
    }


    /**
     * Calculates total based on types and criteria
     *
     * @param items     All `this.basketItems`
     * @param id        Current product id, so code can see which product is getting a `quantity` change
     * @param quantity  Current quantity change for `id`, this is required as value is fed before quantity change is applied
     */
    calculateTotals(items, id, quantity) {

        let result = 0
        this.discount = 0

        for(let item of items) {

            if(item.id == id)
                item.quantity = quantity

            switch(item.type) {
                case 'diverse':
                    for(let option of item.options) {

                        if(option.code == 'price')
                            result += (option.value * item.quantity)
                    }

                    break

                case 'SKU':
                        
                    result += this.priceMap.get(item.id) * item.quantity
                    console.log('get SKU price for ' + item.id, this.priceMap.get(item.id))
                    break

                default:
                    result += (item.price * item.quantity)
                    break
            }
        }

        this.total = result
        this.plainTotal = this.total

        // Put total into the discount service
        this._discount.total = this.total

        this.loadDiscounts()

    }


    /**
     * Will return the discounted total.
     * Percentage is calcualted first then fixed discount is subtracted.
     * 
     * If the fixed discount results in a negative value, 0 is returned
     * 
     * @returns number
     */
    calculateDiscount() {
        let discountedPrice = this.total

        if(this.discountPercentage > 0) {
            discountedPrice = discountedPrice - (discountedPrice * (this.discountPercentage / 100))
        }

        if(this.discount > 0) {
            discountedPrice = discountedPrice - this.discount >= 0 ? discountedPrice - this.discount : 0
        }

        return discountedPrice
    }


    showDiscount(type, id) {
        if(id.includes(type))
            return true
        else
            return false
    }

    discountTypes(items, type) {
        let display: boolean = false

        for(let item of items) {
            if(item.id.includes(type))
                display = true
        }

        return display
    }

    /**
     * Find `needle` option in options array and add affix to return value if true.
     * 
     * TODO: Internationalize affix value for fixed and default
     * 
     * @param options {array} - options array with `code` and `value`
     * @param needle {string} - the option you are looking for
     * @param affix  {bool} - add affix or not
     */
    getValueFromOption(options: any, needle: string, affix:boolean = false) {
        let optionValue,
            suffix = affix ? 'kr' : ''


        for(let option of options) {
            if(option.code === needle)
                optionValue = option.value

            // Set affix if true
            if(affix) {
                if(option.code === 'type') {
                    switch(option.value) {
                        case 'percentage':
                            suffix = '%'
                            break;
                        case 'fixed':
                            suffix = 'kr'
                            break;
                        default:
                            suffix = 'kr'
                            break;
                    }
                }
            }
        }

        return optionValue.toString() + ' ' + suffix
    }



    /**
     * Gets the price of a SKU product
     * 
     * @param id 
     */
    getSkuPrice(id) {
        this._basketService.getResultPrice(id.split('--')[1]).then((response) => {

            if(!!response) {

                this.priceMap.set(id, response)
                this.calculateTotals(this.basketItems, null, null)
            }
        })

    }


    /**
     * Retruns attribute affix if attribute is set to have an affix
     *
     * @param attribute
     */
    hasMeasureAffix(attribute: string) {
        let affix = null

        for(let attr of this.attributesWithAffixes) {
            if(attr.name === attribute) {
                affix = attr.affix
            }
        }

        return affix
    }



    /**
     * Loops through items and searches for discount string.
     * Returns boolean based on whether string was found or not
     * 
     * @param items 
     * @returns boolean
     */
    containsDiscounts(items) {
        let containsDiscounts: boolean = false
        let discountString: string = 'individual-discount'

        for(let item of items) {
            if(item.type && item.type === discountString)
                containsDiscounts = true
        }

        return containsDiscounts
    }
}