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

  Audacity: A Digital Audio Editor

  Prefs.cpp

  Dominic Mazzoni

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

\file Prefs.cpp
\brief Utility functions for working with our wxConf (gPrefs)


  Audacity uses wxWidgets' wxConfig class to handle preferences.
  See Prefs.h for more information on how it works...

\verbatim
  Note: The info below is very outdated and incomplete

  Preference field specification:
   /
      Version					- Audacity Version that created these prefs
      DefaultOpenPath			- Default directory for NEW file selector
   /FileFormats
      CopyOrEditUncompressedData - Copy data from uncompressed files or
         [ "copy", "edit"]   - edit in place?
      ExportFormat_SF1		   - Format to export PCM data in
                             (this number is a libsndfile1.0 format)
   /SamplingRate
      DefaultProjectSampleRate- New projects will have this rate
         [ 8000, 11025, 16000, 22050, 44100, 48000 ]
   /AudioIO
      PlaybackDevice			- device to use for playback
      RecordingDevice			- device to use for recording
         (these are device names understood by PortAudio)
   /Display
      WaveformColor			- 0xRRGGBB  --since it will be stored in
      ShadowColor				- 			  decimal, it will be somewhat
      SpectrumLowColor		- 			  non-intuitive to edit, but
      SpectrumHighColor		- 			  much easier to parse.
   /Locale
      Language				- two-letter language code for translations

   (*): wxGTK
   (+): wxWin
   ($): wxMac
\endverbatim

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


#include "Audacity.h"
#include "Prefs.h"

#include <wx/defs.h>
#include <wx/app.h>
#include <wx/intl.h>
#include <wx/fileconf.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>

#include "Internat.h"
#include "MemoryX.h"

std::unique_ptr<AudacityPrefs> ugPrefs {};

AudacityPrefs *gPrefs = NULL;
int gMenusDirty = 0;

wxDEFINE_EVENT(EVT_PREFS_UPDATE, wxCommandEvent);

struct PrefsListener::Impl : wxEvtHandler
{
   Impl( PrefsListener &owner );
   ~Impl();
   void OnEvent(wxCommandEvent&);
   PrefsListener &mOwner;
};

PrefsListener::Impl::Impl( PrefsListener &owner )
   : mOwner{ owner }
{
   wxTheApp->Bind(EVT_PREFS_UPDATE, &PrefsListener::Impl::OnEvent, this);
}

PrefsListener::Impl::~Impl()
{
}

PrefsListener::PrefsListener()
   : mpImpl{ std::make_unique<Impl>( *this ) }
{
}

PrefsListener::~PrefsListener()
{
}

void PrefsListener::UpdateSelectedPrefs( int )
{
}

void PrefsListener::Impl::OnEvent( wxCommandEvent &evt )
{
   evt.Skip();
   auto id = evt.GetId();
   if (id <= 0)
      mOwner.UpdatePrefs();
   else
      mOwner.UpdateSelectedPrefs( id );
}

#if 0
// Copy one entry from one wxConfig object to another
static void CopyEntry(wxString path, wxConfigBase *src, wxConfigBase *dst, wxString entry)
{
   switch(src->GetEntryType(entry)) {
   case wxConfigBase::Type_Unknown:
   case wxConfigBase::Type_String: {
      wxString value = src->Read(entry, wxT(""));
      dst->Write(path + entry, value);
      break;
   }
   case wxConfigBase::Type_Boolean: {
      bool value = false;
      src->Read(entry, &value, value);
      dst->Write(path + entry, value);
      break;
   }
   case wxConfigBase::Type_Integer: {
      long value = false;
      src->Read(entry, &value, value);
      dst->Write(path + entry, value);
      break;
   }
   case wxConfigBase::Type_Float: {
      double value = false;
      src->Read(entry, &value, value);
      dst->Write(path + entry, value);
      break;
   }
   }
}


// Recursive routine to copy all groups and entries from one wxConfig object to another
static void CopyEntriesRecursive(wxString path, wxConfigBase *src, wxConfigBase *dst)
{
   wxString entryName;
   long entryIndex;
   bool entryKeepGoing;

   entryKeepGoing = src->GetFirstEntry(entryName, entryIndex);
   while (entryKeepGoing) {
      CopyEntry(path, src, dst, entryName);
      entryKeepGoing = src->GetNextEntry(entryName, entryIndex);
   }

   wxString groupName;
   long groupIndex;
   bool groupKeepGoing;

   groupKeepGoing = src->GetFirstGroup(groupName, groupIndex);
   while (groupKeepGoing) {
      wxString subPath = path+groupName+wxT("/");
      src->SetPath(subPath);
      CopyEntriesRecursive(subPath, src, dst);
      src->SetPath(path);
      groupKeepGoing = src->GetNextGroup(groupName, groupIndex);
   }
}
#endif

AudacityPrefs::AudacityPrefs(const wxString& appName,
               const wxString& vendorName,
               const wxString& localFilename,
               const wxString& globalFilename,
               long style,
               const wxMBConv& conv) :
   wxFileConfig(appName,
               vendorName,
               localFilename,
               globalFilename,
               style,
               conv)
{
}



void InitPreferences( const wxFileName &configFileName )
{
   wxString appName = wxTheApp->GetAppName();

   ugPrefs = std::make_unique<AudacityPrefs>
      (appName, wxEmptyString,
       configFileName.GetFullPath(),
       wxEmptyString, wxCONFIG_USE_LOCAL_FILE);
   gPrefs = ugPrefs.get();

   wxConfigBase::Set(gPrefs);
}

bool CheckWritablePreferences()
{
   return gPrefs->Write("/TEST", true) && gPrefs->Flush();
}

TranslatableString UnwritablePreferencesErrorMessage( const wxFileName &configFileName )
{
   return
     XO("Audacity cannot start because the settings file at %s is not writable.")
        .Format(configFileName.GetFullPath());
}

void FinishPreferences()
{
   if (gPrefs) {
      wxConfigBase::Set(NULL);
      ugPrefs.reset();
      gPrefs = NULL;
   }
}

//////////
EnumValueSymbols::EnumValueSymbols(
   ByColumns_t,
   const TranslatableStrings &msgids,
   wxArrayStringEx internals
)
   : mInternals( std::move( internals ) )
{
   auto size = mInternals.size(), size2 = msgids.size();
   if ( size != size2 ) {
      wxASSERT( false );
      size = std::min( size, size2 );
   }
   reserve( size );
   auto iter1 = mInternals.begin();
   auto iter2 = msgids.begin();
   while( size-- )
      emplace_back( *iter1++, *iter2++ );
}

const TranslatableStrings &EnumValueSymbols::GetMsgids() const
{
   if ( mMsgids.empty() )
      mMsgids = transform_container<TranslatableStrings>( *this,
         std::mem_fn( &EnumValueSymbol::Msgid ) );
   return mMsgids;
}

const wxArrayStringEx &EnumValueSymbols::GetInternals() const
{
   if ( mInternals.empty() )
      mInternals = transform_container<wxArrayStringEx>( *this,
         std::mem_fn( &EnumValueSymbol::Internal ) );
   return mInternals;
}

//////////
const EnumValueSymbol &ChoiceSetting::Default() const
{
   if ( mDefaultSymbol >= 0 && mDefaultSymbol < (long)mSymbols.size() )
      return mSymbols[ mDefaultSymbol ];
   static EnumValueSymbol empty;
   return empty;
}

wxString ChoiceSetting::Read() const
{
   const auto &defaultValue = Default().Internal();
   return ReadWithDefault( defaultValue );
}

wxString ChoiceSetting::ReadWithDefault( const wxString &defaultValue ) const
{
   wxString value;
   if ( !gPrefs->Read(mKey, &value, defaultValue) )
      if (!mMigrated) {
         const_cast<ChoiceSetting*>(this)->Migrate( value );
         mMigrated = true;
      }

   // Remap to default if the string is not known -- this avoids surprises
   // in case we try to interpret config files from future versions
   auto index = Find( value );
   if ( index >= mSymbols.size() )
      value = defaultValue;
   return value;
}

size_t ChoiceSetting::Find( const wxString &value ) const
{
   auto start = GetSymbols().begin();
   return size_t(
      std::find( start, GetSymbols().end(), EnumValueSymbol{ value, {} } )
         - start );
}

void ChoiceSetting::Migrate( wxString &value )
{
   (void)value;// Compiler food
}

bool ChoiceSetting::Write( const wxString &value )
{
   auto index = Find( value );
   if (index >= mSymbols.size())
      return false;

   auto result = gPrefs->Write( mKey, value );
   mMigrated = true;
   return result;
}

EnumSettingBase::EnumSettingBase(
   const wxString &key,
   EnumValueSymbols symbols,
   long defaultSymbol,

   std::vector<int> intValues, // must have same size as symbols
   const wxString &oldKey
)
   : ChoiceSetting{ key, std::move( symbols ), defaultSymbol }
   , mIntValues{ std::move( intValues ) }
   , mOldKey{ oldKey }
{
   auto size = mSymbols.size();
   if( mIntValues.size() != size ) {
      wxASSERT( false );
      mIntValues.resize( size );
   }
}

void ChoiceSetting::SetDefault( long value )
{
   if ( value < (long)mSymbols.size() )
      mDefaultSymbol = value;
   else
      wxASSERT( false );
}

int EnumSettingBase::ReadInt() const
{
   auto index = Find( Read() );

   wxASSERT( index < mIntValues.size() );
   return mIntValues[ index ];
}

int EnumSettingBase::ReadIntWithDefault( int defaultValue ) const
{
   wxString defaultString;
   auto index0 = FindInt( defaultValue );
   if ( index0 < mSymbols.size() )
      defaultString = mSymbols[ index0 ].Internal();
   else
      wxASSERT( false );

   auto index = Find( ReadWithDefault( defaultString ) );

   wxASSERT( index < mSymbols.size() );
   return mIntValues[ index ];
}

size_t EnumSettingBase::FindInt( int code ) const
{
   const auto start = mIntValues.begin();
   return size_t(
      std::find( start, mIntValues.end(), code )
         - start );
}

void EnumSettingBase::Migrate( wxString &value )
{
   int intValue = 0;
   if ( !mOldKey.empty() &&
        gPrefs->Read(mOldKey, &intValue, 0) ) {
      // Make the migration, only once and persistently.
      // Do not DELETE the old key -- let that be read if user downgrades
      // Audacity.  But further changes will be stored only to the NEW key
      // and won't be seen then.
      auto index = (long) FindInt( intValue );
      if ( index >= (long)mSymbols.size() )
         index = mDefaultSymbol;
      if ( index >= 0 && index < (long)mSymbols.size() ) {
         value = mSymbols[index].Internal();
         Write(value);
         gPrefs->Flush();
      }
   }
}

bool EnumSettingBase::WriteInt( int code ) // you flush gPrefs afterward
{
   auto index = FindInt( code );
   if ( index >= mSymbols.size() )
      return false;
   return Write( mSymbols[index].Internal() );
}

wxString WarningDialogKey(const wxString &internalDialogName)
{
   return wxT("/Warnings/") + internalDialogName;
}

ByColumns_t ByColumns{};

#include <set>

namespace {
   using PreferenceInitializers = std::set< PreferenceInitializer* >;
   PreferenceInitializers &allInitializers()
   {
      static PreferenceInitializers theSet;
      return theSet;
   }
}

PreferenceInitializer::PreferenceInitializer()
{
   allInitializers().insert( this );
}

PreferenceInitializer::~PreferenceInitializer()
{
   allInitializers().erase( this );
}

void PreferenceInitializer::ReinitializeAll()
{
   for ( auto pInitializer : allInitializers() )
      (*pInitializer)();
}
