/**
*     Helper class for flash to interact with non game-specific code (ie. play a sound)
*/

#ifndef __GAMESWF_HELPER__
#define __GAMESWF_HELPER__

#include "RKSingleton.h"
#include "gameswf/gameswf.h"    //!< gameswf::Point

#include <RKString.h>

struct RKVector;
struct RKVector2;
struct RKRect;

class RKCamera;
class RKRenderLayer;

namespace gameswf
{
  struct FunctionCall;
  class CharacterHandle;
  class FlashFX;

  FlashFX* load(const char* filename, bool background = false);
  void unload(FlashFX* swf);

  void preserveVisibility(const char* filename);
  void pushVisibiliyList();
  void popVisibiliyList();
}

class GameSWFHelper : public RKSingleton < GameSWFHelper >
{
  GameSWFHelper(const GameSWFHelper&);
  GameSWFHelper& operator=(const GameSWFHelper&);

public:
  GameSWFHelper();

  /// \brief Sets a camera's viewport on a custom RenderLayer. Used for 3D UI elements
  /// \param a_hHandle The CharacterHandle who's bounds will be used
  /// \param a_pCamera The camera who's viewport will be set
  /// \param a_pRenderLayer The custom renderLayer a_pCamera will render on
  /// \note Will make the camera orthographic
  /// \precondition a_pCamera cannot be controlled by a CameraController - CameraControllers reset camera aspect to the screen aspect every update.
  static void SetCameraViewportToCharacterHandle(const RKRect &viewport, RKCamera* a_pCamera, RKRenderLayer* a_pRenderLayer);

  /// \brief Set a renderlayer's custom camera to NULL
  /// \param a_pRenderLayer The custom renderLayer which we clear the custom camera
  static void ClearCustomCamera(RKRenderLayer* a_pRenderLayer);

  /// \brief Takes a position in world-space, and returns the position in screen-space of the specified swf
  /// \param a_pFX The swf who's size will determine the screen-space coordinates
  /// \param a_worldPosition The position in the world to be translated into screen-space
  /// \note If you use this to position a CharacterHandle, it will only work if the CharacterHandle has no parent (top level). Nested CharacterHandles will be positioned incorrectly. 
  static gameswf::Point WorldToSWFCoordinates(const gameswf::FlashFX* a_pFX, const RKVector& a_worldPosition);
  
  /// \brief Checks if the target character handle is valid, logging "Invalid handle" and the given message under the "ui" type if not.
  /// \param a_hHandle The handle to check the validity of
  /// \param a_sErrorMessage An optional error message to output additional information if the handle is invalid.
  /// \return Returns the result of CharacterHandle::IsValid()
  static bool IsHandleValidLog(gameswf::CharacterHandle a_hHandle, const char* a_sErrorMessage /* = nullptr*/);

  /// \brief Finds a handle in a flash element, and assigns it the given text if valid
  /// \param a_sHandleName the name of the character handle to find
  /// \param a_sText The text to assign to the handle provided it is valid
  /// \param a_pFX The swf in which to find the handle
  /// \param a_sErrorMessage The message to output if the handle is invalid. NOTE: Calls IsHandleValidLog internally
  /// \return True if the handle is valid and the text was assigned, false if not. 
  static bool SetHandleText(const char* a_sHandleName, const char* a_sText, gameswf::FlashFX* a_pFX, const char* a_sErrorMessage /*= nullptr*/);

  /// \brief Recursively checks parents to see if they are visible. If any are not, returns false
  /// \param a_hHandle The handle to check
  /// \return True if all ancestors are currently visible, false if any are.
  static bool AreAllAncestorsVisible(gameswf::CharacterHandle& a_hHandle);

  /// \brief Sets all the children to (in)visible and (dis/en)abled
  /// \param a_hHandle The parent handle
  /// \param a_bVisible Determines if the visibility to true or false
  static void SetAllChildrenVisible(gameswf::CharacterHandle& a_hHandle, bool a_bVisible);

  /// \brief Reorders a swf with a relative layer distance to another given swf
  /// \param a_pSWFBeingReordered The SWF that will have its order changed
  /// \param a_pRelativeSWF The SWF that a_pSWFBeingReordered will be moved in relation to
  /// \param a_order The layer distance from a_pRelativeSWF that a_pSWFBeingReordered will be moved to. A positive number will move it on top.
  static void SetRelativeSWFOrder(gameswf::FlashFX* a_pSWFBeingReordered, gameswf::FlashFX* a_pRelativeSWF, int a_order);

  /// \brief Checks if the given CharacterHandle is on a frame with the given label name
  /// \param a_hHandle The CharacterHandle to check
  /// \param a_sLabelName The label name to check
  /// \return True if the frame the CharacterHandle is on has a label of the given name.
  static bool IsOnFrame(gameswf::CharacterHandle& a_hHandle, const char* a_sLabelName);

  /// \brief Recurses up the hierarchy to find the world position of a CharacterHandle within the space of its swf
  /// \param a_hHandle The CharacterHandle to find the position of
  /// \note This value differs from CharacterHandle::getWorldPosition - getWorldPosition is affected by additional scale factors
  /// \return The world position relative to the swf's stage
  static gameswf::Point GetHandleWorldPosition(gameswf::CharacterHandle& a_hHandle);

  /// \brief Recurse to the ends of all lower hierarchies, printing the full path of the end nodes
  /// \note Requires RKLog info to be output, with filterType "ui"
  static void PrintLeafNames(gameswf::CharacterHandle& a_hHandle);

  /// \brief Sets a BNE.Text/BNE.TextButton's localization ID in ActionScript
  /// \param a_hHandle The handle of type BNE.Text or BNE.ButtonText to set the localization ID of
  /// \param a_sLocalizationID The localization ID of the target string
  /// \note This will not work with paramaterized strings
  static void SetLocalizationID(gameswf::CharacterHandle& a_hHandle, const char* a_sLocalizationID);

  /// \brief Unregister BNE.Text and BNE.TextButton from automatically updating with the StringPack.
  /// \note This behaviour is only desired when setting localization from code. 
  static void ClearAutomaticLocalization(gameswf::CharacterHandle& a_hHandle);

  /// \brief Get the HTML text for a string with a given font.
  /// \param a_sFontName The font name.
  /// \param a_sText The original text.
  static RKString GetHtmlText(const char* a_sFontName, const char* a_sText);

  /// \brief Sets a button to be enabled at the BNE.Button/BNE.TextButton level - this is an independent disable from the gameswf::CharacterHandle
  /// \param a_hButton The button to set enabled/disabled
  /// \param a_bIsEnabled If the button is to be enabled or disabled
  static void SetButtonEnabled(gameswf::CharacterHandle& a_hButton, bool a_bIsEnabled);

  /// \brief Compares the current frame the element is on, and determines if it falls between the given animation label names
  /// \param a_hElement The MovieClip being tested if it falls between animation labels
  /// \param a_sStartLabelName The name of the label to start checking from (inclusive)
  /// \param a_sEndLabelName The name of the label to finish checking from (exclusive/inclusive dependant on next member)
  /// \param a_bInclusiveOfEndLabel A flag to determine if the end label name should be factored if it falls between animations. Inclusive is likely used if the end label is "animName_end" - a specific identifier that the animation has ended, while exclusive would be used if it's the start of the next animation.
  /// \return True if the MovieClip is currently on a frame between the two labels.
  static bool IsBetweenAnimations(gameswf::CharacterHandle& a_hElement, const char* a_sStartLabelName, const char* a_sEndLabelName, bool a_bInclusiveOfEndLabel = false);

private:
  /// \brief Plays a sound through Soundpack
  static void PlaySound(const gameswf::FunctionCall& a_fn);

  /// \brief Informs SCRIPT of the button that was pressed, the function name, and the parameter
  static void ButtonPressedToScript(const gameswf::FunctionCall& a_fn);

  /// \brief Gets the device width
  static void GetDeviceWidth(const gameswf::FunctionCall& a_fn);

  /// \brief Gets the device height
  static void GetDeviceHeight(const gameswf::FunctionCall& a_fn);

  /// \brief Gets the device aspect ratio
  static void GetDeviceAspect(const gameswf::FunctionCall& a_fn);

  /// \brief Gets if on frame with the specified label
  static void IsOnFrameWithLabel(const gameswf::FunctionCall& a_fn);
};

/// \brief Static functions to get around passing in int 0 values to/from flash. Flash discards a single argument of int 0 (assumed it as no argument), where it is used heavily when referencing button/callback indices, etc.
namespace ASIntArg
{
  static const int From(int a_ASInt)  { return a_ASInt - 1;    }
  static const int To(int a_rawIndex) { return a_rawIndex + 1; }
}

#endif // __GAMESWF_HELPER__
