/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#pragma once

#include "AddonClass.h"
#include "CallbackHandler.h"

#include "threads/Event.h"

/**
 * This class is an interface that can be used to define programming language
 * specific hooks.
 */

class IPlayerCallback;

namespace XBMCAddon
{
  namespace xbmc
  {
    class Monitor;
  }

  class LanguageHook : public AddonClass
  {
  protected:
    inline LanguageHook() = default;

  public:
    ~LanguageHook() override;

    /**
     * If the scripting language needs special handling for calls
     *  that block or are delayed in any other means, this should
     *  be overloaded.
     *
     * In Python when control is passed to a native
     *  method that blocks, you need to allow other threads in
     *  Python to run by using Py_BEGIN_ALLOW_THREADS. This is
     *  the place to put that functionality
     */
    virtual void DelayedCallOpen() { }

    /**
     * If the scripting language needs special handling for calls
     *  that block or are delayed in any other means, this should
     *  be overloaded.
     *
     * In Python when control is passed to a native
     *  method that blocks, you need to allow other threads in
     *  Python to run by using Py_BEGIN_ALLOW_THREADS. When that
     *  delayed method ends you need to restore the Python thread
     *  state using Py_END_ALLOW_THREADS. This is the place to put
     *  that functionality
     */
    virtual void DelayedCallClose() { }

    virtual void MakePendingCalls() {}

    /**
     * For scripting languages that need a global callback handler, this
     *  method should be overloaded to supply one.
     */
    virtual CallbackHandler* GetCallbackHandler() { return NULL; }

    /**
     * This is a callback method that can be overridden to receive a callback
     *  when an AddonClass that has this language hook is on is being constructed.
     *  It is called from the constructor of AddonClass so the implementor
     *  cannot assume the subclasses have been built or that calling a
     *  virtual function on the AddonClass will work as expected.
     */
    virtual void Constructing(AddonClass* beingConstructed) { }

    /**
     * This is a callback method that can be overridden to receive a callback
     *  when an AddonClass that has this language hook is on is being destructed.
     *  It is called from the destructor of AddonClass so the implementor
     *  should assume the subclasses have been torn down and that calling a
     *  virtual function on the AddonClass will not work as expected.
     */
    virtual void Destructing(AddonClass* beingDestructed) { }

    /**
     * This method should be done a different way but since the only other way
     *  I can think to do it requires an InheritableThreadLocal C++ equivalent,
     *  I'm going to defer to this technique for now.
     *
     * Currently (for python) the scripting language has the Addon id injected
     *  into it as a global variable. Therefore the only way to retrieve it is
     *  to use scripting language specific calls. So until I figure out a
     *  better way to do this, this is how I need to retrieve it.
     */
    virtual String GetAddonId() { return emptyString; }
    virtual String GetAddonVersion() { return emptyString; }
    virtual long GetInvokerId() { return -1; }

    virtual void RegisterPlayerCallback(IPlayerCallback* player) = 0;
    virtual void UnregisterPlayerCallback(IPlayerCallback* player) = 0;
    virtual void RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0;
    virtual void UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0;
    virtual bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds) = 0;

    static void SetLanguageHook(LanguageHook* languageHook);
    static LanguageHook* GetLanguageHook();
    static void ClearLanguageHook();
  };

  /**
   * This class can be used to access the language hook's DelayedCallOpen
   *  and DelayedCallClose. It should be used whenever an API method
   *  is written such that it can block for an indefinite amount of time
   *  since certain scripting languages (like Python) need to do extra
   *  work for delayed calls (like free the python locks and handle
   *  callbacks).
   */
  class DelayedCallGuard
  {
    LanguageHook* languageHook;
    bool clearOnExit = false;

  public:
    inline explicit DelayedCallGuard(LanguageHook* languageHook_) : languageHook(languageHook_)
    { if (languageHook) languageHook->DelayedCallOpen(); }

    inline DelayedCallGuard() : languageHook(LanguageHook::GetLanguageHook())
    { if (languageHook) languageHook->DelayedCallOpen(); }

    inline ~DelayedCallGuard()
    {
      if (clearOnExit) LanguageHook::ClearLanguageHook();
      if (languageHook) languageHook->DelayedCallClose();
    }

    inline LanguageHook* getLanguageHook() { return languageHook; }
  };

  class SetLanguageHookGuard
  {
  public:
    inline explicit SetLanguageHookGuard(LanguageHook* languageHook) { LanguageHook::SetLanguageHook(languageHook); }
    inline ~SetLanguageHookGuard() { LanguageHook::ClearLanguageHook(); }
  };

}

