/*!********************************************************************

  Audacity: A Digital Audio Editor

  @file JournalWindowPaths.cpp

  Paul Licameli

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

#include "JournalEvents.h"
#include "JournalOutput.h"
#include "JournalRegistry.h"
#include "JournalWindowPaths.h"

#include <map>
#include <mutex>
#include <optional>
#include <unordered_map>

#include <wx/event.h>
#include <wx/eventfilter.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/tglbtn.h>
#include <wx/valgen.h>
#include <wx/window.h>

#include <lib-exceptions/TenacityException.h>
#include <lib-strings/Identifier.h>
#include <lib-strings/wxArrayStringEx.h>

namespace Journal {

namespace Events {

namespace {

struct Type;
using Types = std::vector< Type >;

// The list of event types to intercept and record.  Its construction must be
// delayed until wxWidgets has initialized and chosen the integer values of
// run-time event types.
static const Types &TypeCatalog();

// Events need to be recorded in the journal, but the numbers associated with
// event types by wxWidgets are chosen dynamically and may not be the same
// across runs or platforms.  We need an invariant name for each event type
// of interest, which also makes the journal more legible than if we wrote mere
// numbers.
using Code = Identifier;

// Lookup into the event type catalog during recording
using ByTypeMap = std::map< wxEventType, const Type& >;
static const ByTypeMap &ByType();

// Lookup into the event type catalog during playback
using ByCodeMap = std::unordered_map< Code, const Type & >;
static const ByCodeMap &ByCode();

// This sub-object populates the journal system's dictionary for playback
struct RegisteredEventType : RegisteredCommand {
   static bool DispatchEvent( wxArrayStringEx fields );
   explicit RegisteredEventType( Code code )
   : RegisteredCommand{ code.GET(), DispatchEvent }
   {}
};

/*!
An entry in a catalog describing the types of events that are intercepted
and recorded, and simulated when playing back.
*/
struct Type : RegisteredEventType {

   // Function that returns a list of parameters that, with the event type,
   // are sufficient to record an event to the journal and recreate it on
   // playback; or a nullopt for failure
   using Serializer =
      std::function< std::optional<wxArrayStringEx>( const wxEvent& ) >;

   // Function that recreates an event at playback; or a null for failure
   using Deserializer = std::function< std::unique_ptr<wxEvent>(
      wxEventType, const wxArrayStringEx& ) >;

   // Helper to keep casts out of the supplied function's definition
   // Tag is the subtype of wxEvent expected by fn
   template< typename Tag, typename SerialFn >
   Serializer makeSerializer( SerialFn fn )
   {
      // Return an adaptor taking wxEvent
      return [fn = std::move( fn )]( const wxEvent &e ){
         return fn(dynamic_cast<const Tag&>(e));
      };
   }

   // Helper to check consistency
   // Tag is a subtype of wxEvent (such as wxCommandEvent), and fn defines
   // a deserializer dependent on a run-time wxEvent type (such as one of
   // the many event types implemented by wxCommandEvent)
   template< typename Tag, typename Fn >
   Deserializer checkDeserializer( wxEventTypeTag<Tag> type, Fn fn )
   {
      // Check consistency of the deduced template parameters
      // The deserializer should produce unique pointer to Tag
      using DeserializerResult =
         decltype( *fn( wxEVT_NULL, wxArrayStringEx{} ) );
      static_assert( std::is_same_v< Tag&, DeserializerResult >,
         "deserializer produces the wrong type" );

      return std::move( fn );
   }

   // Type-erasing constructor applies compile-time type consistency checks to
   // the functions, which can be supplied as lambdas using a subtype of wxEvent
   template<
      typename Tag, // such as wxCommandEvent
      typename SerialFn,
      typename DeserialFn
   >
   Type(
      wxEventTypeTag<Tag> type, /*!< such as wxEVT_BUTTON
         Tag is deduced at compile time, the int value is fixed at runtime
      */
      const Code &code, SerialFn serialFn, DeserialFn deserialFn )
      : RegisteredEventType{ code }
      , type{ type }
      , code{ code }
      , serializer{ makeSerializer<Tag>( std::move( serialFn ) ) }
      , deserializer{ checkDeserializer( type, std::move( deserialFn ) ) }
   {
   }

   // Data members
   wxEventType type; //!< Just an int!
   Code code; //!< Persistent cross-platform name corresponding to the int
   Serializer serializer;
   Deserializer deserializer;
};

bool RegisteredEventType::DispatchEvent( wxArrayStringEx fields )
{
   bool handled = false;
   if ( !fields.empty() ) {
      auto &catalog = ByCode();
      auto first = fields.begin();
      if (auto iter = catalog.find( *first ); iter != catalog.end()) {
         auto &type = iter->second;
         fields.erase( first );
         auto pEvent = type.deserializer( type.type, fields );
         if ( pEvent ) {
            // So far only dispatching command events.  Maybe other
            // methods of dispatch will be appropriate for other types.
            if ( auto pHandler = dynamic_cast<wxEvtHandler*>(
               pEvent->GetEventObject() )
            ) {
               if (auto pWindow = dynamic_cast<wxWindow *>(pHandler))
                  // Allow for the pushing and popping of event handlers
                  // on windows
                  pHandler = pWindow->GetEventHandler();
               pHandler->SafelyProcessEvent( *pEvent );
               handled = true;
            }
         }
      }
   }
   return handled;
}

wxString WindowEventName( const wxEvent &event )
{
   wxString windowName;
   if ( auto pWindow = dynamic_cast< wxWindow * >( event.GetEventObject() ) )
      windowName = WindowPaths::FindPath( *pWindow ).GET();
   return windowName;
}

std::optional<wxArrayStringEx> WindowEventSerialization( const wxEvent &event )
{
   std::optional< wxArrayStringEx > result;
   if ( auto windowName = WindowEventName( event ); !windowName.empty() )
      result.emplace( wxArrayStringEx{ windowName } );
   return result;
}

template<typename Event = wxCommandEvent, typename EventType >
static Type NullaryCommandType( EventType type, const wxString &code ){
   const auto deserialize =
   []( wxEventType evtType, const wxArrayStringEx &components) {
      std::unique_ptr< wxCommandEvent > result;
      if ( components.size() == 1 ) {
         if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
            result = std::make_unique< wxCommandEvent >(
               evtType, pWindow->GetId() );
            result->SetEventObject( pWindow );
         }
      }
      return result;
   };

   return Type{ type, code, WindowEventSerialization, deserialize };
};

template<typename Event = wxCommandEvent, typename EventType >
static Type BooleanCommandType( EventType type, const wxString &code ){
   // Writes a window identifier to the journal and a bool
   const auto serialize =
   []( const Event &event ) {
      auto result = WindowEventSerialization( event );
      if ( result )
         result->push_back(
            wxString::Format( L"%d", event.GetInt() ? 1 : 0 ) );
      return result;
   };

   const auto deserialize =
   []( wxEventType type, const wxArrayStringEx &components ) {
      std::unique_ptr< Event > result;
      int value;
      if ( components.size() == 2 ) {
         if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
            if ( long longValue; components[1].ToLong( &longValue ) ) {
               bool value = (longValue != 0);
               result = std::make_unique< Event >(
                  type, pWindow->GetId() );
               result->SetEventObject( pWindow );
               result->SetInt( value );
               // Also change the state of the control before the event is
               // processed.  This class handles the most common control
               // types.
               wxGenericValidator validator( &value );
               validator.SetWindow( pWindow );
               validator.TransferToWindow();
            }
         }
      }
      return result;
   };

   return Type{ type, code, serialize, deserialize };
};

template<typename Event = wxCommandEvent, typename EventType >
static Type NumericalCommandType( EventType type, const wxString &code ){
   // Writes a window identifier to the journal and an int
   const auto serialize =
   []( const Event &event ) {
      auto result = WindowEventSerialization( event );
      if ( result )
         result->push_back( wxString::Format( L"%d", event.GetInt() ) );
      return result;
   };

   const auto deserialize =
   []( wxEventType type, const wxArrayStringEx &components ) {
      std::unique_ptr< Event > result;
      int value;
      if ( components.size() == 2 ) {
         if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
            if ( long longValue;
                components[1].ToLong( &longValue ) &&
                longValue == (value = static_cast<int>(longValue) )
            ) {
               result = std::make_unique< Event >(
                  type, pWindow->GetId() );
               result->SetEventObject( pWindow );
               result->SetInt( value );
               // Also change the state of the control before the event is
               // processed.  This class handles the most common control
               // types.
               wxGenericValidator validator( &value );
               validator.SetWindow( pWindow );
               validator.TransferToWindow();
            }
         }
      }
      return result;
   };

   return Type{ type, code, serialize, deserialize };
};

template<typename Event = wxCommandEvent, typename EventType >
static Type TextualCommandType( EventType type, const wxString &code ){
   // Writes a window identifier to the journal and a string
   const auto serialize =
   []( const Event &event ) {
      auto result = WindowEventSerialization( event );
      if ( result )
         result->push_back( event.GetString() );
      return result;
   };

   const auto deserialize =
   []( wxEventType type, const wxArrayStringEx &components ) {
      std::unique_ptr< Event > result;
      if ( components.size() == 2 ) {
         if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
            auto value = components[1];
            result = std::make_unique< Event >(
               type, pWindow->GetId() );
            result->SetEventObject( pWindow );
            result->SetString( value );
            // Also change the state of the control before the event is
            // processed.
            if ( auto pCtrl = dynamic_cast<wxTextEntry*>( pWindow ) ) {
               // Generic validator calls the SetValue() member function,
               // which generates an event for this type of control, but we
               // don't want that.  So use ChangeValue() instead
               pCtrl->ChangeValue( value );
            }
            else {
               // This class handles the most common control types.
               wxGenericValidator validator( &value );
               validator.SetWindow( pWindow );
               validator.TransferToWindow();
            }
         }
      }
      return result;
   };

   return Type{ type, code, serialize, deserialize };
};

const Types &TypeCatalog()
{
   static Types result {
      NullaryCommandType( wxEVT_BUTTON, "Press" ),
      NullaryCommandType( wxEVT_COMBOBOX_DROPDOWN, "DropDown" ),
      NullaryCommandType( wxEVT_COMBOBOX_CLOSEUP, "CloseUp" ),

      BooleanCommandType( wxEVT_CHECKBOX, "Check" ),
      BooleanCommandType( wxEVT_RADIOBUTTON, "Radio" ),
      BooleanCommandType( wxEVT_RADIOBOX, "RadioBox" ),
      BooleanCommandType( wxEVT_TOGGLEBUTTON, "Toggle" ),

      NumericalCommandType( wxEVT_CHOICE, "Choose" ),
      NumericalCommandType( wxEVT_SLIDER, "Slide" ),
      NumericalCommandType<wxSpinEvent>( wxEVT_SPINCTRL, "Spin" ),
      NumericalCommandType<wxSpinEvent>( wxEVT_SPIN_DOWN, "SpinUp" ),
      NumericalCommandType<wxSpinEvent>( wxEVT_SPIN_UP, "SpinDown" ),

      TextualCommandType( wxEVT_COMBOBOX, "Combo" ),
      TextualCommandType( wxEVT_TEXT, "Text" ),
      TextualCommandType( wxEVT_TEXT_ENTER, "FullText" ),
   };
   return result;
}

static const ByTypeMap &ByType()
{
   static std::once_flag flag;
   static ByTypeMap result;
   std::call_once( flag, []{
      for ( const auto &type : TypeCatalog() )
         result.emplace( type.type, type );
   } );
   return result;
}

static const ByCodeMap &ByCode()
{
   static std::once_flag flag;
   static ByCodeMap result;
   std::call_once( flag, []{
      for ( const auto &type : TypeCatalog() )
         result.emplace( type.code, type );
   } );
   return result;
}

//! Singleton object listens to global wxEvent stream
struct Watcher : public wxEventFilter
{
   Watcher()
   {
      wxEvtHandler::AddFilter( this );
   }

   ~Watcher()
   {
      wxEvtHandler::RemoveFilter( this );
   }

   int FilterEvent( wxEvent &event ) override
   {
      if (!mWatching)
         // Previously encountered error stopped recording of any more events
         return Event_Skip;

      static const auto &catalog = Events::ByType();
      const auto type = event.GetEventType();
      if (const auto iter = catalog.find(type); iter != catalog.end()) {
         // Try to write a representation to the journal
         const auto &info = iter->second;
         auto pStrings = info.serializer(event);
         if (!pStrings) {
            // After one event of one of the interesting types fails to record,
            // don't try again
            mWatching = false;
            throw SimpleMessageBoxException( ExceptionType::BadUserAction,
               XO("Journal recording failed"));
         }
         else {
            pStrings->insert(pStrings->begin(), info.code.GET());
            Journal::Output(*pStrings);
         }
      }
      else
         // Just ignore this un-catalogued event type
         ;

      return Event_Skip;
   }

   bool mWatching{ true };
};

}

void Initialize()
{
   (void) TypeCatalog();
}

void Watch()
{
   static Watcher instance;
}

}

}
