//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------

#include <netdb.h>
#include "os.h"
#include "exceptions.h"
#include "protobuf_util.h"
#include "rasutil.h"
#include "rasctl_commands.h"
#include "rascsi_interface.pb.h"
#include <sstream>
#include <iostream>
#include <list>

// Separator for the INQUIRY name components
#define COMPONENT_SEPARATOR ':'

using namespace std;
using namespace rascsi_interface;

RasctlCommands::RasctlCommands(PbCommand& command, const string& hostname, int port)
{
	this->command = command;
	this->hostname = hostname;
	this->port = port;
}

void RasctlCommands::SendCommand()
{
	// Send command
	int fd = -1;
	try {
    	struct hostent *host = gethostbyname(hostname.c_str());
    	if (!host) {
    		throw io_exception("Can't resolve hostname '" + hostname + "'");
    	}

    	fd = socket(AF_INET, SOCK_STREAM, 0);
    	if (fd < 0) {
    		throw io_exception("Can't create socket");
    	}

    	struct sockaddr_in server;
    	memset(&server, 0, sizeof(server));
    	server.sin_family = AF_INET;
    	server.sin_port = htons(port);
    	server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    	memcpy(&server.sin_addr.s_addr, host->h_addr, host->h_length);

    	if (connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
    		ostringstream error;
    		error << "Can't connect to rascsi process on host '" << hostname << "', port " << port;
    		throw io_exception(error.str());
    	}

        SerializeMessage(fd, command);
    }
    catch(const io_exception& e) {
    	cerr << "Error: " << e.getmsg() << endl;

        if (fd >= 0) {
        	close(fd);
        }

        exit(fd < 0 ? ENOTCONN : EXIT_FAILURE);
    }

    // Receive result
    try {
        DeserializeMessage(fd, result);

    	if (!result.status()) {
    		throw io_exception(result.msg());
    	}
    }
    catch(const io_exception& e) {
    	close(fd);

    	cerr << "Error: " << e.getmsg() << endl;

    	exit(EXIT_FAILURE);
    }

    close(fd);

	if (!result.msg().empty()) {
		cout << result.msg() << endl;
	}
}

void RasctlCommands::CommandDevicesInfo()
{
	SendCommand();

	rasctl_display.DisplayDevices(result.devices_info());
}

void RasctlCommands::CommandLogLevel(const string& log_level)
{
	AddParam(command, "level", log_level);

	SendCommand();
}

void RasctlCommands::CommandReserveIds(const string& reserved_ids)
{
	AddParam(command, "ids", reserved_ids);

    SendCommand();
}

void RasctlCommands::CommandCreateImage(const string& image_params)
{
	size_t separatorPos = image_params.find(COMPONENT_SEPARATOR);
	if (separatorPos != string::npos) {
		AddParam(command, "file", image_params.substr(0, separatorPos));
		AddParam(command, "size", image_params.substr(separatorPos + 1));
	}
	else {
		cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl;
		exit(EXIT_FAILURE);
	}

	AddParam(command, "read_only", "false");

    SendCommand();
}

void RasctlCommands::CommandDeleteImage(const string& filename)
{
	AddParam(command, "file", filename);

    SendCommand();
}

void RasctlCommands::CommandRenameImage(const string& image_params)
{
	size_t separatorPos = image_params.find(COMPONENT_SEPARATOR);
	if (separatorPos != string::npos) {
		AddParam(command, "from", image_params.substr(0, separatorPos));
		AddParam(command, "to", image_params.substr(separatorPos + 1));
	}
	else {
		cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
		exit(EXIT_FAILURE);
	}

    SendCommand();
}

void RasctlCommands::CommandCopyImage(const string& image_params)
{
	size_t separatorPos = image_params.find(COMPONENT_SEPARATOR);
	if (separatorPos != string::npos) {
		AddParam(command, "from", image_params.substr(0, separatorPos));
		AddParam(command, "to", image_params.substr(separatorPos + 1));
	}
	else {
		cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
		exit(EXIT_FAILURE);
	}

    SendCommand();
}

void RasctlCommands::CommandDefaultImageFolder(const string& folder)
{
	AddParam(command, "folder", folder);

	SendCommand();
}

void RasctlCommands::CommandDeviceInfo()
{
	SendCommand();

	for (const auto& device : result.devices_info().devices()) {
		rasctl_display.DisplayDeviceInfo(device);
	}
}

void RasctlCommands::CommandDeviceTypesInfo()
{
	SendCommand();

	rasctl_display.DisplayDeviceTypesInfo(result.device_types_info());
}

void RasctlCommands::CommandVersionInfo()
{
	SendCommand();

	rasctl_display.DisplayVersionInfo(result.version_info());
}

void RasctlCommands::CommandServerInfo()
{
	SendCommand();

	PbServerInfo server_info = result.server_info();

	rasctl_display.DisplayVersionInfo(server_info.version_info());
	rasctl_display.DisplayLogLevelInfo(server_info.log_level_info());
	rasctl_display.DisplayImageFiles(server_info.image_files_info());
	rasctl_display.DisplayMappingInfo(server_info.mapping_info());
	rasctl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info());
	rasctl_display.DisplayDeviceTypesInfo(server_info.device_types_info());
	rasctl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info());

	if (server_info.devices_info().devices_size()) {
		list<PbDevice> sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
		sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id(); });

		cout << "Attached devices:" << endl;

		for (const auto& device : sorted_devices) {
			rasctl_display.DisplayDeviceInfo(device);
		}
	}
}

void RasctlCommands::CommandDefaultImageFilesInfo()
{
	SendCommand();

	rasctl_display.DisplayImageFiles(result.image_files_info());
}

void RasctlCommands::CommandImageFileInfo(const string& filename)
{
	AddParam(command, "file", filename);

	SendCommand();

	rasctl_display.DisplayImageFile(result.image_file_info());
}

void RasctlCommands::CommandNetworkInterfacesInfo()
{
	SendCommand();

	rasctl_display.DisplayNetworkInterfaces(result.network_interfaces_info());
}

void RasctlCommands::CommandLogLevelInfo()
{
	SendCommand();

	rasctl_display.DisplayLogLevelInfo(result.log_level_info());
}

void RasctlCommands::CommandReservedIdsInfo()
{
	SendCommand();

	rasctl_display.DisplayReservedIdsInfo(result.reserved_ids_info());
}

void RasctlCommands::CommandMappingInfo()
{
	SendCommand();

	rasctl_display.DisplayMappingInfo(result.mapping_info());
}
