/*
 * 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/>.
 *
 * Authored by: Antti Kaijanmäki <antti.kaijanmaki@canonical.com>
 */

#include <core/dbus/dbus.h>
#include <core/dbus/fixture.h>
#include <core/dbus/object.h>
#include <core/dbus/property.h>
#include <core/dbus/service.h>
#include <core/dbus/interfaces/properties.h>
#include <core/dbus/types/stl/tuple.h>
#include <core/dbus/types/stl/vector.h>

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

#include "sig_term_catcher.h"
#include "test_data.h"
#include "test_service.h"

#include <core/testing/cross_process_sync.h>
#include <core/testing/fork_and_run.h>

#include <gtest/gtest.h>

#include <system_error>
#include <thread>

#include <services/nm.h>
#include <services/urfkill.h>
namespace fdo = org::freedesktop;
namespace NM = fdo::NetworkManager;

#include <com/ubuntu/connectivity/networking/manager.h>
namespace cuc = com::ubuntu::connectivity;

namespace dbus = core::dbus;

namespace
{
struct Service : public core::dbus::testing::Fixture {};

auto session_bus_config_file =
        core::dbus::testing::Fixture::default_session_bus_config_file() =
        core::testing::session_bus_configuration_file();

auto system_bus_config_file =
        core::dbus::testing::Fixture::default_system_bus_config_file() =
        core::testing::system_bus_configuration_file();

}

TEST_F(Service, flightMode)
{
        core::testing::CrossProcessSync services_ready;
        core::testing::CrossProcessSync failing_invokations;
        core::testing::CrossProcessSync client_ready;

        auto service = [&, this]()
        {
            core::testing::SigTermCatcher sc;

            auto bus = system_bus();
            bus->install_executor(core::dbus::asio::make_executor(bus));

            auto nm_service = NM::Service::Mock(bus);
            auto nm_root = nm_service.nm;

            nm_root->object->install_method_handler<NM::Interface::NetworkManager::Method::GetDevices>([bus, nm_root](const dbus::Message::Ptr& msg)
            {
                auto reply = dbus::Message::make_method_return(msg);
                reply->writer() << std::vector<dbus::types::ObjectPath>();
                bus->send(reply);
            });

            nm_root->state->set(0);


            auto urfkill_service = fdo::URfkill::Service::Mock(bus);
            auto urfkill_root = urfkill_service.urfkill;

            auto rfkill_device = fdo::URfkill::Interface::Device(urfkill_root->service->add_object_for_path(dbus::types::ObjectPath("/org/freedesktop/URfkill/devices/0")));

            // as we only have one device and it's not blocked the initial state should be off
            rfkill_device.hard->set(false);
            rfkill_device.soft->set(false);

            urfkill_root->object->install_method_handler<fdo::URfkill::Interface::URfkill::Method::EnumerateDevices>([bus](const dbus::Message::Ptr& msg)
            {
                auto reply = dbus::Message::make_method_return(msg);
                std::vector<dbus::types::ObjectPath> devices;
                devices.push_back(dbus::types::ObjectPath("/org/freedesktop/URfkill/devices/0"));
                reply->writer() << devices;
                bus->send(reply);
            });

            bool fail = true;
            urfkill_root->object->install_method_handler<fdo::URfkill::Interface::URfkill::Method::Block>([bus, &fail](const dbus::Message::Ptr& msg)
            {
                auto reader = msg->reader();
                // not used for now.
                //std::uint32_t type = reader.pop_uint32();
                //bool block = reader.pop_boolean();

                auto reply = dbus::Message::make_method_return(msg);
                reply->writer() << !fail;
                bus->send(reply);
            });

            std::thread t{[bus](){ bus->run(); }};

            services_ready.try_signal_ready_for(std::chrono::milliseconds{2000});

            EXPECT_EQ(1, failing_invokations.wait_for_signal_ready_for(std::chrono::milliseconds{2000}));
            fail = false;

            EXPECT_EQ(1, client_ready.wait_for_signal_ready_for(std::chrono::milliseconds{2000}));
            sc.wait_for_signal_for(std::chrono::seconds{10});

            bus->stop();

            if (t.joinable())
                t.join();

            return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success;
        };

        auto client = [&, this]()
        {
            EXPECT_EQ(1, services_ready.wait_for_signal_ready_for(std::chrono::milliseconds{2000}));
            std::unique_ptr<cuc::networking::Manager> mgr;
            mgr = cuc::networking::Manager::createInstance();

            std::vector<cuc::networking::Manager::FlightModeStatus> statuses;
            try {
                EXPECT_EQ(mgr->flightMode().get(), cuc::networking::Manager::FlightModeStatus::off);

                mgr->flightMode().changed().connect([&statuses](cuc::networking::Manager::FlightModeStatus status) {
                    statuses.push_back(status);
                });

                mgr->enableFlightMode();
                mgr->disableFlightMode();

                failing_invokations.try_signal_ready_for(std::chrono::milliseconds{2000});

                mgr->enableFlightMode();
                mgr->disableFlightMode();

            } catch (std::runtime_error e) {
                std::cout << "exception: " << e.what() << std::endl;
            }

            client_ready.try_signal_ready_for(std::chrono::milliseconds{2000});

            EXPECT_EQ(statuses.size(), 2);
            EXPECT_EQ(statuses.at(0), cuc::networking::Manager::FlightModeStatus::on);
            EXPECT_EQ(statuses.at(1), cuc::networking::Manager::FlightModeStatus::off);

            delete mgr.release();

            return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success;
        };

        EXPECT_EQ(core::testing::ForkAndRunResult::empty, core::testing::fork_and_run(service, client));
}
