import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
import WebViewDataProvider from "./WebViewDataProvider.js";
import ConverterFactory from "./converter/ConverterFactory.js";
import AggregatedDataPointConverter from "./converter/converters/AggregatedDataPointConverter.js";
import TransactionConverter from "./converter/converters/TransactionConverter.js";
import WorkerConverter from "./converter/converters/WorkerConverter.js";

export default class RestClient {

    /**
     * @type {express}
     */
    #service = null;

    /**
     * @type {WebViewDataProvider}
     */
    #dataProvider = null;

    /**
     * @type {number}
     */
    #port = 3333;

    /**
     * @param {number} port 
     */
    constructor(port = 3333){
        this.#port = port;

        this.#service = express();
        this.#service.use(bodyParser.json({ limit: '80mb' }));
        this.#service.use(cors({ origin: '*' }));

        this.#registerEndpoints();
    }

    /**
     * @param {number} port 
     */
    setPort(port){
        this.#port = port;
    }

    /**
     * @param {WebViewDataProvider} dataProvider 
     */
    start(dataProvider){
        this.#dataProvider = dataProvider;
        this.#service.listen(this.#port, () => { });
    }

    #registerEndpoints() {
        this.#registerGetters();
        this.#registerSetters();
    }

    #registerGetters(){
        this.#service.get("/assets/orderbook", (req, res, next) => {
            res.json(this.#getAllOrderBooks());
        });

        this.#service.get("/assets/transactions", (req, res, next) => {
            res.json(this.#getAllTransactions(req.query.type));
        });

        this.#service.get("/assets/balance", (req, res, next) => {
            res.json(this.#getBalances());
        });

        this.#service.get("/asset/:symbol/orderbook", (req, res, next) => {
            res.json(this.#getAssetOrderBook(req.params.symbol));
        });

        this.#service.get("/asset/:symbol/transactions", (req, res, next) => {
            res.json(this.#getTransactions(req.params.symbol, req.query.type));
        });

        this.#service.get("/asset/:symbol/aggregated", (req, res, next) => {
            res.json(this.#getAggregatedAssetData(req.params.symbol, req.query.startTime, req.query.duration, req.query.allowUnfilled));
        });

        this.#service.get("/asset/:symbol/balance", (req, res, next) => {
            res.json(this.#getBalances(req.params.symbol));
        });

        this.#service.get("/worker", (req, res, next) => {
            res.json(this.#getWorker());
        });

        this.#service.get("/worker/:name/attribute/:attr", (req, res, next) => {
            res.json(this.#getWorkerAttribute(req.params.name, req.params.attr));
        });
    }

    #registerSetters(){
        this.#service.get("/asset/:symbol/transaction/:transactionID/sell", (req, res, next) => {
            // TODO sell transaction if status still same as before (query parameter: old status)
            //res.json(this.#getTransactions(req.params.symbol, req.query.type));
        });

        this.#service.get("/asset/:symbol/transaction/:transactionID/cancel", (req, res, next) => {
            // TODO cancle transaction if status still same as before (query parameter: old status)
            //res.json(this.#getTransactions(req.params.symbol, req.query.type));
        });

        this.#service.get("/asset/:symbol/transaction/:transactionID/delete", (req, res, next) => {
            // TODO sell transaction if status still same as before (query parameter: old status)
            //res.json(this.#getTransactions(req.params.symbol, req.query.type));
        });
    }

    #getAggregatedAssetData(asset, starttime, duration, allowUnfilled){
        const st = starttime != null ? starttime : -1;
        const d = duration != null ? duration : 3600000;
        const allowUnfinished= allowUnfilled != null ? allowUnfilled == "true" : false;

        const data = this.#dataProvider.getAssetData(asset).getAggregatedData(d, st, allowUnfinished);
        const converter = new AggregatedDataPointConverter();
        const result = [];

        data.forEach(dp => result.push(converter.toRestObject(dp)));

        return result;
    }

    #getBalances(asset = null){
        /**
         * @type {Map<string, number>}
         */
        const balances = this.#dataProvider.getBalances();
        const result = [];

        if(asset == null){
            balances.forEach((value, key) => result.push({symbol: key, balance: value}));
            return result;
        } else {
            const balance = balances.get(asset);
            if(balance == null){
                return null;
            } else {
                return {symbol: asset, balance: balance};
            }
        }
    }

    #getAssetOrderBook(asset){
        const orderBook = this.#dataProvider.getOrderBook(asset);

        return ConverterFactory.getConverter(orderBook).toRestObject(orderBook);
    }

    #getTransactions(asset, type = null){
        const allTransactions = type == "all";
        const transactions = allTransactions ? this.#dataProvider.getAllTransactions(asset) : this.#dataProvider.getActiveTransactions(asset);
        const transactionConverter = new TransactionConverter();

        return transactions.map(t => transactionConverter.toRestObject(t));
    }

    #getAllTransactions(type = null){
        const allTransactions = type == "all";
        const transactions = allTransactions ? this.#dataProvider.getAllTransactions() : this.#dataProvider.getActiveTransactions();
        const transactionConverter = new TransactionConverter();

        return transactions.map(t => transactionConverter.toRestObject(t));
    }

    #getAllOrderBooks(){
        const orderBooks = [];

        this.#dataProvider.getAvailableAssets().forEach(asset => {
            const orderBook = this.#dataProvider.getOrderBook(asset);
            orderBooks.push(ConverterFactory.getConverter(orderBook).toRestObject(orderBook));
        });

        return orderBooks;
    }

    #getWorker(){
        const workerConverter = new WorkerConverter();
        let result = [];
        this.#dataProvider.getWorkers().forEach(w => {
            result.push(workerConverter.toRestObject(w));
        });

        return result;
    }

    #getWorkerAttribute(name, attributeName){
        let result = null;
        this.#dataProvider.getWorkers().forEach(w => {
            if(w.getName() === name){
                result =  w.getAttribute(attributeName);
            }
        });

        return result;
    }
}