
#include "pch.h"

#include <iostream>

#include <boost/bind.hpp>
#include <boost/program_options.hpp>
#include <boost/tokenizer.hpp>

#include "application.h"
#include "foreign_menu.h"
#include "foreign_menu_prot.h"
#include "usb_server.h"

using namespace boost::program_options;

#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif

#define USB_CTRL_OPTION_CLIENT      "host-name"

#define USB_CTRL_OPTION_PIPE_NAME   "connection"
#define USB_CTRL_OPTION_FILTER      "filter"
#define USB_CTRL_OPTION_AUTO_SHARE  "auto-share"
#define USB_CTRL_OPTION_LANAGUAGE   "lanaguage"
#define USB_CTRL_OPTION_HELP        "help"

#define USB_CTRL_SHORT_PIPE_NAME    ",c"
#define USB_CTRL_SHORT_FILTER       ",f"
#define USB_CTRL_SHORT_AUTO_SHARE   ",a"
#define USB_CTRL_SHORT_LANAGUAGE    ",l"
#define USB_CTRL_SHORT_HELP         ",h"

#define DEFAULT_LANGUAGE "USB Devices,No USB devices,USB Redirector is not installed"

#define NO_EXIT_CODE (-1)

#ifdef _MSC_VER
#pragma warning(disable:4355) // 'this' : used in base member initializer list.
#endif // _MSC_VER

Application::Application() :
    _usb_server(new UsbServer(boost::bind(&Application::on_usb_notification, this))),
    _foreign_menu(new ForeignMenu(*this)),
    _exit_code(NO_EXIT_CODE)
{

}

Application::~Application()
{
    std::deque<Application::Message*>::iterator iter;

    for (iter = _queue.begin(); iter != _queue.end(); ++iter)
    {
        delete *iter;
    }
}

bool Application::parse_command_line(std::vector<std::string>& args)
{
    typedef boost::char_separator<char> separator;
    typedef boost::tokenizer<separator, std::string::const_iterator, std::string> tokenizer;

    std::string text;

    options_description visible("usbrdrctrl host-name [port]");
    visible.add_options()
        (USB_CTRL_OPTION_PIPE_NAME USB_CTRL_SHORT_PIPE_NAME, value<std::string>(&_pipe_name)->default_value(""), "")
        (USB_CTRL_OPTION_FILTER USB_CTRL_SHORT_FILTER, value<std::string>(&_filter)->default_value(""), "")
        (USB_CTRL_OPTION_AUTO_SHARE USB_CTRL_SHORT_AUTO_SHARE, "Enable USB auto sharing.")
        (USB_CTRL_OPTION_LANAGUAGE USB_CTRL_SHORT_LANAGUAGE, value<std::string>(&text)->default_value(DEFAULT_LANGUAGE), "")
        (USB_CTRL_OPTION_HELP USB_CTRL_SHORT_HELP, "Show usage.")
        ;

    // The following options are not included in the usage string.
    options_description hidden;
    hidden.add_options()
        (USB_CTRL_OPTION_CLIENT, value< std::vector<std::string> >(), "")
        ;

    options_description opts;
    opts.add(visible);
    opts.add(hidden);

    positional_options_description popts;
    popts.add(USB_CTRL_OPTION_CLIENT, 2);

    variables_map vars;
    store(command_line_parser(args).options(opts).positional(popts).run(), vars);
    notify(vars);

    if (vars.count(USB_CTRL_OPTION_HELP)) {
        std::cout << visible << std::endl;
        return false;
    }

    if (vars.count(USB_CTRL_OPTION_AUTO_SHARE)) {
        _auto_share = true;
    } else {
        _auto_share = false;
    }

    separator sep(",");
    tokenizer texts(text, sep);
    tokenizer::iterator iter = texts.begin();

    _menu_title_text = *iter++;
    _no_devices_text = *iter++;
    _not_installed_text = *iter;

    if (vars.count(USB_CTRL_OPTION_CLIENT))
    {
        const std::vector<std::string>& params =
            vars[USB_CTRL_OPTION_CLIENT].as< std::vector<std::string> >();

        _host_name = params[0];

        if (params.size() > 1) {
            _host_port = atoi(params[1].c_str());
        } else {
            _host_port = 32023;
        }
    }

    if (_host_name.empty()) {
        throw boost::program_options::error("host name is missing");
    }

    return true;
}

int Application::execute()
{
    if (!_foreign_menu->open_connection(_pipe_name))
    {
        return EXIT_FAILURE;
    }

    _usb_server->start(_host_name, _host_port);
    if (!_filter.empty())
    {
        _usb_server->set_filter(_filter);
    }

    _foreign_menu->start();
    _foreign_menu->init(_menu_title_text);

    LOG_INFO("USB Controller is now running...");
    int ret = process_messages();

    _usb_server->stop();
    _foreign_menu->stop();

    LOG_INFO("USB Controller terminated (return code = " << ret << ")");

    return ret;
}

void Application::on_usb_notification()
{
    push_message(new Application::Message(
        boost::bind(&Application::handle_usb_notification, this)));
}

void Application::on_menu_item_click(uint32_t id)
{
    push_message(new Application::Message(
        boost::bind(&Application::handle_menu_item_click, this, id)));
}

void Application::on_menu_activate()
{
    push_message(new Application::Message(
        boost::bind(&Application::handle_menu_activate, this)));
}

void Application::on_menu_deactivate()
{
    push_message(new Application::Message(
        boost::bind(&Application::handle_menu_deactivate, this)));
}

void Application::on_connection_close()
{
    push_message(new Application::Message(
        boost::bind(&Application::handle_connection_close, this)));
}

int Application::process_messages()
{
    boost::mutex mutex;
    boost::mutex::scoped_lock lock(mutex);

    while (_exit_code == NO_EXIT_CODE)
    {
        if (_queue.empty())
        {
            _drain_cond.wait(lock);
        }

        Message *msg = pop_message();

        if (msg != NULL)
        {
            (*msg)();
            delete msg;
        }
    }

    return _exit_code;
}

void Application::push_message(Application::Message *msg)
{
    boost::mutex::scoped_lock lock(_protect_queue);

    _queue.push_front(msg);

    _drain_cond.notify_one();
}

Application::Message* Application::pop_message()
{
    boost::mutex::scoped_lock lock(_protect_queue);

    Application::Message* msg = _queue.back();

    _queue.pop_back();

    return msg;
}

void Application::handle_usb_notification()
{
    UsbServer::devices_desc devices;

    _foreign_menu->remove_items();

    if (_usb_server->get_devices_desc(devices))
    {
        UsbServer::devices_desc::const_iterator iter = devices.begin();

        // Limit the number of items which are added to the menu.
        size_t devs = min(devices.size(), 20);
    
        for (size_t i = 0; i < devs; ++i)
        {
            unsigned long hdev = iter->first;
            uint32_t flags = 0;

            if (_usb_server->is_shared_device(hdev) == true) {
                flags |= FOREIGN_MENU_ITEM_TYPE_CHECKED;
            }

            if (_usb_server->is_share_allowed(hdev) == false) {
                flags |= FOREIGN_MENU_ITEM_TYPE_DIM;
            }

            _foreign_menu->add_item(hdev, iter->second, flags);

            ++iter;
        }
    }
    else
    {
        const char *text;

        if (_usb_server->is_running()) {
            text = _no_devices_text.c_str();
        } else {
            text = _not_installed_text.c_str();
        }

        _foreign_menu->add_item(0, text, FOREIGN_MENU_ITEM_TYPE_DIM);
    }
}

void Application::handle_menu_item_click(uint32_t hdev)
{
    if (_usb_server->is_shared_device(hdev) == true)
    {
        _usb_server->stop_sharing_device(hdev);
    }
    else
    {
        _usb_server->share_device(hdev);
    }
}

void Application::handle_menu_activate()
{
    if (_auto_share == true)
    {
        _usb_server->auto_sharing(true);
    }
}

void Application::handle_menu_deactivate()
{
    if (_auto_share == true)
    {
        _usb_server->auto_sharing(false);
    }
}

void Application::handle_connection_close()
{
    _exit_code = EXIT_SUCCESS;
}
