const WebSocket = require('ws');
const webSocketHost = "0.0.0.0"
const webSocketPort = 9898
const wsServer = new WebSocket.Server({ host: webSocketHost, port: webSocketPort });
const http = require("http");
var target = process.env.PROJECTOR_ADDRESS ||'127.0.0.1:50051';
var target = "proveeprojectorservice:50051"
var knntarget = "proveeknnservice:50052"
//knntarget = '127.0.0.1:50052'
console.log("Target is:" + target);
var PROTO_PATH = __dirname + '/../protos/v3/projector.proto';

var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
const { callErrorFromStatus } = require('@grpc/grpc-js/build/src/call');
var packageDefinition = protoLoader.loadSync(
  PROTO_PATH,
  {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true
  });


var count = 0;
var pointID = 0;
class ProjectionRequest{
  constructor(ws,client,calls,lineAmount){
    this.index = count;
    count += 1;
    this.ws = ws;
    this.client = client;
    this.calls = calls;
    this.lineAmount = lineAmount
  }
}

wsServer.on('connection', function connection(ws) {
  console.log(`Client connected with websocket`);
  var currConnection = new ProjectionRequest(ws,null,null,0);
  ws.on('message', function incoming(message) {
    parseMessage(message, currConnection);
  });

  ws.on('close', (code,reason) =>{
    console.log("Closed");
    if (currConnection.client){
      grpc.closeClient(client);
    }    
  })
});


function getGRPCCLient(_target){
  let pkg = grpc.loadPackageDefinition(packageDefinition);
  let projector = pkg.provee["Projector"];
  client = new projector(_target, grpc.credentials.createInsecure());
  return client
}

//Connects to grpc after first message
function getConnection(ws,client){  
  call = client.getProjectionPoints();
  console.log(`Connecting  to grpc Server at: ${target}`)

  call.on("data", (response) => {
    sendDataStreamToClient(JSON.stringify(response),ws)
  });
  
  call.on("error", (err) => {  
    console.log(
      `Unexpected stream error: code = ${err.code}` +
      `, message = "${err.message}"`
    );
    console.log("Retrying in 5 seconds");
    setTimeout(function(){getConnection(ws)}, 5000);
  });

  call.on("end", () => {console.log("GRPC connection closed.")})

  return call;
}


//Parse the message from browser
function parseMessage(message, connection) {
  const jsonMessage = JSON.parse(message);
  switch (jsonMessage["type"]) {
    case "requestPointStream":
      console.log("Requested stream");
      sendRandomPointStream(connection);
      break;
    case "sendDataRow":
      sendRowToServer(jsonMessage["row"],jsonMessage["lineIndex"],connection);
      break;
    case "setLineCount":
      connection.lineAmount =  parseInt(jsonMessage["amount"]);
      break;
    case "getKNN":
      console.log("setup KNN");
      connection.client = getGRPCCLient(knntarget);
      var allCalls = []
      var grpcConnection = getConnection(connection.ws,connection.client);
      allCalls.push(grpcConnection)

      connection.calls = allCalls;
      console.log("KNN setup" + connection.calls.length);
      break;
    case "setProjectorAmount":
      var amount =  parseInt(jsonMessage["amount"]);
      connection.client = getGRPCCLient(target);
      var allCalls = []
      
      //Create that amount of grpc connections
      for(var i = 0; i< amount; i+=1){
        var grpcConnection = getConnection(connection.ws,connection.client);
        allCalls.push(grpcConnection)
      }

      connection.calls = allCalls;
      break; 
    default:
      console.log("Error! Unknown request:" + jsonMessage["type"]);
  }
}
function generatePoint() {
  const id = pointID++;
  const x = Math.floor(Math.random() * 100);
  const y = Math.floor(Math.random() * 100);

  return { id, x, y };
}

//Get the stream
function sendRandomPointStream(connection) {
  for(var i = 0; i < 100000; i++){
    sendDataStreamToClient(JSON.stringify(generatePoint()),connection.ws);
  }
}

//Send row to each microservice
function sendRowToServer(row,lineIndex,connection) {
  var hdvector = [];
  for (var i =1; i < row.length; i++){
    hdvector.push(parseFloat(row[i]));
  }
  var trainingSetRow = {id: row[0] ,hdvector: hdvector};
  //Linearly distribute rows to the projectors
  for(var i =0; i< connection.calls.length;i++){
    if( lineIndex < (connection.lineAmount / (connection.calls.length-i))){
      connection.calls[i].write(trainingSetRow)
    }
  }
}

//Send client to browser
function sendDataStreamToClient(data, ws) {
  var list = data.toString().trim().split("\n");
  for (var i = 0; i < list.length; i++) {
    try {
      var response = list[i];
      ws.send(response);
    } catch (e) {
      console.log(e);
    }
  }
}