/*
 * Copyright 2019-2020 PixlOne
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <cassert>
#include "Report.h"
#include "Receiver.h"
#include "Error.h"
#include "../../util/thread.h"

using namespace logid::backend::dj;
using namespace logid::backend;

InvalidReceiver::InvalidReceiver(Reason reason) : _reason (reason)
{
}

const char* InvalidReceiver::what() const noexcept
{
    switch(_reason) {
    case NoDJReports:
        return "No DJ reports";
    default:
        return "Invalid receiver";
    }
}

InvalidReceiver::Reason InvalidReceiver::code() const noexcept
{
    return _reason;
}

Receiver::Receiver(std::string path) :
    _raw_device (std::make_shared<raw::RawDevice>(std::move(path))),
    _hidpp10_device (_raw_device, hidpp::DefaultDevice)
{
    if(!supportsDjReports(_raw_device->reportDescriptor()))
        throw InvalidReceiver(InvalidReceiver::NoDJReports);
}

void Receiver::enumerateDj()
{
    _sendDjRequest(hidpp::DefaultDevice, GetPairedDevices,{});
}

Receiver::NotificationFlags Receiver::getHidppNotifications()
{
    auto response = _hidpp10_device.getRegister(EnableHidppNotifications, {},
            hidpp::ReportType::Short);

    NotificationFlags flags{};
    flags.deviceBatteryStatus = response[0] & (1 << 4);
    flags.receiverWirelessNotifications = response[1] & (1 << 0);
    flags.receiverSoftwarePresent = response[1] & (1 << 3);

    return flags;
}

void Receiver::enableHidppNotifications(NotificationFlags flags)
{
    std::vector<uint8_t> request(3);

    if(flags.deviceBatteryStatus)
        request[0] |= (1 << 4);
    if(flags.receiverWirelessNotifications)
        request[1] |= 1;
    if(flags.receiverSoftwarePresent)
        request[1] |= (1 << 3);

    _hidpp10_device.setRegister(EnableHidppNotifications, request,
            hidpp::ReportType::Short);
}

void Receiver::enumerateHidpp()
{
    /* This isn't in the documentation but this is how solaar does it
     * All I know is that when (p0 & 2), devices are enumerated
     */
    _hidpp10_device.setRegister(ConnectionState, {2},
            hidpp::ReportType::Short);
}

///TODO: Investigate usage
uint8_t Receiver::getConnectionState(hidpp::DeviceIndex index)
{
    auto response = _hidpp10_device.getRegister(ConnectionState, {index},
            hidpp::ReportType::Short);

    return response[0];
}

void Receiver::startPairing(uint8_t timeout)
{
    ///TODO: Device number == Device index?
    std::vector<uint8_t> request(3);

    request[0] = 1;
    request[1] = hidpp::DefaultDevice;
    request[2] = timeout;

    _hidpp10_device.setRegister(DevicePairing, request,
            hidpp::ReportType::Short);
}

void Receiver::stopPairing()
{
    ///TODO: Device number == Device index?
    std::vector<uint8_t> request(3);

    request[0] = 2;
    request[1] = hidpp::DefaultDevice;

    _hidpp10_device.setRegister(DevicePairing, request,
            hidpp::ReportType::Short);
}

void Receiver::disconnect(hidpp::DeviceIndex index)
{
    ///TODO: Device number == Device index?
    std::vector<uint8_t> request(3);

    request[0] = 3;
    request[1] = index;

    _hidpp10_device.setRegister(DevicePairing, request,
            hidpp::ReportType::Short);
}

std::map<hidpp::DeviceIndex, uint8_t> Receiver::getDeviceActivity()
{
    auto response = _hidpp10_device.getRegister(DeviceActivity, {},
            hidpp::ReportType::Long);

    std::map<hidpp::DeviceIndex, uint8_t> device_activity;
    for(uint8_t i = hidpp::WirelessDevice1; i <= hidpp::WirelessDevice6; i++)
        device_activity[static_cast<hidpp::DeviceIndex>(i)] = response[i];

    return device_activity;
}

struct Receiver::PairingInfo
    Receiver::getPairingInfo(hidpp::DeviceIndex index)
{
    std::vector<uint8_t> request(1);
    request[0] = index;
    request[0] += 0x1f;

    auto response = _hidpp10_device.getRegister(PairingInfo, request,
            hidpp::ReportType::Long);

    struct PairingInfo info{};
    info.destinationId = response[1];
    info.reportInterval = response[2];
    info.pid = response[4];
    info.pid |= (response[3] << 8);
    info.deviceType = static_cast<DeviceType::DeviceType>(response[7]);

    return info;
}

struct Receiver::ExtendedPairingInfo
    Receiver::getExtendedPairingInfo(hidpp::DeviceIndex index)
{
    std::vector<uint8_t> request(1);
    request[0] = index;
    request[0] += 0x2f;

    auto response = _hidpp10_device.getRegister(PairingInfo, request,
            hidpp::ReportType::Long);

    ExtendedPairingInfo info{};

    info.serialNumber = 0;
    for(uint8_t i = 0; i < 4; i++)
        info.serialNumber |= (response[i+1] << 8*i);

    for(uint8_t i = 0; i < 4; i++)
        info.reportTypes[i] = response[i + 5];

    uint8_t psl = response[8] & 0xf;
    if(psl > 0xc)
        info.powerSwitchLocation = PowerSwitchLocation::Reserved;
    else
        info.powerSwitchLocation = static_cast<PowerSwitchLocation>(psl);

    return info;
}

std::string Receiver::getDeviceName(hidpp::DeviceIndex index)
{
    std::vector<uint8_t> request(1);
    request[0] = index;
    request[0] += 0x3f;

    auto response = _hidpp10_device.getRegister(PairingInfo, request,
            hidpp::ReportType::Long);

    uint8_t size = response[1];
    assert(size <= 14);

    std::string name(size, ' ');
    for(std::size_t i = 0; i < size; i++)
        name[i] = response[i + 2];

    return name;
}

hidpp::DeviceIndex Receiver::deviceDisconnectionEvent(const hidpp::Report&
report)
{
    assert(report.subId() == DeviceDisconnection);
    return report.deviceIndex();
}

hidpp::DeviceConnectionEvent Receiver::deviceConnectionEvent(const
        hidpp::Report &report)
{
    assert(report.subId() == DeviceConnection);

    hidpp::DeviceConnectionEvent event{};

    event.index = report.deviceIndex();
    event.unifying = ((report.address() & 0b111) == 0x04);

    event.deviceType = static_cast<DeviceType::DeviceType>(
            report.paramBegin()[0] & 0x0f);
    event.softwarePresent = report.paramBegin()[0] & (1<<4);
    event.encrypted = report.paramBegin()[0] & (1<<5);
    event.linkEstablished = !(report.paramBegin()[0] & (1<<6));
    event.withPayload = report.paramBegin()[0] & (1<<7);
    event.fromTimeoutCheck = false;

    event.pid =(report.paramBegin()[2] << 8);
    event.pid |= report.paramBegin()[1];

    return event;
}

void Receiver::_handleDjEvent(Report& report)
{
    for(auto& handler : _dj_event_handlers)
        if(handler.second->condition(report))
            handler.second->callback(report);
}

void Receiver::_handleHidppEvent(hidpp::Report &report)
{
    for(auto& handler : _hidpp_event_handlers)
        if(handler.second->condition(report))
            handler.second->callback(report);
}

void Receiver::addDjEventHandler(const std::string& nickname,
        const std::shared_ptr<EventHandler>& handler)
{
    assert(_dj_event_handlers.find(nickname) == _dj_event_handlers.end());
    _dj_event_handlers.emplace(nickname, handler);
}

void Receiver::removeDjEventHandler(const std::string &nickname)
{
    _dj_event_handlers.erase(nickname);
}

const std::map<std::string, std::shared_ptr<EventHandler>>&
Receiver::djEventHandlers()
{
    return _dj_event_handlers;
}

void Receiver::addHidppEventHandler(const std::string& nickname,
        const std::shared_ptr<hidpp::EventHandler>& handler)
{
    assert(_hidpp_event_handlers.find(nickname) == _hidpp_event_handlers.end());
    _hidpp_event_handlers.emplace(nickname, handler);
}

void Receiver::removeHidppEventHandler(const std::string &nickname)
{
    _hidpp_event_handlers.erase(nickname);
}

const std::map<std::string, std::shared_ptr<hidpp::EventHandler>>&
Receiver::hidppEventHandlers()
{
    return _hidpp_event_handlers;
}

void Receiver::_sendDjRequest(hidpp::DeviceIndex index, uint8_t function,
        const std::vector<uint8_t>&& params)
{
    assert(params.size() <= LongParamLength);

    Report::Type type = params.size() <= ShortParamLength ?
            ReportType::Short : ReportType::Long;

    Report request(type, index, function);

    std::copy(params.begin(), params.end(), request.paramBegin());

    _raw_device->sendReportNoResponse(request.rawData());
}

Receiver::ConnectionStatusEvent Receiver::connectionStatusEvent(Report& report)
{
    assert(report.feature() == ConnectionStatus);
    ConnectionStatusEvent event{};
    event.index = report.index();
    event.linkLost = report.paramBegin()[0];
    return event;
}

void Receiver::listen()
{
    if(!_raw_device->isListening())
        _raw_device->listenAsync();

    if(_raw_device->eventHandlers().find("RECV_HIDPP") ==
        _raw_device->eventHandlers().end()) {
        // Pass all HID++ events on DefaultDevice to handleHidppEvent
        std::shared_ptr<raw::RawEventHandler> hidpp_handler =
                std::make_shared<raw::RawEventHandler>();
        hidpp_handler->condition = [](std::vector<uint8_t>& report)->bool
        {
            return (report[hidpp::Offset::Type] == hidpp::Report::Type::Short ||
                    report[hidpp::Offset::Type] == hidpp::Report::Type::Long);
        };
        hidpp_handler->callback = [this](std::vector<uint8_t>& report)
                ->void {
            hidpp::Report _report(report);
            this->_handleHidppEvent(_report);
        };
        _raw_device->addEventHandler("RECV_HIDPP", hidpp_handler);
    }

    if(_raw_device->eventHandlers().find("RECV_DJ") ==
       _raw_device->eventHandlers().end()) {
        // Pass all DJ events with device index to handleDjEvent
        std::shared_ptr<raw::RawEventHandler> dj_handler =
                std::make_shared<raw::RawEventHandler>();
        dj_handler->condition = [](std::vector<uint8_t>& report)->bool
        {
            return (report[Offset::Type] == Report::Type::Short ||
                    report[Offset::Type] == Report::Type::Long);
        };
        dj_handler->callback = [this](std::vector<uint8_t>& report)->void
        {
            Report _report(report);
            this->_handleDjEvent(_report);
        };
        _raw_device->addEventHandler("RECV_DJ", dj_handler);
    }
}

void Receiver::stopListening()
{
    _raw_device->removeEventHandler("RECV_HIDPP");
    _raw_device->removeEventHandler("RECV_DJ");

    if(_raw_device->eventHandlers().empty())
        _raw_device->stopListener();
}

std::shared_ptr<raw::RawDevice> Receiver::rawDevice() const
{
    return _raw_device;
}