/**************************************************************************
 *   Copyright (C) 2010 by David S. Register                               *
 *                                                                         *
 *   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 2 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 <https://www.gnu.org/licenses/>. *
 **************************************************************************/

/**
 * \file
 *
 * PlugInManager and helper classes -- Mostly gui parts (dialogs) and
 * plugin API stuff.
 */

#ifndef _PLUGINMGR_H_
#define _PLUGINMGR_H_

#include <atomic>
#include <memory>
#include <string>

#include "config.h"

#include <wx/wx.h>
#include <wx/bmpcbox.h>
#include <wx/choice.h>
#include <wx/dynarray.h>
#include <wx/dynlib.h>
#include <wx/hyperlink.h>
#include <wx/json_defs.h>
#include <wx/jsonwriter.h>
#include <wx/tglbtn.h>

#ifndef __ANDROID__
#ifdef OCPN_USE_CURL
#include <wx/curl/http.h>
#include <wx/curl/dialog.h>
#endif
#endif

#include "o_sound/o_sound.h"

#include "model/ais_target_data.h"
#include "model/catalog_parser.h"
#include "model/comm_navmsg.h"
#include "model/plugin_blacklist.h"
#include "model/plugin_loader.h"
#include "model/semantic_vers.h"

#include "chartimg.h"
#include "observable.h"
#include "ocpndc.h"
#include "ocpn_plugin.h"
#include "s57chart.h"  // for Object list
#include "top_frame.h"

//----------------------------------------------------------------------------
// PlugIn Messaging scheme Event
//----------------------------------------------------------------------------

class PlugInManager;                // forward
extern PlugInManager* g_pi_manager; /**< Global instance */

class PluginListPanel;    // forward
class PluginPanel;        // forward
class pluginUtilHandler;  // forward in .cpp file

PlugIn_AIS_Target* Create_PI_AIS_Target(AisTargetData* ptarget);

class OCPN_MsgEvent : public wxEvent {
public:
  OCPN_MsgEvent(wxEventType commandType = wxEVT_NULL, int id = 0);

  OCPN_MsgEvent(const OCPN_MsgEvent& event)
      : wxEvent(event),
        m_MessageID(event.m_MessageID),
        m_MessageText(event.m_MessageText) {}

  ~OCPN_MsgEvent();

  // accessors
  wxString GetID() { return m_MessageID; }
  wxString GetJSONText() { return m_MessageText; }

  void SetID(const wxString& string) { m_MessageID = string; }
  void SetJSONText(const wxString& string) { m_MessageText = string; }

  // required for sending with wxPostEvent()
  wxEvent* Clone() const;

private:
  wxString m_MessageID;
  wxString m_MessageText;
};

extern const wxEventType wxEVT_OCPN_MSG;

enum ActionVerb {
  NOP = 0,
  UPGRADE_TO_MANAGED_VERSION,
  UPGRADE_INSTALLED_MANAGED_VERSION,
  REINSTALL_MANAGED_VERSION,
  DOWNGRADE_INSTALLED_MANAGED_VERSION,
  UNINSTALL_MANAGED_VERSION,
  INSTALL_MANAGED_VERSION,
  UPDATE_IMPORTED_VERSION
};

class PlugInMenuItemContainer {
public:
  wxMenuItem* pmenu_item;
  opencpn_plugin* m_pplugin;
  bool b_viz;
  bool b_grey;
  int id;
  wxString m_in_menu;
  bool extended;
};

//    Define an array of PlugIn MenuItem Containers
WX_DEFINE_ARRAY_PTR(PlugInMenuItemContainer*, ArrayOfPlugInMenuItems);

class PlugInToolbarToolContainer {
public:
  PlugInToolbarToolContainer();
  ~PlugInToolbarToolContainer();

  opencpn_plugin* m_pplugin;
  int id;
  wxString label;
  wxBitmap* bitmap_day;
  wxBitmap* bitmap_dusk;
  wxBitmap* bitmap_night;
  wxBitmap* bitmap_Rollover_day;
  wxBitmap* bitmap_Rollover_dusk;
  wxBitmap* bitmap_Rollover_night;

  wxItemKind kind;
  wxString shortHelp;
  wxString longHelp;
  wxObject* clientData;
  int position;
  bool b_viz;
  bool b_toggle;
  int tool_sel;
  wxString pluginNormalIconSVG;
  wxString pluginRolloverIconSVG;
  wxString pluginToggledIconSVG;
};

//    Define an array of PlugIn ToolbarTool Containers
WX_DEFINE_ARRAY_PTR(PlugInToolbarToolContainer*, ArrayOfPlugInToolbarTools);

//-----------------------------------------------------------------------------------------------------
//
//          The PlugIn Manager Specification
//
//-----------------------------------------------------------------------------------------------------

class BlacklistUI;

class PlugInManager : public wxEvtHandler {
public:
  PlugInManager(AbstractTopFrame* parent);
  virtual ~PlugInManager();

  bool RenderAllCanvasOverlayPlugIns(ocpnDC& dc, const ViewPort& vp,
                                     int canvasIndex, int priority);
  bool RenderAllGLCanvasOverlayPlugIns(wxGLContext* pcontext,
                                       const ViewPort& vp, int canvasIndex,
                                       int priority);
  // void SendCursorLatLonToAllPlugIns(double lat, double lon);
  void SendViewPortToRequestingPlugIns(ViewPort& vp);
  void PrepareAllPluginContextMenus();

  void NotifySetupOptions();
  void ClosePlugInPanel(const PlugInContainer* pic, int ix);
  void CloseAllPlugInPanels(int);

  ArrayOfPlugInToolbarTools& GetPluginToolbarToolArray() {
    return m_PlugInToolbarTools;
  }
  int AddToolbarTool(wxString label, wxBitmap* bitmap, wxBitmap* bmpRollover,
                     wxItemKind kind, wxString shortHelp, wxString longHelp,
                     wxObject* clientData, int position, int tool_sel,
                     opencpn_plugin* pplugin);

  void RemoveToolbarTool(int tool_id);
  void SetToolbarToolViz(int tool_id, bool viz);
  void SetToolbarItemState(int tool_id, bool toggle);
  void SetToolbarItemBitmaps(int item, wxBitmap* bitmap, wxBitmap* bmpDisabled);

  int AddToolbarTool(wxString label, wxString SVGfile, wxString SVGRolloverfile,
                     wxString SVGToggledfile, wxItemKind kind,
                     wxString shortHelp, wxString longHelp,
                     wxObject* clientData, int position, int tool_sel,
                     opencpn_plugin* pplugin);

  void SetToolbarItemBitmaps(int item, wxString SVGfile,
                             wxString SVGfileRollover, wxString SVGfileToggled);

  opencpn_plugin* FindToolOwner(const int id);
  wxString GetToolOwnerCommonName(const int id);
  void ShowDeferredBlacklistMessages();

  ArrayOfPlugInMenuItems& GetPluginContextMenuItemArray() {
    return m_PlugInMenuItems;
  }
  int AddCanvasContextMenuItemPIM(wxMenuItem* pitem, opencpn_plugin* pplugin,
                                  const char* name = "",
                                  bool is_extended = false);
  void RemoveCanvasContextMenuItem(int item, const char* name = "");
  void SetCanvasContextMenuItemViz(int item, bool viz, const char* name = "");
  void SetCanvasContextMenuItemGrey(int item, bool grey, const char* name = "");

  bool UpDateChartDataTypes();
  void FinalizePluginLoadall();

  bool UpdateConfig();
  void SendResizeEventToAllPlugIns(int x, int y);
  void SetColorSchemeForAllPlugIns(ColorScheme cs);
  void NotifyAuiPlugIns(void);
  bool CallLateInit(void);

  bool IsAnyPlugInChartEnabled();

  void SendBaseConfigToAllPlugIns();
  void SendS52ConfigToAllPlugIns(bool bReconfig = false);
  void SendSKConfigToAllPlugIns();

  void UpdateManagedPlugins();
  bool CheckBlacklistedPlugin(const PluginMetadata plugin);

  void InitCommListeners(void);
  /**
   * Process incoming NMEA 0183 messages from the message bus.
   * Filters messages based on their source configuration and distributes
   * valid messages to all interested plugins.
   *
   * @param n0183_msg Message container with NMEA 0183 sentence and metadata
   */
  void HandleN0183(std::shared_ptr<const Nmea0183Msg> n0183_msg);
  /**
   * Process incoming SignalK messages from the message bus.
   * Validates and forwards SignalK data to plugins that have registered
   * interest in SignalK messages.
   *
   * @param sK_msg Message container with SignalK data and metadata
   */
  void HandleSignalK(std::shared_ptr<const SignalkMsg> sK_msg);

  wxArrayString GetPlugInChartClassNameArray(void);
  opencpn_plugin* GetProvidingPlugin(const wxString& ChartClassName);

  ListOfPI_S57Obj* GetPlugInObjRuleListAtLatLon(ChartPlugInWrapper* target,
                                                float zlat, float zlon,
                                                float SelectRadius,
                                                const ViewPort& vp);
  wxString CreateObjDescriptions(ChartPlugInWrapper* target,
                                 ListOfPI_S57Obj* rule_list);

  wxString GetLastError();
  AbstractTopFrame* GetParentFrame() { return m_parent; }

  void DimeWindow(wxWindow* win);
  pluginUtilHandler* GetUtilHandler() { return m_utilHandler; }
  void SetListPanelPtr(PluginListPanel* ptr) { m_listPanel = ptr; }

  ListOfPI_S57Obj* GetLightsObjRuleListVisibleAtLatLon(
      ChartPlugInWrapper* target, float zlat, float zlon, const ViewPort& vp);

private:
  bool CheckBlacklistedPlugin(wxString name, int major, int minor);
  bool CheckBlacklistedPlugin(opencpn_plugin* plugin);
  void OnNewMessageType();

  ObservableListener evt_ais_json_listener;
  ObservableListener evt_blacklisted_plugin_listener;
  ObservableListener evt_download_failed_listener;
  ObservableListener evt_download_ok_listener;
  ObservableListener evt_load_directory_listener;
  ObservableListener evt_load_plugin_listener;
  ObservableListener evt_plugin_loadall_finalize_listener;
  ObservableListener evt_pluglist_change_listener;
  ObservableListener evt_update_chart_types_listener;
  ObservableListener evt_json_to_all_plugins_listener;
  ObservableListener evt_routeman_json_listener;
  ObservableListener evt_routeman_leginfo_listener;

  ObservableListener m_listener_SignalK;

  ObsListener m_new_msgtype_lstnr;

  ObsListener m_on_msg_sent_listener;

  std::unordered_map<std::string, ObsListener> m_0183_listeners;

  wxBitmap* BuildDimmedToolBitmap(wxBitmap* pbmp_normal,
                                  unsigned char dim_ratio);

  void ProcessLateInit(const PlugInContainer* pic);
  void OnPluginActivate(const PlugInContainer* pic);
  void OnPluginDeactivate(const PlugInContainer* pic);
  void HandlePluginLoaderEvents();
  void HandlePluginHandlerEvents();

  AbstractTopFrame* m_parent;
  std::unique_ptr<BlacklistUI> m_blacklist_ui;

  wxString m_last_error_string;

  ArrayOfPlugInMenuItems m_PlugInMenuItems;
  ArrayOfPlugInToolbarTools m_PlugInToolbarTools;

  wxString m_plugin_location;

  int m_plugin_tool_id_next;
  int m_plugin_menu_item_id_next;
  wxBitmap m_cached_overlay_bm;

  wxArrayString m_plugin_order;
  void SetPluginOrder(wxString serialized_names);
  wxString GetPluginOrder();

  pluginUtilHandler* m_utilHandler;
  PluginListPanel* m_listPanel;
  std::unique_ptr<AbstractBlacklist> m_blacklist;

#ifndef __ANDROID__
#ifdef OCPN_USE_CURL

public:
  wxCurlDownloadThread* m_pCurlThread;
  // The libcurl handle being re used for the transfer.
  std::shared_ptr<wxCurlBase> m_pCurl;

  // returns true if the error can be ignored
  bool HandleCurlThreadError(wxCurlThreadError err, wxCurlBaseThread* p,
                             const wxString& url = wxEmptyString);
  void OnEndPerformCurlDownload(wxCurlEndPerformEvent& ev);
  void OnCurlDownload(wxCurlDownloadEvent& ev);

  wxEvtHandler* m_download_evHandler;
  long* m_downloadHandle;
  bool m_last_online;
  long m_last_online_chk;
#endif
#endif

  DECLARE_EVENT_TABLE()
};

WX_DEFINE_ARRAY_PTR(PluginPanel*, ArrayOfPluginPanel);

/*
 * Panel with a single + sign which opens the "Add/download plugins" dialog.
 */
class AddPluginPanel : public wxPanel {
public:
  AddPluginPanel(wxWindow* parent);
  void OnClick(wxMouseEvent& event);
  ~AddPluginPanel();

protected:
  wxBitmap m_bitmap;
  wxStaticBitmap* m_staticBitmap;
  wxWindow* m_parent;
};

/*
 * Panel with buttons to control plugin catalog management.
 */
class CatalogMgrPanel : public wxPanel {
public:
  CatalogMgrPanel(wxWindow* parent);
  ~CatalogMgrPanel();
  void OnUpdateButton(wxCommandEvent& event);
  void SetListPanelPtr(PluginListPanel* listPanel) {
    m_PluginListPanel = listPanel;
  }
  void OnTarballButton(wxCommandEvent& event);
  void OnPluginSettingsButton(wxCommandEvent& event);

protected:
  wxString GetCatalogText(bool);
  void SetUpdateButtonLabel();
  wxString GetImportInitDir();

  wxButton *m_updateButton, *m_advancedButton, *m_tarballButton;
  wxButton* m_adv_button;
  wxStaticText* m_catalogText;
  wxWindow* m_parent;
  PluginListPanel* m_PluginListPanel;
  ObservableListener catalog_listener;
};

#define ID_CMD_BUTTON_PERFORM_ACTION 27663

class PluginListPanel : public wxScrolledWindow {
  DECLARE_EVENT_TABLE()

public:
  PluginListPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos,
                  const wxSize& size);
  ~PluginListPanel();

  void SelectPlugin(PluginPanel* pi);
  void MoveUp(PluginPanel* pi);
  void MoveDown(PluginPanel* pi);
  void UpdateSelections();
  void UpdatePluginsOrder();

  /** Complete reload from plugins array. */
  void ReloadPluginPanels();
  void SelectByName(wxString& name);

private:
  void AddPlugin(const PlugInData& pd);
  void AddPlugin(const std::string& name);
  int ComputePluginSpace(ArrayOfPluginPanel plugins, wxBoxSizer* sizer);
  // void Clear();

  ArrayOfPluginPanel m_PluginItems;
  PluginPanel* m_PluginSelected;
  wxString m_selectedName;
  int m_pluginSpacer;
  std::atomic_flag m_is_loading;  //!<  recursive lock.
};

/** Invokes client browser on plugin info_url when clicked. */
class WebsiteButton : public wxPanel {
public:
  WebsiteButton(wxWindow* parent, const char* url);
  ~WebsiteButton() {};
  void SetURL(std::string url) { m_url = url; }

protected:
  std::string m_url;
};

class PluginPanel : public wxPanel {
  DECLARE_EVENT_TABLE()

  /** An entry in the list of plugins presented by Options | Plugins. */
public:
  /** Construct an entry for a loaded plugin. */
  PluginPanel(wxPanel* parent, wxWindowID id, const wxPoint& pos,
              const wxSize& size, const PlugInData plugin);

  /** Construct an entry reflecting a plugin available in the catalog. */
  PluginPanel(wxPanel* parent, wxWindowID id, const wxPoint& pos,
              const wxSize& size, PluginMetadata plugin);

  /** Construct an entry reflecting a safe-loaded "uninstall-only" item */
  PluginPanel(wxPanel* parent, const std::string& name);

  ~PluginPanel();

  void OnPluginSelected(wxMouseEvent& event);
  void OnPluginSelectedUp(wxMouseEvent& event);
  void DoPluginSelect();

  void SetSelected(bool selected);
  void OnPluginPreferences(wxCommandEvent& event);
  void OnPluginEnableToggle(wxCommandEvent& event);
  void OnPluginAction(wxCommandEvent& event);
  void OnPluginUninstall(wxCommandEvent& event);
  void OnPluginUp(wxCommandEvent& event);
  void OnPluginDown(wxCommandEvent& event);
  void SetEnabled(bool enabled);
  bool GetSelected() { return m_bSelected; }
  const PlugInData* GetPluginPtr() { return &m_plugin; };
  void SetActionLabel(wxString& label);
  ActionVerb GetAction() { return m_action; }
  const PlugInData* GetPlugin() { return &m_plugin; }
  void OnPaint(wxPaintEvent& event);

private:
  PluginListPanel* m_PluginListPanel;
  bool m_bSelected;
  const PlugInData m_plugin;
  wxStaticText* m_pName;
  wxStaticText* m_pVersion;
  wxStaticText* m_pDescription;
  wxBoxSizer* m_pButtons;
  wxStaticBitmap* m_itemStaticBitmap;
  wxStaticBitmap* m_itemStatusIconBitmap;
  wxButton* m_pButtonPreferences;
  wxButton *m_pButtonAction, *m_pButtonUninstall;

  wxCheckBox* m_cbEnable;
  WebsiteButton* m_info_btn;
  ActionVerb m_action;
  int m_penWidthUnselected, m_penWidthSelected;
  bool m_is_safe_panel;
};

//  API 1.11 adds access to S52 Presentation library
//  These are some wrapper conversion utilities

class S52PLIB_Context {
public:
  S52PLIB_Context() {
    bBBObj_valid = false;
    bCS_Added = false;
    bFText_Added = false;
    CSrules = NULL;
    FText = NULL;
    ChildRazRules = NULL;
    MPSRulesList = NULL;
    LUP = NULL;
  };

  ~S52PLIB_Context() {};

  BoundingBox BBObj;  // lat/lon BBox of the rendered object
  bool bBBObj_valid;  // set after the BBObj has been calculated once.

  Rules* CSrules;  // per object conditional symbology
  int bCS_Added;

  S52_TextC* FText;
  int bFText_Added;
  wxRect rText;

  LUPrec* LUP;
  ObjRazRules* ChildRazRules;
  mps_container* MPSRulesList;
};

void CreateCompatibleS57Object(PI_S57Obj* pObj, S57Obj* cobj,
                               chart_context* pctx);
void UpdatePIObjectPlibContext(PI_S57Obj* pObj, S57Obj* cobj);

#endif  // _PLUGINMGR_H_
