const express = require('express');
const interval_tree = require('node-interval-tree');
const constants = require('../constants.json');
const morgan = require('morgan');
let port = constants.rm_port
let workerNodes = {}, timeline = {}
let addresslist = []
let rr_ptr = 0;

let kafka = require('kafka-node'),
    Producer = kafka.Producer,
    client = new kafka.KafkaClient({
        kafkaHost: constants.network.external.kafka_host,
        autoConnect: true
    }),
    producer = new Producer(client),
    Consumer = kafka.Consumer,
    consumer = new Consumer(client,
        [
            { topic: 'heartbeat' }, // receives heartbeat messages from workers, also acts as worker join message
            { topic: "request" } // receives deployment details from RM
        ],
        [
            { autoCommit: true }
        ])


const app = express()
app.use(morgan('combined', {
    skip: function (req, res) { return res.statusCode < 400 }
}))
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

/**
 * speculative resource allocation plan for workflows
 */
let spec_plan = {}

function rr() {
    
    return addresslist[(rr_ptr++) % addresslist.length]
}

function random() {
    
    return addresslist[Math.floor(Math.random() * addresslist.length)]
}

function workflow(plan, functionHash) {
    let time = Date.now()
    let tolerance = 2 * 1000
    /**
     * adding a tolerance of 2s
     */
    if (spec_plan[functionHash]) {
        // console.log(spec_plan[functionHash]);
        let timeline = spec_plan[functionHash].timeline
        for (const specs of timeline) {
            // console.log(time - specs, time, specs);
            if (time > specs - tolerance && time < specs + tolerance) {
                console.log("found");
                return spec_plan[functionHash].address
            } else if (time < specs - 2 || time > specs + 2) {
                delete specs
            }
        }
    } 
    
    // console.log(workerNodes, addresslist, addresslist.length, Math.floor(Math.random() * addresslist.length));
    let address = addresslist[Math.floor(Math.random() * addresslist.length)]
    
    if (plan) {
        for (const node in plan) {
            if (plan.hasOwnProperty(node)) {
                if (!spec_plan[node])
                    spec_plan[node] = {
                        address,
                        timeline: []
                    }
                spec_plan[node].timeline.push(plan[node] + time)
            }
        }
        console.log("updated spec_plan", spec_plan);
    }
    // console.log("address", address, spec_plan);
    
    return address;
}

function getAddress(plan, functionHash) {
    let address = ""
    if (constants.resource_scheduler.select === constants.resource_scheduler.options.rr)
        address = rr()
    else if (constants.resource_scheduler.select === constants.resource_scheduler.options.random)
        address = random()
    else if (constants.resource_scheduler.select === constants.resource_scheduler.options.workflow)
        address = workflow(plan, functionHash)
    
    console.log(functionHash, address);
    return address
}

app.post('/resource/plan', (req, res) => {
    let addresslist = Object.keys(workerNodes)
    // console.log(workerNodes, addresslist, addresslist.length, Math.floor(Math.random() * addresslist.length));
    let address = addresslist[Math.floor(Math.random() * addresslist.length)]
    let plan = req.body.plan
    const time = Date.now()
    console.log(plan)
    if (plan) {
        for (const node in plan) {
            if (plan.hasOwnProperty(node)) {
                let functionHash = plan[node].id
                if (!spec_plan[functionHash])
                    spec_plan[functionHash] = {
                        address,
                        timeline: []
                    }
                spec_plan[functionHash].timeline.push(plan[node].invokeTime + time)
            }
        }
        console.log("updated spec_plan", spec_plan);
    }
    res.sendStatus(200)
})

consumer.on('message', function (message) {
    let topic = message.topic
    message = message.value
    // console.log(message);
    if (topic === "heartbeat") {
        message = JSON.parse(message)
        
        if ((Date.now() - message.timestamp < 1000) && !workerNodes[message.address]) {
            workerNodes[message.address] = message
            addresslist = Object.keys(workerNodes)
            console.log("New worker discovered. Worker List: ")
            console.log(workerNodes);
            // console.log(Object.keys(workerNodes));
        }
    } else if (topic === "request") {
        message = JSON.parse(message)
        console.log("request", message);
        
        let payload = [{
            topic: "RESPONSE_RM_2_DM_DUMMY",
            messages: JSON.stringify({
                    "resource_id": message.resource_id,
                    "timestamp": Date.now(),
                    "nodes": [
                        { node_id: getAddress(message.plan, message.functionHash), port: null }]
            }),
            partition: 0
        }]
        producer.send(payload, () => {
            console.log(`Replied`)
        })
    }
})

app.listen(port, () => console.log(`Server listening on port ${port}!`))