/*
 * Copyright 2014-2016 Canonical Ltd.
 * Copyright 2023 Robert Tari
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU 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 warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
 *
 * Authors:
 *   Charles Kerr <charles.kerr@canonical.com>
 *   Robert Tari <robert@tari.in>
 */

#include <datetime/appointment.h>
#include <datetime/settings.h>
#include <datetime/snap.h>

#include "notification-fixture.h"

using namespace ayatana::indicator::datetime;

namespace uin = ayatana::indicator::notifications;

/***
****
***/

namespace
{
  static constexpr char const * APP_NAME {"indicator-datetime-service"};
}


namespace
{
  gboolean quit_idle (gpointer gloop)
  {
    g_main_loop_quit(static_cast<GMainLoop*>(gloop));
    return G_SOURCE_REMOVE;
  };

  class SoundNotificationFixture : public NotificationFixture
  {
  };
}

/***
****
***/

TEST_F(SoundNotificationFixture, InteractiveDuration)
{
  static constexpr int duration_minutes = 120;
  auto settings = std::make_shared<Settings>();
  settings->alarm_duration.set(duration_minutes);
  auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
  auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>();
  auto snap = create_snap(ne, sb, settings);

  settings->cal_notification_enabled.set(true);
  settings->cal_notification_sounds.set(true);
  settings->cal_notification_vibrations.set(true);
  settings->cal_notification_bubbles.set(true);
  settings->cal_notification_list.set(true);

#ifdef LOMIRI_FEATURES_ENABLED
  /* Here both values true|false should succeed. */
  mock_capabilities(true);
#else
  mock_capabilities(false);
#endif

  // call the Snap Decision
  auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);};
  (*snap)(appt, appt.alarms.front(), func);

  // confirm that Notify got called once
  guint len = 0;
  GError * error = nullptr;
  const auto calls = dbus_test_dbus_mock_object_get_method_calls (notify_mock,
                                                                  notify_obj,
                                                                  METHOD_NOTIFY,
                                                                  &len,
                                                                  &error);
  g_assert_no_error(error);
  ASSERT_EQ(1, len);

  // confirm that the app_name passed to Notify was APP_NAME
  const auto& params = calls[0].params;
  ASSERT_EQ(8, g_variant_n_children(params));
  const char * str = nullptr;
  g_variant_get_child (params, 0, "&s", &str);
  ASSERT_STREQ(APP_NAME, str);

  // confirm that the icon passed to Notify was "calendar-app"
  g_variant_get_child (params, 2, "&s", &str);
  ASSERT_STREQ("calendar-app", str);

  // confirm that the timeout passed to Notify matches duration_minutes
  int32_t i32;
  g_variant_get_child (params, 7, "i", &i32);
  const auto duration = std::chrono::minutes(duration_minutes);
  EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32);

#ifdef LOMIRI_FEATURES_ENABLED
  /* If setting mock_capabilities to false, set the below to false, as well. */
  if (true) {
    // Due to custom logic in Lomiri, also make sure custom timeout hint is set.
    bool b;
    auto hints = g_variant_get_child_value (params, 6);
    i32 = 0;
    b = g_variant_lookup (hints, HINT_LOMIRI_TIMEOUT, "i", &i32);
    EXPECT_TRUE(b);
    EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32);
    g_variant_unref(hints);
  }
#endif

  ne.reset();
}

/***
****
***/

/**
 * A DefaultSoundBuilder wrapper which remembers the parameters of the last sound created.
 */
class TestSoundBuilder: public uin::SoundBuilder
{
public:
    TestSoundBuilder() =default;
    ~TestSoundBuilder() =default;

    virtual std::shared_ptr<uin::Sound> create(const std::string& role, const std::string& uri, unsigned int volume, bool loop) override {
        m_role = role;
        m_uri = uri;
        m_volume = volume;
        m_loop = loop;
        return m_impl.create(role, uri, volume, loop);
    }

    const std::string& role() { return m_role; }
    const std::string& uri() { return m_uri; }
    unsigned int volume() { return m_volume; }
    bool loop() { return m_loop; }

private:
    std::string m_role;
    std::string m_uri;
    unsigned int m_volume;
    bool m_loop;
    uin::DefaultSoundBuilder m_impl;
};

std::string path_to_uri_if_exists(const std::string& path)
{
  std::string uri;
  auto file = g_file_new_for_path(path.c_str());

  if (g_file_query_exists(file, /* cancellable */ nullptr)) {
    auto uri_cstr = g_file_get_uri(file);
    uri = std::string(uri_cstr);
    g_free(uri_cstr);
  }

  g_clear_pointer(&file, g_object_unref);
  return uri;
}

TEST_F(NotificationFixture,DefaultSounds)
{
  auto settings = std::make_shared<Settings>();
  auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
  auto sb = std::make_shared<TestSoundBuilder>();
  auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);};

  settings->cal_notification_enabled.set(true);
  settings->cal_notification_sounds.set(true);
  settings->cal_notification_vibrations.set(true);
  settings->cal_notification_bubbles.set(true);
  settings->cal_notification_list.set(true);

  const struct {
      Appointment appointment;
      std::string expected_role;
      std::string expected_uri;
  } test_cases[] = {
      { ualarm, "alarm", path_to_uri_if_exists(ALARM_DEFAULT_SOUND) }
    // No sound for appointments
    //  { appt, "alert", path_to_uri_if_exists(CALENDAR_DEFAULT_SOUND) }
  };

  auto snap = create_snap(ne, sb, settings);
  for(const auto& test_case : test_cases)
  {
    (*snap)(test_case.appointment, test_case.appointment.alarms.front(), func);
    EXPECT_EQ(test_case.expected_uri, sb->uri());
    EXPECT_EQ(test_case.expected_role, sb->role());
  }
}
