import TradingPair from "../../apiwrapper/assets/TradingPair.js";
import Transaction from "../../apiwrapper/transaction/Transaction.js";
import TransactionSettings from "../../apiwrapper/transaction/TransactionSettings.js";
import AssetData from "../data/asset/AssetData.js";
import AbstractStrategy from "../strategy/AbstractStrategy.js";
import AbstractWorker from "./AbstractWorker.js";

export default class StatisticWorker extends AbstractWorker {
    static #ATTRIBUTE_NAME_TRADES = "TRADES_STATISTIC";
    static #ATTRIBUTE_NAME_TRADE_VOLUME = "TRADE_VOLUME_STATISTIC";
    static #ATTRIBUTE_NAME_PRICE = "PRICE_STATISTIC";
    static #ATTRIBUTE_NAME_PEAK_COUNT = "PEAK_COUNT_STATISTIC";
    static #ATTRIBUTE_NAME_FULL = "FULL_STATISTIC";

    /**
     * @type {[number, number][]}
     */
    #tradeCount = [];

    /**
     * @type {[number, number][]}
     */
    #tradeVolume = [];

    /**
     * @type {[number, number][]}
     */
    #price = [];

    /**
     * @type {[number, boolean][]}
     */
    #peakStart = [];

    /**
     * @type {[number, boolean][]}
     */
    #peakEnd = [];

    /**
     * @type {[number, number][]}
     */
    #buys = [];

    /**
     * @type {[number, number][]}
     */
    #sells = [];

    /**
     * @type {number}
     */
    #peackCount = 0;

    /**
     * @type {Transaction[]}
     */
    #transactions = [];

    /**
     * @type {number}
     */
    #lastBuy = 0;

    /**
     * @type {number}
     */
    #buyCount = 0;

    /**
     * @type {number}
     */
    #sellCount = 0;

    /**
     * @type {number}
     */
    #lastPeakEndTimestamp = 0;

    /**
     * @type {number}
     */
    #overallWin = 0;

    /**
     * @type {number}
     */
    #lastTickTimestamp = 0;

    /**
     * @param {string} name 
     * @param {TradingPair} tradingPair 
     * @param {AbstractStrategy} strategy 
     */
    constructor(name, tradingPair, strategy) {
        super(name, tradingPair, strategy);
    }

    onInit() {
        this.getLogger().debug("Statistic worker " + this.getName() + " for " + this.getTradingPair().getKey() + " initialized!");
    }

    /**
     * @param {AssetData} assetData 
     * @param {Transaction[]} transactions 
     */
    onUpdate(assetData, transactions) {
        const strategy = this.getStrategy();
        if(strategy != null){
            strategy.setAssetData(assetData);
            const doneTransactions = [];

            if(strategy.shouldBuy()){
                const price = assetData.getOrderBook().getBestBid().price;
                const t = new Transaction(this.getId(), this.getTradingPair());
                t.buySettings = new TransactionSettings(price, 50/price);
                t.buySettings.price = price;
                t.buySettings.quantity = t.buySettings.orderQuantity;
                this.getStrategy().addTransaction(t);
                this.#transactions.push(t);
                this.#buyCount++;
                this.#lastBuy = Date.now();
            }

            this.#transactions.forEach((t, i) => {
                if(strategy.shouldSell(t)){
                    doneTransactions.push(i);
                    this.#overallWin += t.buySettings.quantity * assetData.getOrderBook().getBestBid().price - t.buySettings.quantity * t.buySettings.price;
                    this.#sellCount++;
                }
            });

            doneTransactions.sort((a, b) => a-b);
            for(let i = doneTransactions.length - 1; i > 0; i--){
                this.#transactions.splice(i, 1);
            }
        }

        this.#updateStatistics(assetData);
    }

    getAttribute(attributeName) {
        switch (attributeName) {
            case StatisticWorker.#ATTRIBUTE_NAME_TRADES:
                return this.#tradeCount;
            case StatisticWorker.#ATTRIBUTE_NAME_TRADE_VOLUME:
                return this.#tradeVolume
            case StatisticWorker.#ATTRIBUTE_NAME_PRICE:
                return this.#price;
            case StatisticWorker.#ATTRIBUTE_NAME_PEAK_COUNT:
                return this.#peackCount;
            case StatisticWorker.#ATTRIBUTE_NAME_FULL:
                return this.#convertToFullStatistic();
        }
    }

    #convertToFullStatistic() {
        const obj = {
            symbol: this.getTradingPair().getKey(),
            peakCount: this.#peackCount,
            strategyWin: this.#overallWin,
            entries: []
        };

        for (let i = 0; i < this.#price.length; i++) {
            obj.entries.push({
                timestamp: this.#price[i][0],
                tradeCount: this.#tradeCount[i][1],
                tradeVolume: this.#tradeVolume[i][1],
                maxPrice: this.#price[i][1],
                minPrice: this.#price[i][2],
                avgPrice: this.#price[i][3],
                peakStart: this.#peakStart[i][1],
                peakEnd: this.#peakEnd[i][1],
                buys: this.#buys[i][1],
                sells: this.#sells[i][1],
            });
        }

        return obj;
    }

    /**
     * @param {AssetData} assetData 
     */
    async #updateStatistics(assetData) {
        if ((Date.now() - this.#lastTickTimestamp) < 60000){
            return;
        }
        this.#lastTickTimestamp = Date.now();
        const lastMinAssetData = assetData.getSlotForPeriode('1m');

        if (lastMinAssetData == null || lastMinAssetData.getHigh() == Number.MIN_VALUE) {
            return;
        }

        const timestamp = Date.now()
        const peaks = assetData.getPeakDetector().peaks;
        this.#peackCount = peaks.length;
        this.#tradeCount.push([timestamp, lastMinAssetData.getTrades()]);
        this.#tradeVolume.push([timestamp, lastMinAssetData.getVolume()]);
        this.#price.push([timestamp, lastMinAssetData.getHigh(), lastMinAssetData.getLow(), (lastMinAssetData.getHigh() + lastMinAssetData.getLow()) / 2]);
        this.#peakEnd.push([timestamp, false]);
        this.#peakStart.push([timestamp, false]);
        this.#buys.push([timestamp, this.#buyCount]);
        this.#sells.push([timestamp, this.#sellCount]);

        peaks.sort((p1, p2) => p1.startTime - p2.startTime);
        let lastPeakIndex = 0;
        for(let i = 0; i < peaks.length; i++){
            if(this.#lastPeakEndTimestamp <= peaks[i].endTime){
                lastPeakIndex = i;
                break;
            }
        }

        for(let i = 0; i < this.#peakEnd.length; i++){
            for(let pi = lastPeakIndex; pi < peaks.length; pi++){
                if(peaks[pi].startTime >= this.#peakStart[i][0] - 60000 && peaks[pi].startTime <= this.#peakStart[i][0]){
                    this.#peakStart[i][1] = true;
                }

                if(peaks[pi].endTime >= this.#peakEnd[i][0] - 60000 && peaks[pi].endTime <= this.#peakEnd[i][0]){
                    this.#peakEnd[i][1] = true;
                    this.#lastPeakEndTimestamp = peaks[pi].endTime;
                }
            }
        }

        this.#buyCount = 0;
        this.#sellCount = 0;
    }

    /**
    * @returns {string} string representation of this object
    */
    toJson() {
        return super.toJson();
    }

    /**
     * @param {string} objString 
     * @returns {AbstractWorker}
     */
    static fromJson(objString) {
        return super.fromJson(objString, new StatisticWorker());
    }
}