import { NetworkProxy } from "../../../drivers/network/typings"
import { CurrencyCode } from "../../commonTypes"

import *  as defaultConversions from "./usd.json"

export interface Conversion {
    usd: number
}

export interface ICurrencyRepository {
    convertCurrencyToUSD(currencyFrom: CurrencyCode): Promise<Conversion>
}

interface PendingRequest {
    currencyFrom: CurrencyCode
    resolve: any
}

export default class CurrencyRepository implements ICurrencyRepository {
    private url: string
    private networkDriver: NetworkProxy
    private cache
    private requesting = false
    private pendingRequests:Array<PendingRequest> = []

    public constructor(url, networkDriver) {
        this.url = url
        this.networkDriver = networkDriver

        const datesAreWithinAnHour = (newerDate, olderDate) => {
            const ONE_HOUR = 60 * 60 * 1000
            return (newerDate - olderDate) < ONE_HOUR
        }

        const cacheHandler = {
            get: function(cache, currency) {
                const now = new Date()

                if ((cache[currency] !== undefined) && datesAreWithinAnHour(now, cache[currency].date)) {
                    return cache[currency]
                } else {
                    return undefined
                }
            }
        }

        this.cache = new Proxy({}, cacheHandler)
    }

    private purgeQueue() {
        // Those pending requests that already have a value in the cache will be resolved.
        // For the rest, the process will start again by calling the `request` method 
        // again for them
        const metaPendingRequests = this.pendingRequests.map( item => ({...item, ready: false}))

        metaPendingRequests.forEach( (pendingRequest) => {
            const {currencyFrom, resolve} = pendingRequest
            if (this.cache[currencyFrom] !== undefined) {
                resolve(this.cache[currencyFrom])
                pendingRequest.ready = true
                return
            }
        })

        metaPendingRequests
            .filter( request => !request.ready)
            .forEach( request => this.request(request.currencyFrom, request.resolve))

        this.pendingRequests = []
    }

    private request(currencyFrom, resolve) {
        if (this.requesting) {
            this.pendingRequests.push({currencyFrom, resolve})
            return
        }

        this.requesting = true
        this.networkDriver.get(`${this.url}?from=${currencyFrom}`).then((data) => {
            let { usd } = data as Conversion
            const now = new Date()

            if (isNaN(usd) || usd === null) {
                usd = 1 / defaultConversions.usd[currencyFrom.toLowerCase()]
            } else {
                this.cache[currencyFrom] = { date: now, usd: usd }
            }

            resolve({
                usd: usd
            })

            this.requesting = false

            this.purgeQueue()

        }).catch(e => {
            resolve({
                usd: 1 / defaultConversions.usd[currencyFrom.toLowerCase()],
            })
            return
        })
    }

    public convertCurrencyToUSD = (currencyFrom: CurrencyCode): Promise<Conversion> => {
        return new Promise((resolve, reject) => {
            if (currencyFrom === CurrencyCode.USD) {
                resolve({
                    usd: 1
                })
                return
            }

            if ( this.cache[currencyFrom] !== undefined) {
                resolve(this.cache[currencyFrom])
                return
            }

            // this.request(currencyFrom, resolve)

            const usd = 1 / defaultConversions.usd[currencyFrom.toLowerCase()]
            this.cache[currencyFrom] = { date: new Date(), usd: usd }

            resolve({
                usd: usd
            })

        })

    }
}