// Copyright (C) 2001 - 2021 Rob Caelers & Raymond Penners
// All rights reserved.
//
// 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/>.
//

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "debug.hh"

#include "GenericDBusApplet.hh"

#include "ui/TimerBoxControl.hh"
#include "ui/GUIConfig.hh"
#include "commonui/Text.hh"

using namespace workrave;

GenericDBusApplet::GenericDBusApplet(std::shared_ptr<IPluginContext> context)
  : context(context)
  , menu_model(context->get_menu_model())
  , menu_helper(menu_model)
  , apphold(context->get_toolkit())
{
  TRACE_ENTRY();

  control = std::make_shared<TimerBoxControl>(context->get_core(), "applet", this);

  for (auto &i: data)
    {
      i.bar_text = "";
      i.bar_primary_color = 0;
      i.bar_primary_val = 0;
      i.bar_primary_max = 0;
      i.bar_secondary_color = 0;
      i.bar_secondary_val = 0;
      i.bar_secondary_max = 0;
    }

  GUIConfig::trayicon_enabled().connect(this, [this](bool) { send_tray_icon_enabled(); });

  init();
}

GenericDBusApplet::~GenericDBusApplet()
{
  TRACE_ENTRY();
}

boost::signals2::signal<void(GenericDBusApplet::TimerData, GenericDBusApplet::TimerData, GenericDBusApplet::TimerData)> &
GenericDBusApplet::signal_timers_updated()
{
  return timers_updated_signal;
}

boost::signals2::signal<void(GenericDBusApplet::MenuItems)> &
GenericDBusApplet::signal_menu_updated()
{
  return menu_updated_signal;
}

boost::signals2::signal<void(GenericDBusApplet::MenuItem)> &
GenericDBusApplet::signal_menu_item_updated()
{
  return menu_item_updated_signal;
}

boost::signals2::signal<void(bool)> &
GenericDBusApplet::signal_tray_icon_updated()
{
  return tray_icon_updated_signal;
}

#if defined(HAVE_DBUS)
void
GenericDBusApplet::set_name_watcher(NameWatcher watcher)
{
  name_watches.clear();
  name_watcher = std::move(watcher);
}

void
GenericDBusApplet::publish_state()
{
  send_menu_updated_event();
  send_tray_icon_enabled();
  update_view();
}
#endif

void
GenericDBusApplet::set_slot(BreakId id, int slot)
{
  TRACE_ENTRY_PAR(id, slot);
  data[slot].slot = id;
}

void
GenericDBusApplet::set_time_bar(BreakId id,
                                int value,
                                TimerColorId primary_color,
                                int primary_val,
                                int primary_max,
                                TimerColorId secondary_color,
                                int secondary_val,
                                int secondary_max)
{
  TRACE_ENTRY_PAR(int(id), value);
  data[id].bar_text = Text::time_to_string(value);
  data[id].bar_primary_color = (int)primary_color;
  data[id].bar_primary_val = primary_val;
  data[id].bar_primary_max = primary_max;
  data[id].bar_secondary_color = (int)secondary_color;
  data[id].bar_secondary_val = secondary_val;
  data[id].bar_secondary_max = secondary_max;
}

void
GenericDBusApplet::set_icon(OperationModeIcon icon)
{
  (void)icon;
}

void
GenericDBusApplet::update_view()
{
  TRACE_ENTRY();
  timers_updated_signal(data[BREAK_ID_MICRO_BREAK], data[BREAK_ID_REST_BREAK], data[BREAK_ID_DAILY_LIMIT]);
}

void
GenericDBusApplet::init()
{
  workrave::utils::connect(menu_model->signal_update(), this, [this]() { send_menu_updated_event(); });
  workrave::utils::connect(menu_helper.signal_update(), this, [this](auto node) { update_menu_item(node); });
  menu_helper.setup_event();
  workrave::utils::connect(context->get_toolkit()->signal_timer(), control, [this]() { control->update(); });
}

void
GenericDBusApplet::applet_embed(bool enabled, const std::string &sender)
{
  TRACE_ENTRY_PAR(enabled, sender);
  embedded = enabled;

  name_watches.clear();
  active_bus_names.clear();

  if (!sender.empty())
    {
      if (name_watcher)
        {
          name_watches.emplace(sender,
                               name_watcher(sender, [this, sender](bool present) { bus_name_presence(sender, present); }));
          bus_name_presence(sender, true);
        }
    }

  if (!enabled)
    {
      TRACE_MSG("Disabling");
      apphold.release();
      visible = false;
    }
}

void
GenericDBusApplet::get_menu(std::list<MenuItem> &menuitems)
{
  std::list<MenuItem> items;
  init_menu_list(items, menu_model->get_root());
  menuitems = items;
}

void
GenericDBusApplet::get_tray_icon_enabled(bool &enabled) const
{
  enabled = GUIConfig::applet_icon_enabled()();
}

void
GenericDBusApplet::applet_command(int command)
{
  auto node = menu_helper.find_node(command);
  if (node)
    {
      node->activate();
    }
}

void
GenericDBusApplet::applet_menu_action(const std::string &action)
{
  auto node = menu_helper.find_node(action);
  if (node)
    {
      node->activate();
    }
}

void
GenericDBusApplet::button_clicked(uint32_t button)
{
  (void)button;
  control->force_cycle();
}

void
GenericDBusApplet::bus_name_presence(const std::string &name, bool present)
{
  TRACE_ENTRY_PAR(name, present);
  TRACE_VAR(visible, embedded);
  if (present)
    {
      active_bus_names.insert(name);
      if (!visible && embedded)
        {
          TRACE_MSG("Enabling");
          visible = true;
          apphold.hold();
        }
    }
  else
    {
      active_bus_names.erase(name);
      if (active_bus_names.empty())
        {
          TRACE_MSG("Disabling");
          visible = false;
          embedded = false;
          apphold.release();
        }
    }
}

void
GenericDBusApplet::send_tray_icon_enabled()
{
  TRACE_ENTRY();
  bool on = GUIConfig::applet_icon_enabled()();

  tray_icon_updated_signal(on);
}

void
GenericDBusApplet::send_menu_updated_event()
{
  std::list<MenuItem> items;
  init_menu_list(items, menu_model->get_root());

  menu_updated_signal(items);
}

void
GenericDBusApplet::init_menu_list(std::list<MenuItem> &items, menus::Node::Ptr node)
{
  uint32_t command = menu_helper.allocate_command(node->get_id());

  uint8_t flags = MENU_ITEM_FLAG_NONE;

  if (node->is_visible())
    {
      flags |= MENU_ITEM_FLAG_VISIBLE;
    }

  if (auto n = std::dynamic_pointer_cast<menus::SubMenuNode>(node); n)
    {
      bool add_sub_menu = !items.empty();

      if (add_sub_menu)
        {
          items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::SubMenuBegin, flags);
        }
      for (auto &menu_to_add: n->get_children())
        {
          init_menu_list(items, menu_to_add);
        }
      if (add_sub_menu)
        {
          items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::SubMenuEnd, flags);
        }
    }

  else if (auto n = std::dynamic_pointer_cast<menus::SectionNode>(node); n)
    {
      for (auto &menu_to_add: n->get_children())
        {
          init_menu_list(items, menu_to_add);
        }
    }

  else if (auto n = std::dynamic_pointer_cast<menus::RadioGroupNode>(node); n)
    {
      items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::RadioGroupBegin, flags);
      for (auto &menu_to_add: n->get_children())
        {
          init_menu_list(items, menu_to_add);
        }
      items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::RadioGroupEnd, flags);
    }

  else if (auto n = std::dynamic_pointer_cast<menus::ActionNode>(node); n)
    {
      items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::Action, flags);
    }

  else if (auto n = std::dynamic_pointer_cast<menus::ToggleNode>(node); n)
    {
      if (n->is_checked())
        {
          flags |= MENU_ITEM_FLAG_ACTIVE;
        }
      items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::Check, flags);
    }

  else if (auto n = std::dynamic_pointer_cast<menus::RadioNode>(node); n)
    {
      if (n->is_checked())
        {
          flags |= MENU_ITEM_FLAG_ACTIVE;
        }
      items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::Radio, flags);
    }

  else if (auto n = std::dynamic_pointer_cast<menus::SeparatorNode>(node); n)
    {
      items.emplace_back(n->get_text(), node->get_dynamic_text(), node->get_id(), command, MenuItemType::Separator, flags);
    }
}

void
GenericDBusApplet::update_menu_item(menus::Node::Ptr node)
{
  uint32_t command = menu_helper.allocate_command(node->get_id());
  uint8_t flags = MENU_ITEM_FLAG_NONE;
  MenuItemType type{};

  if (node->is_visible())
    {
      flags |= MENU_ITEM_FLAG_VISIBLE;
    }

  if (auto n = std::dynamic_pointer_cast<menus::SubMenuNode>(node); n)
    {
      type = MenuItemType::SubMenuBegin;
    }
  else if (auto n = std::dynamic_pointer_cast<menus::RadioGroupNode>(node); n)
    {
      type = MenuItemType::RadioGroupBegin;
    }
  else if (auto n = std::dynamic_pointer_cast<menus::ActionNode>(node); n)
    {
      type = MenuItemType::Action;
    }
  else if (auto n = std::dynamic_pointer_cast<menus::ToggleNode>(node); n)
    {
      if (n->is_checked())
        {
          flags |= MENU_ITEM_FLAG_ACTIVE;
        }
      type = MenuItemType::Check;
    }

  else if (auto n = std::dynamic_pointer_cast<menus::RadioNode>(node); n)
    {
      if (n->is_checked())
        {
          flags |= MENU_ITEM_FLAG_ACTIVE;
        }
      type = MenuItemType::Radio;
    }

  else if (auto n = std::dynamic_pointer_cast<menus::SeparatorNode>(node); n)
    {
      type = MenuItemType::Separator;
    }

  MenuItem item(node->get_text(), node->get_dynamic_text(), node->get_id(), command, type, flags);
  menu_item_updated_signal(item);
}
