// Fusion Research Driver - Common Module - HTTP Support
//
// Copyright 2021 Fusion Research, Inc.
// by Alexa Jade Fanti
//
// Comments:
//   This file provides supporting functions and classes for HTTP request/response operations.
//
// Depends: 
//   fusion_stringutils.js
//   fusion_debugging.js
//   fusion_sys_config.js
//   fusion_serverdiscovery.js
//
//System.Print("Fusion Driver - Common Module - HTTP Support loaded...\r\n");


//*************** START  XML Utilities ***************
// All XML returned by our server is written in a format with a line at the top that causes 
// problems for the E4X XML parser provided by RTI. That line looks something like: 
//		<?xml ..... ?> 
//
// To make this not a problem, we have to remove this from the XML before parsing.
// This function does that.
//
// Parameters
//		dirtyXml - raw data we want to parse, but fear is "dirty".
//
// Return - The data "cleaned" of the offending "dirt".
//
function SanitizeXML(dirtyXml)
{
	if (dirtyXml === undefined) {
		Debug_SysConfig("SanitizeXML parameter 'dirtyXml' is undefined, returning empty string!", g_DEBUG_LEVEL_CRITICAL);
		return "";
	}
	if (dirtyXml == "") return "";
	
	var cleanXml = dirtyXml;
	
	var pos = dirtyXml.indexOf("?>");
	if (pos < 0) {
		cleanXml = dirtyXml;
	} else {
		cleanXml = dirtyXml.substr(pos +2);
	}
/*
DebugHTTP("", g_DEBUG_LEVEL_CRITICAL);
DebugHTTP("", g_DEBUG_LEVEL_ERROR);
DebugHTTP("", g_DEBUG_LEVEL_WARN);
DebugHTTP("", g_DEBUG_LEVEL_INFO);
DebugHTTP("", g_DEBUG_LEVEL_TRACE);
*/
	return cleanXml;
}
//*************** STOP   XML Utilities ***************


/******************************************************************
 * START HTTP Request Object
 ******************************************************************/
//
// This contains all data for (and is used to perform) a non-blocking request.
// When created it takes the URL and parses that into the constituent parts.
// Upon Fetch() it will call it's callback when completed!
//
function HttpRequest(url)
{
	this.Initialize();

	if (url === undefined) {
		Debug_SysConfig("HttpRequest created with 'url' undefined!", g_DEBUG_LEVEL_CRITICAL);
	} else {
		this.SetUrl(url);
	}
}

HttpRequest.prototype.Initialize = function()
{
	this.ip_address = "";
	this.http_port  = 0;
	this.uri        = "/";
    this.tag        = null;

    this.callback   = null; // Call back with the request and response once received
}

// This parses the URL into it's constituent parts...
HttpRequest.prototype.SetUrl = function(url)
{
    this.Initialize();
    
    // Remove the leading "http://"
	url = url.replace("http://", "");

	//we need to separate the host (hostname.com) from the port (:8080, if there) and the target (/images/cnnhd)
	var urlEnd = url.indexOf('/');
	if (urlEnd < 0) urlEnd = 0;

	var addr = url.substr(0, urlEnd);
	addr = addr.split(":");

	if ((addr[0] !== undefined) && (addr[0] !== "")) this.ip_address = addr[0]; 
	if (addr[1] !== undefined) this.http_port = parseInt(addr[1], 10);
	if (this.uri !== "") this.SetUri(url.substr(urlEnd));

	// If the address and port are unknown, let's use what we should be able to use!
	if (this.ip_address == "") this.ip_address = OperationalServerIP();
	if (this.http_port <= 0) this.http_port = 9720;
}

//*************** START Getters ***************
HttpRequest.prototype.GetUrl = function()
{
	return "http://" + this.ip_address + ':' + this.http_port + this.uri;
}

HttpRequest.prototype.GetIPAddress = function()
{
    return this.ip_address;
}

HttpRequest.prototype.GetHttpPort = function()
{
    return this.http_port;
}

HttpRequest.prototype.GetUri = function()
{
    return this.uri;
}

//*************** START Setters ***************
HttpRequest.prototype.SetUri = function(uri)
{
	this.uri = uri;
    if (this.uri.indexOf("/") !== 0) this.uri = "/" + this.uri;    
}

HttpRequest.prototype.GetTag = function()
{
    return this.tag;
}

HttpRequest.prototype.SetTag = function(tag)
{
	this.tag = tag;
}

HttpRequest.prototype.SetCallback = function(callback)
{
	this.callback = callback;
}

//*************** START Other ***************
HttpRequest.prototype.PerformCallback = function(httpResponse)
{
    if ((this.callback !== undefined) && (this.callback != null) && (httpResponse !== undefined)) try {
        this.callback(this, httpResponse);
    } catch(err) {
		Debug_SysConfig("HttpRequest failed to perform callback with error: " + err, g_DEBUG_LEVEL_ERROR);
    }
}

HttpRequest.prototype.GETCommand = function()
{
	return "GET " + this.uri + " HTTP/1.1\r\n\r\n";
}

HttpRequest.prototype.Fetch = function()
{
    HTTP_SYS_Fetch(this);
}
/******************************************************************
 * END   HTTP Request Object
 ******************************************************************/


/******************************************************************
 * START HTTP Response Object
 ******************************************************************/


//*************** START  HTTP Response Object ***************
function HttpResponse(rawResponse)
{
	this.Initialize();
	if (rawResponse !== undefined) this.ParseRawResponse(rawResponse);
}

HttpResponse.prototype.Initialize = function()
{
    this.protocol      = "";
    this.response_code = 0;
    this.response_text = ""; 
	this.headers       = Array();
	this.body          = "";
}

HttpResponse.prototype.ParseRawResponse = function(data)
{
    this.Initialize();

	var lines = data.split("\r\n");
    var numLines = lines.length;

    // Read the first line of the response (protocol response_code response_text)
    if (numLines > 0) {
        var status = lines[0].split(' ');
        if (status.length > 0) this.protocol = status[1].trim();
        if (status.length > 1) this.response_code = parseInt(status[1].trim(), 10);
        if (status.length > 2) this.response_text = status[2].trim();
    }

    // Read the headers till we get to a blank line and note where that is (it's the start of the body)
    var startBody = 0;
    for (var i = 1; i < numLines; i++) {
        if (lines[i] === "") {
            startBody = i;
        } else {
            if (startBody == 0) this.headers.push(lines[i].trim());
        }
    }
    
    // Read the body
    for (var i = startBody; i < numLines; i++) this.body = this.body + lines[i].trim();
}

//*************** START Getters ***************
HttpResponse.prototype.GetResponseCode = function()
{
    return this.response_code;
}

HttpResponse.prototype.GetResponseText = function()
{
    return this.response_text;
}

HttpResponse.prototype.GetHeaders = function()
{
    return this.headers;
}

HttpResponse.prototype.GetBody = function()
{
    return this.body;
}

HttpResponse.prototype.GetBody_AsXML = function()
{
    try {
        return new XML(SanitizeXML(this.body));
	} catch(err) {
		Debug_SysConfig("HttpResponse failed to GetBody_AsXML with error: " + err, g_DEBUG_LEVEL_ERROR);
	}
}
/******************************************************************
 * END   HTTP Response Object
 ******************************************************************/


/******************************************************************
 * START HTTP Global Variables and Functions
 ******************************************************************/

// This is a global array of HttpRequest objects we create when they want to make a request!
var g_HTTP_SYS_REQUEST_QUEUE = [];

// This is the global copy of the "current" HttpRequest object. We can have only one at a time!
var g_HTTP_SYS_CURRENT_REQUEST = null;


// Set up the System Comm Object
var g_HTTP_SYS_OBJECT = new HTTP(HTTP_SYS_OnCommRx);

g_HTTP_SYS_OBJECT.OnConnectFunc       = HTTP_SYS_OnConnect;
g_HTTP_SYS_OBJECT.OnConnectFailedFunc = HTTP_SYS_OnConnectFailed;
g_HTTP_SYS_OBJECT.OnDisconnectFunc    = HTTP_SYS_OnDisconnect;

// ----------------------------------------------------------
// Define the Comm Object's Connection Functions 
// ----------------------------------------------------------

// This is called by the Comm object when it's established a connection.
// If there's anything in the global send queue, we pop it off and send it.
function HTTP_SYS_OnConnect()
{
	DebugHTTP("HTTP_SYS OnConnect...", g_DEBUG_LEVEL_INFO);
	if (g_HTTP_SYS_REQUEST_QUEUE.length > 0) {
        g_HTTP_SYS_CURRENT_REQUEST = g_HTTP_SYS_REQUEST_QUEUE.shift();
        var cmd = g_HTTP_SYS_CURRENT_REQUEST.GETCommand();
		DebugHTTP("HTTP_SYS Write: " + cmd, g_DEBUG_LEVEL_TRACE);
        g_HTTP_SYS_OBJECT.Write(cmd);
	}
}

// This is called by the Comm object when it's lost the connection.
// If there's anything more in the global send queue, we re-open the connection.
function HTTP_SYS_OnDisconnect()
{
	DebugHTTP("HTTP_SYS OnDisconnect", g_DEBUG_LEVEL_INFO);
	g_HTTP_SYS_OBJECT.Close();
    
    // If we have more to ask... re-connect!
	if (g_HTTP_SYS_REQUEST_QUEUE.length > 0) {
        var httpRequest = g_HTTP_SYS_REQUEST_QUEUE[0];
        g_HTTP_SYS_OBJECT.Open(httpRequest.GetIPAddress(), httpRequest.GetHttpPort());
        g_HTTP_SYS_OBJECT.AddRxHTTPFraming();
    }
}

// This is called by the Comm object when the connection fails.
function HTTP_SYS_OnConnectFailed()
{
	DebugHTTP("HTTP_SYS OnConnectFailed", g_DEBUG_LEVEL_INFO);
	g_HTTP_SYS_OBJECT.Close();
}

// Define the Comm Object's OnDataReceived Function.
// This is called by the Comm object when it receives data.
// We look for the global (current) request object and habd the data off.
function HTTP_SYS_OnCommRx(data)
{
	DebugHTTP("HTTP_SYS OnCommRx", g_DEBUG_LEVEL_INFO);
	DebugHTTP("\r\n" + data + "\r\n------------------------------", g_DEBUG_LEVEL_TRACE);
	g_HTTP_SYS_OBJECT.Disconnect();

    if (g_HTTP_SYS_CURRENT_REQUEST !== undefined) {
    	g_HTTP_SYS_CURRENT_REQUEST.PerformCallback(new HttpResponse(data));
    }
}

// ----------------------------------------------------------
// Define the function the Request Object needs to Fetch
// ----------------------------------------------------------

// This is called by the HTTP request to add itself to our global queue
// and connect the socket if it's not already connected!
function HTTP_SYS_Fetch(httpRequest)
{
	if (httpRequest !== undefined) {
        g_HTTP_SYS_REQUEST_QUEUE.push(httpRequest);
        if (g_HTTP_SYS_OBJECT.ConnectState == 0) {
            g_HTTP_SYS_OBJECT.Open(httpRequest.GetIPAddress(), httpRequest.GetHttpPort());
            g_HTTP_SYS_OBJECT.AddRxHTTPFraming();
        }
    }
}


// ----------------------------------------------------------
// Define a few public "helper" functions we can use.
// ----------------------------------------------------------

// We provide this as an example of a callback that can ge assigned to the HttpRequest object
// We don't expect anyone to actually use this in real life!
function HTTP_SYS_Callback_DEBUG(httpRequest, httpResponse) 
{
	DebugHTTP("HTTP_SYS_Callback_DEBUG Callback Tag = " + httpRequest.GetTag(), g_DEBUG_LEVEL_TRACE);
    DebugHTTP("HTTP_SYS_Callback_DEBUG ASK: " + httpRequest.GetUrl(), g_DEBUG_LEVEL_TRACE);
    DebugHTTP("HTTP_SYS_Callback_DEBUG ANSWER: " + httpResponse.GetResponseCode() + "\r\n" + httpResponse.GetResponseText() + httpResponse.GetBody(), g_DEBUG_LEVEL_TRACE);
}

// This is just an encapsulation to make it easier to fetch a HTTP page
// and wait for the response (blocking). This just returns the raw data!
function HTTP_SYS_GetURL_Blocking(url)
{
    return System.GetURL(url);
}

// This is just an encapsulation to make it easier to fetch a HTTP page
// and wait for the response (blocking). This returns thesanitized XML!
function HTTP_SYS_GetURL_AsXML_Blocking(url)
{
	try {
		var clean_xml = SanitizeXML(System.GetURL(url));
		if (clean_xml != '') return new XML(clean_xml)
	} catch (err) {
		DebugHTTP("HTTP_SYS_GetURL_AsXML_Blocking cannot fetch URL = " + url, g_DEBUG_LEVEL_ERROR);
	}
	return;
}
/******************************************************************
 * END   HTTP Global Variables and Functions
 ******************************************************************/


// Example usage!

/*
var httpReq = new HttpRequest("http://10.1.14.55:9720/oms/api/1/control/lms?action=get_version");
httpReq.SetTag('1');
httpReq.SetCallback(HTTP_SYS_Callback_DEBUG);
httpReq.Fetch();
*/

/*
var httpReq = new HttpRequest("http://192.168.1.10:9720/oms/api/1/manage/playfimx?action=status&Number=2");
httpReq.SetTag('2');
httpReq.SetCallback(function(httpRequest, httpResponse) {
    System.Print("Inline Callback " + httpRequest.GetTag());
    System.Print("ASK: " + httpRequest.GetUrl());
    System.Print("ANSWER: " + httpResponse.GetResponseCode() + " " + httpResponse.GetResponseText() + httpResponse.GetBody());
    
    ParseXML_Status(httpResponse.GetBody_AsXML());
});
httpReq.Fetch(httpReq);
*/

/*
var httpReq = new HttpRequest("http://192.168.1.10:9720/oms/api/1/manage/playfimx?action=status&Number=3");
httpReq.SetTag('3');
httpReq.SetCallback(Callback);
httpReq.Fetch(httpReq);

function Callback(httpRequest, httpResponse) 
{
    System.Print("Callback " + httpRequest.GetTag());
    System.Print("ASK: " + httpRequest.GetUrl());
    System.Print("ANSWER: " + httpResponse.GetResponseCode() + " " + httpResponse.GetResponseText() + httpResponse.GetBody());
}
*/

