/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2015 by Amaury Pouly
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include <cstdio>
#include <thread>
#include <chrono>
#include <cstring>
#include <iostream>
#include "hwstub.hpp"
#include "hwstub_usb.hpp"
#include "hwstub_uri.hpp"
#include <signal.h>
#include <getopt.h>

/* capture CTRL+C */
volatile sig_atomic_t g_exit_loop = 0;

void do_signal(int sig)
{
    g_exit_loop = 1;
}

std::shared_ptr<hwstub::context> g_ctx;

void print_error(hwstub::error err, bool nl = true)
{
    switch(err)
    {
        case hwstub::error::SUCCESS: printf("success"); break;
        case hwstub::error::ERROR: printf("error"); break;
        default: printf("unknown(%d)", (int)err); break;
    }
    if(nl)
        printf("\n");
}

const char *target_string(uint32_t id)
{
    switch(id)
    {
        case HWSTUB_TARGET_UNK: return "unknown";
        case HWSTUB_TARGET_STMP: return "stmp";
        case HWSTUB_TARGET_RK27: return "rk27";
        case HWSTUB_TARGET_PP: return "pp";
        case HWSTUB_TARGET_ATJ: return "atj";
        case HWSTUB_TARGET_JZ: return "jz";
        default: return "unknown";
    }
}

void print_dev_details(std::shared_ptr<hwstub::device> dev)
{
    std::shared_ptr<hwstub::handle> h;
    hwstub::error err = dev->open(h);
    if(err != hwstub::error::SUCCESS)
    {
        printf("  [cannot open dev: %s]\n", error_string(err).c_str());
        return;
    }
    /* version */
    struct hwstub_version_desc_t ver_desc;
    err = h->get_version_desc(ver_desc);
    if(err != hwstub::error::SUCCESS)
    {
        printf("  [cannot get version descriptor: %s]\n", error_string(err).c_str());
        return;
    }
    printf("  [version %d.%d.%d]\n", ver_desc.bMajor, ver_desc.bMinor, ver_desc.bRevision);
    /* target */
    struct hwstub_target_desc_t target_desc;
    err = h->get_target_desc(target_desc);
    if(err != hwstub::error::SUCCESS)
    {
        printf("  [cannot get target descriptor: %s]\n", error_string(err).c_str());
        return;
    }
    std::string name(target_desc.bName, sizeof(target_desc.bName));
    printf("  [target %s: %s]\n", target_string(target_desc.dID), name.c_str());
    /* layout */
    struct hwstub_layout_desc_t layout_desc;
    err = h->get_layout_desc(layout_desc);
    if(err != hwstub::error::SUCCESS)
    {
        printf("  [cannot get layout descriptor: %s]\n", error_string(err).c_str());
        return;
    }
    printf("  [code layout %#x bytes @ %#x]\n", layout_desc.dCodeSize, layout_desc.dCodeStart);
    printf("  [stack layout %#x bytes @ %#x]\n", layout_desc.dStackSize, layout_desc.dStackStart);
    printf("  [buffer layout %#x bytes @ %#x]\n", layout_desc.dBufferSize, layout_desc.dBufferStart);
}

void print_device(std::shared_ptr<hwstub::device> dev, bool arrived = true)
{
    hwstub::usb::device *udev = dynamic_cast< hwstub::usb::device* >(dev.get());
    if(arrived)
        printf("--> ");
    else
        printf("<-- ");
    if(udev)
    {
        libusb_device *uudev = udev->native_device();
        struct libusb_device_descriptor dev_desc;
        libusb_get_device_descriptor(uudev, &dev_desc);
        printf("USB device @ %d.%u: ID %04x:%04x\n", udev->get_bus_number(),
               udev->get_address(), dev_desc.idVendor, dev_desc.idProduct);
    }
    else
        printf("Unknown device\n");
    if(arrived)
        print_dev_details(dev);
}

void dev_changed(std::shared_ptr<hwstub::context> ctx, bool arrived, std::shared_ptr<hwstub::device> dev)
{
    print_device(dev, arrived);
}

void print_list()
{
    std::vector<std::shared_ptr<hwstub::device>> list;
    hwstub::error ret = g_ctx->get_device_list(list);
    if(ret != hwstub::error::SUCCESS)
    {
        printf("Cannot get device list: %s\n", error_string(ret).c_str());
        return;
    }
    for(auto d : list)
        print_device(d);
}

int usage()
{
    printf("usage: hwstub_test [options]\n");
    printf("  --help/-h           Display this help\n");
    printf("  --verbose/-v        Verbose output\n");
    printf("  --context/-c <uri>  Context URI (see below)\n");
    printf("\n");
    hwstub::uri::print_usage(stdout, true, false);
    return 1;
}

int main(int argc, char **argv)
{
    hwstub::uri::uri uri = hwstub::uri::default_uri();
    bool verbose = false;

    while(1)
    {
        static struct option long_options[] =
        {
            {"help", no_argument, 0, 'h'},
            {"verbose", no_argument, 0, 'v'},
            {"context", required_argument, 0, 'c'},
            {0, 0, 0, 0}
        };

        int c = getopt_long(argc, argv, "hvc:", long_options, NULL);
        if(c == -1)
            break;
        switch(c)
        {
            case -1:
                break;
            case 'v':
                verbose = true;
                break;
            case 'h':
                return usage();
            case 'c':
                uri = hwstub::uri::uri(optarg);
                break;
            default:
                abort();
        }
    }

    if(optind != argc)
        return usage();

    /* intercept CTRL+C */
    signal(SIGINT, do_signal);

    std::string error;
    g_ctx = hwstub::uri::create_context(uri, &error);
    if(!g_ctx)
    {
        printf("Cannot create context: %s\n", error.c_str());
        return 1;
    }
    if(verbose)
        g_ctx->set_debug(std::cout);
    print_list();
    g_ctx->register_callback(dev_changed);
    g_ctx->start_polling();
    while(!g_exit_loop)
        std::this_thread::sleep_for(std::chrono::seconds(1));
    printf("Shutting down...\n");
    g_ctx.reset(); /* will cleanup */

    return 0;
}

