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

#include "alsa_config_model.hpp"
#include <QFont>
#include <QString>
#include <QStringList>
#include <iostream>
#include <sstream>

namespace QSnd
{

Alsa_Config_Model::Alsa_Config_Model ()
{
  reload ();
}

Alsa_Config_Model::~Alsa_Config_Model ()
{
  clear_config ();
}

void
Alsa_Config_Model::clear_config ()
{
  const int rows ( rowCount () );
  if ( rows > 0 ) {
    beginRemoveRows ( QModelIndex (), 0, rows - 1 );
  }

  stree ().reset ();
  if ( _snd_cfg_root != nullptr ) {
    snd_config_delete ( _snd_cfg_root );
    _snd_cfg_root = nullptr;
  }

  if ( rows > 0 ) {
    endRemoveRows ();
  }
}

int
Alsa_Config_Model::load_config ()
{
  int err;

  {
    snd_config_update_t * snd_cfg_upd = nullptr;
    err = snd_config_update_r ( &_snd_cfg_root, &snd_cfg_upd, 0 );
    if ( snd_cfg_upd != 0 ) {
      snd_config_update_free ( snd_cfg_upd );
      snd_cfg_upd = 0;
    }
  }

  if ( ( err < 0 ) || ( _snd_cfg_root == 0 ) ) {
    {
      std::ostringstream msg;
      msg << "[EE] Alsa configuration reading failed" << std::endl;
      std::cerr << msg.str ();
    }
    QSnd::print_alsa_error ( "snd_config_update_r()", err );
  } else {
    add_children_recursively ( 0, _snd_cfg_root );

    const int rows = rowCount ();
    if ( rows > 0 ) {
      beginInsertRows ( QModelIndex (), 0, rows - 1 );
      endInsertRows ();
    }
  }

  return err;
}

int
Alsa_Config_Model::reload ()
{
  int res ( 0 );
  beginResetModel ();

  clear_config ();
  res = load_config ();

  endResetModel ();
  return res;
}

int
Alsa_Config_Model::add_children_recursively ( std::size_t node_idx_n,
                                              snd_config_t * cfg_n )
{
  if ( ( node_idx_n >= stree ().size () ) || ( cfg_n == nullptr ) ) {
    return -1;
  }

  const std::size_t num_children = cfg_count_children ( cfg_n );
  if ( num_children > 0 ) {
    auto child_range = stree ().append_children ( node_idx_n, num_children );
    snd_config_iterator_t iter = snd_config_iterator_first ( cfg_n );
    snd_config_iterator_t iter_end = snd_config_iterator_end ( cfg_n );
    for ( std::size_t child_idx = child_range[ 0 ];
          ( iter != iter_end ) && ( child_idx != child_range[ 1 ] );
          ++child_idx ) {
      snd_config_t * cfg_child = snd_config_iterator_entry ( iter );
      add_children_recursively ( child_idx, cfg_child );
      iter = snd_config_iterator_next ( iter );
    }
  }
  return 0;
}

snd_config_t *
Alsa_Config_Model::cfg_struct ( const Node * node_n ) const
{
  snd_config_t * res = nullptr;
  {
    std::vector< std::size_t > rline;
    stree ().rootline ( node_n, rline );
    res = _snd_cfg_root;
    if ( !rline.empty () ) {
      // Reverse iterate
      for ( auto it = rline.crbegin (), ite = rline.crend (); it != ite;
            ++it ) {
        res = cfg_child ( res, *it );
      }
    }
  }
  return res;
}

snd_config_t *
Alsa_Config_Model::cfg_struct ( const QModelIndex & index_n ) const
{
  snd_config_t * res = nullptr;
  const Node * node = get_node ( index_n );
  if ( node != nullptr ) {
    res = cfg_struct ( node );
  }
  return res;
}

std::size_t
Alsa_Config_Model::cfg_count_children ( snd_config_t * cfg_n ) const
{
  std::size_t count = 0;
  if ( cfg_n != nullptr ) {
    if ( snd_config_get_type ( cfg_n ) == SND_CONFIG_TYPE_COMPOUND ) {
      snd_config_iterator_t iter;
      snd_config_iterator_t iter_end;
      iter = snd_config_iterator_first ( cfg_n );
      iter_end = snd_config_iterator_end ( cfg_n );
      while ( iter != iter_end ) {
        ++count;
        iter = snd_config_iterator_next ( iter );
      }
    }
  }
  return count;
}

snd_config_t *
Alsa_Config_Model::cfg_child ( snd_config_t * cfg_n, std::size_t index_n ) const
{
  if ( cfg_n != nullptr ) {
    if ( snd_config_get_type ( cfg_n ) == SND_CONFIG_TYPE_COMPOUND ) {
      snd_config_iterator_t iter;
      snd_config_iterator_t iter_end;
      iter = snd_config_iterator_first ( cfg_n );
      iter_end = snd_config_iterator_end ( cfg_n );
      std::size_t count = 0;
      while ( iter != iter_end ) {
        if ( count == index_n ) {
          return snd_config_iterator_entry ( iter );
        }
        iter = snd_config_iterator_next ( iter );
        ++count;
      }
    }
  }
  return nullptr;
}

QString
Alsa_Config_Model::cfg_id_string ( snd_config_t * cfg_n ) const
{
  const char * char_ptr = nullptr;
  int err = snd_config_get_id ( cfg_n, &char_ptr );
  if ( ( err == 0 ) && ( char_ptr != nullptr ) ) {
    return QString::fromUtf8 ( char_ptr );
  }
  return QString ();
}

QString
Alsa_Config_Model::cfg_value_string ( snd_config_t * cfg_n ) const
{
  QString res;
  char * char_ptr = nullptr;
  int err = snd_config_get_ascii ( cfg_n, &char_ptr );
  if ( ( err == 0 ) && ( char_ptr != nullptr ) ) {
    res = QString::fromUtf8 ( char_ptr );
    free ( char_ptr );
  }
  return res;
}

QVariant
Alsa_Config_Model::headerData ( int section,
                                Qt::Orientation orientation,
                                int role ) const
{
  if ( orientation == Qt::Horizontal ) {
    if ( role == Qt::DisplayRole ) {
      if ( section == 0 ) {
        return tr ( "Key" );
      } else if ( section == 1 ) {
        return tr ( "Value" );
      }
    }
  }
  return QVariant ();
}

Qt::ItemFlags
Alsa_Config_Model::flags ( const QModelIndex & ) const
{
  Qt::ItemFlags ff ( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
  return ff;
}

QVariant
Alsa_Config_Model::data ( const QModelIndex & index_n, int role_n ) const
{
  QVariant res;
  snd_config_t * cfg = cfg_struct ( index_n );
  if ( cfg != nullptr ) {
    if ( role_n == Qt::DisplayRole ) {
      if ( index_n.column () == 0 ) {
        res = cfg_id_string ( cfg );
      } else if ( index_n.column () == 1 ) {
        res = cfg_value_string ( cfg );
      }
    }
  }
  return res;
}

QString
Alsa_Config_Model::index_address_str ( const QModelIndex & index_n )
{
  QString res;

  QModelIndex idx ( index_n );
  while ( idx.isValid () ) {
    if ( !res.isEmpty () ) {
      res.prepend ( "." );
    }
    res.prepend ( data ( idx, Qt::DisplayRole ).toString () );
    idx = idx.parent ();
  }

  return res;
}

QModelIndex
Alsa_Config_Model::index_from_address ( const QString & addr_n )
{
  QModelIndex res;

  const QStringList lst ( addr_n.split ( "." ) );
  QModelIndex idx_base;
  for ( QStringList::size_type depth = 0; depth < lst.size (); ++depth ) {
    std::size_t num_rows = rowCount ( idx_base );
    for ( std::size_t row = 0; row < num_rows; ++row ) {
      const QModelIndex idx = index ( row, 0, idx_base );
      if ( data ( idx, Qt::DisplayRole ).toString () == lst[ depth ] ) {
        if ( depth == ( lst.size () - 1 ) ) {
          res = idx;
        } else {
          idx_base = idx;
        }
        break;
      }
    }
  }

  return res;
}

} // namespace QSnd
