/// QasTools: Desktop toolset for the Linux sound system ALSA.
/// \copyright See COPYING file.

#include "states_db.hpp"
#include "mstate/mixer/state.hpp"
#include "qastools_config.hpp"
#include "qsnd/cards_db.hpp"
#include "qsnd/ctl_address.hpp"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QSaveFile>
#include <QStandardPaths>
#include <iostream>

namespace MState::Mixer
{

States_Db::States_Db ( QSnd::Cards_Db * cards_db_n )
: Utility::Setup ( nullptr )
, _cards_db ( cards_db_n )
, _empty_state ( std::make_shared< Mixer::State > ( this ) )
{
  // Compose configuration file path
  _config_file =
      QStandardPaths::writableLocation ( QStandardPaths::ConfigLocation );
  _config_file += QChar ( '/' );
  _config_file += QLatin1StringView ( PACKAGE_NAME );
  _config_file += QChar ( '/' );
  _config_file += QLatin1StringView ( PROGRAM_NAME );
  _config_file += QLatin1StringView ( "_mixers_settings.json" );
}

States_Db::~States_Db () = default;

void
States_Db::add_state ( const QString & key_n,
                       const std::shared_ptr< Mixer::State > & state_n )
{
  _states.emplace ( key_n, state_n );
  notify_change ();
}

const std::shared_ptr< Mixer::State > &
States_Db::state ( const QSnd::Ctl_Address & addr_n )
{
  if ( !addr_n.is_valid () ) {
    return _empty_state;
  }

  QString key = addr_n.addr_str ();

  // Translate hw:number adresses to use the id string
  if ( addr_n.ctl_name () == QLatin1StringView ( "hw" ) ) {
    if ( addr_n.num_args () == 1 ) {
      auto & arg = addr_n.arg ( 0 );
      if ( arg.name ().isEmpty () ||
           ( arg.name () == QLatin1StringView ( "CARD" ) ) ) {
        bool to_int_ok = false;
        int card_index = arg.value ().toInt ( &to_int_ok );
        if ( to_int_ok ) {
          // Numeric card index
          for ( std::size_t row = 0; row < _cards_db->num_cards (); ++row ) {
            const auto & handle = _cards_db->card ( row );
            if ( handle->index () == card_index ) {
              // Use id string
              key = QStringLiteral ( "hw:" ) + handle->id ();
              break;
            }
          }
        } else {
          // Non numeric card id
          // Remove optional CARD parameter
          key = QStringLiteral ( "hw:" ) + arg.value ();
        }
      }
    }
  }

  // Insert into database on demand
  if ( auto it = _states.find ( key ); it == _states.end () ) {
    add_state ( key, std::make_shared< Mixer::State > ( this ) );
  }

  return _states.find ( key ).value ();
}

QByteArray
States_Db::read_config_file () const
{
  QByteArray res;
  // Read file data
  {
    QFile file ( _config_file );
    if ( file.open ( QIODeviceBase::ReadOnly ) ) {
      res = file.readAll ();
    }
  }
  return res;
}

void
States_Db::read_from_storage ()
{
  QJsonDocument json_doc;
  {
    QByteArray file_data = read_config_file ();
    if ( file_data.isEmpty () ) {
      return;
    }

    QJsonParseError parse_error;
    json_doc = QJsonDocument::fromJson ( file_data, &parse_error );
    if ( parse_error.error != QJsonParseError::NoError ) {
      std::ostringstream oss;
      oss << "Parsing " << _config_file.toStdString () << " failed:\n";
      oss << parse_error.errorString ().toStdString () << "\n";
      std::cerr << oss.str ();
      return;
    }
  }
  if ( !json_doc.isObject () ) {
    return;
  }

  QJsonObject jroot = json_doc.object ();
  for ( auto it = jroot.constBegin (), ite = jroot.constEnd (); it != ite;
        ++it ) {

    const auto & jval = it.value ();
    if ( !jval.isObject () ) {
      continue;
    }

    auto state = std::make_shared< Mixer::State > ( this );
    QJsonObject jstate_obj ( jval.toObject () );

    // Splitter sizes
    {
      QJsonValue jsplitter_val (
          jstate_obj.value ( QLatin1StringView ( "splitter_sizes" ) ) );
      if ( jsplitter_val.isArray () ) {
        QJsonArray jarr ( jsplitter_val.toArray () );
        if ( jarr.size () == 2 ) {
          if ( jarr[ 0 ].isDouble () && jarr[ 1 ].isDouble () ) {
            QList< int > sizes;
            sizes.resize ( 2, 0 );
            sizes[ 0 ] = jarr[ 0 ].toDouble ();
            sizes[ 1 ] = jarr[ 1 ].toDouble ();
            state->set_splitter_sizes ( sizes );
          }
        }
      }
    }

    // Show stream
    {
      QJsonValue jstreams_val (
          jstate_obj.value ( QLatin1StringView ( "show_stream" ) ) );
      if ( jstreams_val.isArray () ) {
        QJsonArray jarr ( jstreams_val.toArray () );
        if ( jarr.size () == 2 ) {
          for ( std::size_t ii = 0; ii < 2; ++ii ) {
            const auto & jval = jarr[ ii ];
            if ( jval.isBool () ) {
              state->set_show_stream ( ii, jval.toBool ( true ) );
            }
          }
        }
      }
    }

    // Sort by stream direction
    {
      QJsonValue jsort_val (
          jstate_obj.value ( QLatin1StringView ( "sort_stream_dir" ) ) );
      if ( jsort_val.isBool () ) {
        state->set_sort_stream_dir ( jsort_val.toBool () );
      }
    }

    // Controls
    {
      auto read_controls = [ &jstate_obj ] (
                               MState::Mixer::Controls & controls_n,
                               QLatin1StringView key_n ) {
        QJsonValue jctl_val ( jstate_obj.value ( key_n ) );
        if ( jctl_val.isObject () ) {
          QJsonObject jctl_obj ( jctl_val.toObject () );

          // Separation
          {
            QJsonValue jsep_val (
                jctl_obj.value ( QLatin1StringView ( "separated" ) ) );
            if ( jsep_val.isArray () ) {
              QJsonArray jarr ( jsep_val.toArray () );
              for ( const auto & jval : jarr ) {
                if ( jval.isString () ) {
                  controls_n.set_proxies_group_separated ( jval.toString (),
                                                           true );
                }
              }
            }
          }

          // Blacklist
          {
            QJsonValue jbl_val (
                jctl_obj.value ( QLatin1StringView ( "blacklist" ) ) );
            if ( jbl_val.isArray () ) {
              QJsonArray jarr ( jbl_val.toArray () );
              for ( const auto & jval : jarr ) {
                if ( jval.isString () ) {
                  controls_n.set_proxies_group_blacklisted ( jval.toString (),
                                                             true );
                }
              }
            }
          }
        }
      };

      // Read controls
      read_controls ( state->sliders (), QLatin1StringView ( "sliders" ) );
      read_controls ( state->switches (), QLatin1StringView ( "switches" ) );
    }

    // Add state
    add_state ( it.key (), state );
  }

  // Update storage change id
  _storage_change_id = change_id ();
}

void
States_Db::write_to_storage ()
{
  // Check if there were changes at all
  if ( !differs_from_storage () ) {
    // std::cout << "States_Db::write_to_storage: No changes\n";
    return;
  }
  // std::cout << "States_Db::write_to_storage: Writing changes\n";

  QJsonObject jroot;

  for ( auto it = _states.constBegin (), ite = _states.constEnd (); it != ite;
        ++it ) {
    const MState::Mixer::State & state = *it.value ();
    QJsonObject jstate;

    // Splitter sizes
    {
      QJsonArray jarr;
      for ( int size : state.splitter_sizes () ) {
        jarr.append ( QJsonValue ( double ( size ) ) );
      }
      jstate[ QLatin1StringView ( "splitter_sizes" ) ] = jarr;
    }

    // Show stream
    {
      QJsonArray jarr;
      for ( std::size_t ii = 0; ii < 2; ++ii ) {
        jarr.append ( QJsonValue ( state.show_stream ( ii ) ) );
      }
      jstate[ QLatin1StringView ( "show_stream" ) ] = jarr;
    }

    // Sort by stream direction
    {
      jstate[ QLatin1StringView ( "sort_stream_dir" ) ] =
          QJsonValue ( state.sort_stream_dir () );
    }

    // Controls
    {
      auto write_controls =
          [ &jstate ] ( const MState::Mixer::Controls & controls_n,
                        QLatin1StringView key_n ) {
            const auto & sep_groups = controls_n.separated_proxies_groups ();
            const auto & blacklist = controls_n.blacklisted_proxies_groups ();
            if ( !sep_groups.isEmpty () || !blacklist.isEmpty () ) {
              QJsonObject jobj;

              // Separation
              if ( !sep_groups.isEmpty () ) {
                QJsonArray jarr;
                for ( const QString & proxy_id_n : sep_groups ) {
                  jarr.append ( QJsonValue ( proxy_id_n ) );
                }
                jobj[ QLatin1StringView ( "separated" ) ] = jarr;
              }

              // Blacklist
              if ( !blacklist.isEmpty () ) {
                QJsonArray jarr;
                for ( const QString & proxy_id_n : blacklist ) {
                  jarr.append ( QJsonValue ( proxy_id_n ) );
                }
                jobj[ QLatin1StringView ( "blacklist" ) ] = jarr;
              }
              jstate[ key_n ] = jobj;
            }
          };

      // Write controls
      write_controls ( state.sliders (), QLatin1StringView ( "sliders" ) );
      write_controls ( state.switches (), QLatin1StringView ( "switches" ) );
    }

    // Insert json object
    jroot[ it.key () ] = jstate;
  }

  // Check if the data changed before writing to the config file
  QByteArray new_data;
  {
    QJsonDocument json_doc ( jroot );
    new_data = json_doc.toJson ();
  }
  bool changed = false;
  {
    QByteArray file_data = read_config_file ();
    changed = ( new_data != file_data );
  }
  if ( changed ) {
    QSaveFile save_file ( _config_file );
    if ( save_file.open ( QIODevice::WriteOnly ) ) {
      save_file.write ( new_data );
      save_file.commit ();
    }
  }

  // Update storage change id
  _storage_change_id = change_id ();
}

} // namespace MState::Mixer
