/*
 * Copyright © 2013 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3,
 * as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *     Antti Kaijanmäki <antti.kaijanmaki@canonical.com>
 */

#include "manager.h"
#include "wifi/link.h"
#include "set_name_for_thread.h"

#include <core/dbus/asio/executor.h>

using namespace platform::nmofono;
namespace networking = com::ubuntu::connectivity::networking;
namespace dbus = core::dbus;
namespace fdo = org::freedesktop;
namespace NM = fdo::NetworkManager;

Manager::State::State()
{
    try {
        m_bus = std::make_shared<dbus::Bus>(dbus::WellKnownBus::system);
    } catch(std::runtime_error e) {
        std::cerr << "Failed to connect to the bus: " << e.what() << std::endl;
        throw;
    }

    auto executor = dbus::asio::make_executor(m_bus);
    m_bus->install_executor(executor);
    worker = std::move(std::thread([this]()
    {
        m_bus->run();
    }));
    com::ubuntu::location::set_name_for_thread(
                worker,
                "OfonoNmConnectivityManagerWorkerThread");
}

Manager::State::~State()
{
    if (worker.joinable())
    {
        m_bus->stop();
        worker.join();
    }
}

void
Manager::updateNetworkingStatus(std::uint32_t status)
{
    switch(status) {
    case NM_STATE_UNKNOWN:
    case NM_STATE_ASLEEP:
    case NM_STATE_DISCONNECTED:
    case NM_STATE_DISCONNECTING:
    {
        m_status.set(networking::Manager::NetworkingStatus::offline);
        break;
    }
    case NM_STATE_CONNECTING:
    {
        m_status.set(networking::Manager::NetworkingStatus::connecting);
        break;
    }
    case NM_STATE_CONNECTED_LOCAL:
    case NM_STATE_CONNECTED_SITE:
    case NM_STATE_CONNECTED_GLOBAL:
    {
        m_status.set(networking::Manager::NetworkingStatus::online);
        break;
    }
    }
}

Manager::Manager()
{
    try {
        m_state.reset(new State);
    } catch (...) {

        throw;
    }

    auto nm_service = std::make_shared<NM::Service>(m_state->m_bus);
    nm = nm_service->nm;

    auto urfkill_service = std::make_shared<fdo::URfkill::Service>(m_state->m_bus);
    urfkill = urfkill_service->urfkill;

    /// @todo add a watcher for the service
    /// @todo exceptions
    /// @todo offload the initialization to a thread or something
    /// @todo those Id() thingies

    nm->device_added->connect([this](const dbus::types::ObjectPath &path){
        std::cout << "Device Added:" << path.as_string() << std::endl;
        auto links = m_links.get();
        for (auto dev : links) {
            if (std::dynamic_pointer_cast<wifi::Link>(dev)->m_dev.object->path() == path) {
                // already in the list
                return;
            }
        }

        NM::Interface::Device dev(nm->service,
                                  nm->service->object_for_path(path));
        if (dev.type() == NM::Interface::Device::Type::wifi) {
            links.insert(std::make_shared<wifi::Link>(dev,*nm.get()));
            m_links.set(links);
        }
    });
    nm->device_removed->connect([this](const dbus::types::ObjectPath &path){
        std::cout << "Device Removed:" << path.as_string() << std::endl;
        auto links = m_links.get();
        for (auto dev : links) {
            if (std::dynamic_pointer_cast<wifi::Link>(dev)->m_dev.object->path() == path) {
                links.erase(dev);
                break;
            }
        }
        m_links.set(links);
    });
    std::set<std::shared_ptr<networking::Link> > links;
    for(auto dev : nm->get_devices()) {
        switch (dev.type()) {
        case NM::Interface::Device::Type::wifi:
        {
            std::shared_ptr<networking::Link> link;
            link.reset(new wifi::Link(dev, *nm.get()));
            links.insert(link);
            break;
        }
        default:
            ;
        }
    }
    m_links.set(links);

    updateNetworkingStatus(nm->state->get());
    nm->properties_changed->connect([this](NM::Interface::NetworkManager::Signal::PropertiesChanged::ArgumentType map) {
        for (auto entry : map) {
            const std::string &key = entry.first;
            if (key == "ActiveConnections") {

            } else if (key == "PrimaryConnection") {
            } else if (key == "State") {
                updateNetworkingStatus(entry.second.as<std::uint32_t>());
            }
        }
    });

    // if even one of the rfkill devices is not blocked we set flightmode OFF
    // once we get the flightmode backend we can do partial flight mode
    m_flightMode.set(networking::Manager::FlightModeStatus::on);
    for (auto path : urfkill->enumerate_devices()) {
        fdo::URfkill::Interface::Device dev(urfkill->service->object_for_path(path));
        if (!dev.soft->get() && !dev.hard->get()) {
            m_flightMode.set(networking::Manager::FlightModeStatus::off);
            break;
        }
    }

    /// @todo set by the default connections.
    m_characteristics.set(networking::Link::Characteristics::empty);
}

void
Manager::enableFlightMode()
{
    if (urfkill->block(0/*all*/, true))
        m_flightMode.set(networking::Manager::FlightModeStatus::on);
    else
        m_flightMode.set(networking::Manager::FlightModeStatus::off);
}

void
Manager::disableFlightMode()
{
    if (urfkill->block(0/*all*/, false))
        m_flightMode.set(networking::Manager::FlightModeStatus::off);
    else
        m_flightMode.set(networking::Manager::FlightModeStatus::on);
}

const core::Property<networking::Manager::FlightModeStatus>&
Manager::flightMode()
{
    // - connect to each individual URfkill.Killswitch interface
    // - make this property to reflect their combined state
    /// @todo implement flightmode status properly when URfkill gets the flightmode API
    return m_flightMode;
}

const core::Property<std::set<std::shared_ptr<networking::Link> > >&
Manager::links()
{
    return m_links;
}

const core::Property<std::set<std::shared_ptr<networking::Service>>>&
Manager::services()
{
    return m_services;
}

const core::Property<networking::Manager::NetworkingStatus> &
Manager::status()
{
    return m_status;
}

const core::Property<std::uint32_t>&
Manager::characteristics()
{
    return m_characteristics;
}
