import OrderBookEntry from "../../../apiwrapper/orderbook/OrderBookEntry.js";
import UnitHelper from "../../util/UnitHelper.js";
import TimedAssetDataMerger from "./TimedAssetDataMerger.js";

export default class TimedAssetData {
    /**
     * @type {number}
     */
    #duration = 0;

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

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

    /**
     * @type {boolean}
     */
    #closed = false;

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

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

    /**
    * @type {number}
    */
    #high = Number.MIN_VALUE;

    /**
    * @type {number}
    */
    #low = Number.MAX_VALUE;

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

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

    /**
     * @type {OrderBookEntry}
     */
    #bestBid = null;

    /**
     * @type {OrderBookEntry}
     */
    #bestAsk = null;

    /**
     * @type {TimedAssetData[]}
     */
    #subSlots = [];

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

    /**
     * @param {number[]} durations 
     */
    constructor(durations) {
        this.#duration = durations.pop();
        this.#subSlotDurations = [...durations];
    }

    /**
     * @param {number} startTime 
     */
    open(startTime) {
        if (startTime != null) {
            this.#startTime = startTime;
        } else {
            this.#startTime = Date.now();
        }

        this.#closed = false;
    }

    /**
     * @param {number} endTime 
     */
    close(endTime) {
        if (this.#closed) {
            return;
        }

        if (endTime != null) {
            this.#endTime = endTime;
        } else {
            this.#endTime = Date.now();
        }

        this.#closed = true;

        if (this.#subSlots != null && this.#subSlots.length > 0) {
            this.#subSlots[this.#subSlots.length - 1].close(endTime);
        }
    }

    removeSubData() {
        if (!this.#closed) {
            return;
        }

        this.#subSlots.forEach(slot => {slot.close(); slot.removeSubData()});
        this.#subSlots = [];
    }

    /**
     * @returns {TimedAssetData[]}
     */
    getSubData() {
        return this.#subSlots;
    }

    /**
     * @type {TimedAssetData}
     */
    getSlotForPeriode(chronoString){
        const periode = UnitHelper.formatedDurationToMs(chronoString);

        if(this.getDuration() <= periode){
            return this;
        }

        if(this.#subSlotDurations == null || this.#subSlotDurations.length == 0){
            return null;
        }

        const subSlotDuration = this.#subSlotDurations[this.#subSlotDurations.length - 1];

        if(subSlotDuration > periode){
            const lastSlotIndex =  this.#subSlots.length - 1;
            if((this.#subSlots[lastSlotIndex].getEndTime() - this.#subSlots[lastSlotIndex].getStartTime()) >= periode * 0.7){
                return this.#subSlots[lastSlotIndex].getSlotForPeriode(periode);
            } else {
                return this.#subSlots[Math.max(lastSlotIndex-1), 0].getSlotForPeriode(periode);
            }
        }

        const merger = new TimedAssetDataMerger();
        for(let i = this.#subSlots.length - 1, duration = 0; i >= 0 && duration < periode; i--){
            merger.push(this.#subSlots[i]);
            duration += this.#subSlots[i].getEndTime() - this.#subSlots[i].getStartTime();
        }

        return merger.merge(periode);
    }

    /**
     * @returns {number}
     */
    getDuration() {
        return this.#duration;
    }

    /**
     * @param {number} duration 
     */
    setDuration(duration){
        this.#duration = duration;
    }

    /**
     * @returns {number}
     */
    getStartTime() {
        return this.#startTime;
    }

    /**
     * @returns {number}
     */
    getEndTime() {
        return this.#endTime != 0 ? this.#endTime : Date.now();
    }

    /**
    * @returns {number}
    */
    getOpen() {
        return this.#open;
    }

    /**
    * @returns {number}
    */
    getClose() {
        return this.#close;
    }

    /**
    * @returns {number}
    */
    getHigh() {
        return this.#high;
    }

    /**
    * @returns {number}
    */
    getLow() {
        return this.#low;
    }

    /**
    * @returns {number}
    */
    getVolume() {
        return this.#volume;
    }

    /**
    * @returns {number}
    */
    getTrades() {
        return this.#trades;
    }

    /**
     * @returns {OrderBookEntry}
     */
    getBestBid() {
        return this.#bestBid;
    }

    /**
     * @returns {OrderBookEntry}
     */
    getBestAsk() {
        return this.#bestAsk;
    }

    /**
     * @param {number} time
     * @returns {boolean} 
     */
    isInSlot(time) {
        if (this.#closed) {
            return false;
        }

        return (time - this.#startTime) < this.#duration;
    }

    /**
     * @param {number} high 
     * @param {number} low 
     * @param {number} open 
     * @param {number} close 
     */
    push(high, low, open, close) {
        this.#high = Math.max(this.#high, high);
        this.#low = Math.min(this.#low, low);
        this.#close = close;

        if (this.#open <= 0) {
            this.#open = open;
        }

        const subSlot = this.#getSubSlot();
        if (subSlot) {
            subSlot.push(high, low, open, close);
        }
    }

    /**
     * 
     * @param {number} time 
     * @param {number} price 
     * @param {number} quantity 
     */
    pushTrade(time, price, quantity) {
        this.#trades ++;
        this.#volume += quantity;

        const subSlot = this.#getSubSlot();
        if (subSlot) {
            subSlot.pushTrade(time, price, quantity);
        }
    }

    /**
     * @param {number} count 
     * @param {number} avgPrice 
     * @param {number} quantity 
     */
    setTradeValues(count, avgPrice, quantity){
        this.#trades = count;
        this.#volume = quantity;
    }

    /**
     * 
     * @param {OrderBookEntry} ask 
     */
    pushBestAsk(ask) {
        if (this.#bestAsk == null || this.#bestAsk.price < ask.price) {
            this.#bestAsk = ask;
        }

        const subSlot = this.#getSubSlot();
        if (subSlot) {
            subSlot.pushBestAsk(ask);
        }
    }

    /**
     * 
     * @param {OrderBookEntry} bid 
     */
    pushBestBid(bid) {
        if (this.#bestBid == null || this.#bestBid.price > bid.price) {
            this.#bestBid = bid;
        }

        const subSlot = this.#getSubSlot();
        if (subSlot) {
            subSlot.pushBestBid(bid);
        }
    }

    /**
     * 
     * @returns {TimeSlot}
     */
    #getSubSlot() {
        if (this.#subSlotDurations == null || this.#subSlotDurations.length < 1) {
            return null;
        }

        let activeSubSlot = null;

        if (this.#subSlots.length == 0) {
            activeSubSlot = new TimedAssetData([...this.#subSlotDurations]);
            activeSubSlot.open(Date.now());
            this.#subSlots.push(activeSubSlot);
        } else {
            activeSubSlot = this.#subSlots[this.#subSlots.length - 1];

            if (!activeSubSlot.isInSlot(Date.now())) {
                activeSubSlot.close(Date.now());
                activeSubSlot = new TimedAssetData([...this.#subSlotDurations]);
                activeSubSlot.open(Date.now());
                this.#subSlots.push(activeSubSlot);
            }
        }

        return activeSubSlot;
    }
}