/*****************************************************************************
 * expert_view.cpp : Detailed preferences overview - view
 *****************************************************************************
 * Copyright (C) 2019-2022 VLC authors and VideoLAN
 *
 * Authors: Lyndon Brown <jnqnfe@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <QString>
#include <QFont>
#include <QGuiApplication>
#include <QClipboard>
#include <QMenu>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QColorDialog>
#include <QVBoxLayout>
#include <QAction>
#include <QContextMenuEvent>

#include "expert_view.hpp"
#include "preferences_widgets.hpp"

#include <vlc_config_cat.h>
#include <vlc_modules.h>

#define COLUMN_COUNT 4
/*********************************************************************
 * The Table
 *********************************************************************/
ExpertPrefsTable::ExpertPrefsTable( QWidget *parent ) :
    QTreeView( parent )
{
    setSelectionBehavior( QAbstractItemView::SelectRows );
    setSelectionMode( QAbstractItemView::SingleSelection );
    setAlternatingRowColors( true );

    setStyleSheet( "QTreeView::item { padding: 9px 0; }" );

    /* edit sub-dialog (reusable) */
    expert_edit = new ExpertPrefsEditDialog( this );

    connect( this, &QAbstractItemView::doubleClicked, this, &ExpertPrefsTable::doubleClicked );

    /* context menu actions */
    reset_action = new QAction( qtr( "&Reset" ), this );
    toggle_action = new QAction( qtr( "&Toggle" ), this );
    modify_action = new QAction( qtr( "&Modify" ), this );
    copy_name_action = new QAction( qtr( "Copy &name" ), this );
    copy_value_action = new QAction( qtr( "Copy &value" ), this );

    connect( reset_action, &QAction::triggered, this, &ExpertPrefsTable::resetItem );
    connect( toggle_action, &QAction::triggered, this, &ExpertPrefsTable::toggleItem );
    connect( modify_action, &QAction::triggered, this, &ExpertPrefsTable::modifyItem );
    connect( copy_name_action, &QAction::triggered, this, &ExpertPrefsTable::copyItemName );
    connect( copy_value_action, &QAction::triggered, this, &ExpertPrefsTable::copyItemValue );
}

void ExpertPrefsTable::applyAll()
{
    model()->submit();
}

/* apply filter on tree */
void ExpertPrefsTable::filter( const QString &text, bool modified_only )
{
    bool text_nonempty = !text.isEmpty();

    ExpertPrefsTableModel *model = myModel();
    for( int i = 0 ; i < model->rowCount(); i++ )
    {
        ExpertPrefsTableItem *item = model->itemAt( i );
        bool hide = ( ( modified_only && item->matchesDefault() ) ||
                      ( text_nonempty && !item->contains( text, Qt::CaseInsensitive ) ) );
        setRowHidden( i, QModelIndex(), hide );
    }
}

#ifndef QT_NO_CONTEXTMENU
void ExpertPrefsTable::contextMenuEvent( QContextMenuEvent *event )
{
    QModelIndex index = currentIndex();
    if( !index.isValid() || isRowHidden( index.row(), QModelIndex() ) )
        return;
    /* Avoid menu from right-click on empty space after last item */
    if( event->reason() == QContextMenuEvent::Mouse &&
        !indexAt( viewport()->mapFromGlobal( event->globalPos() ) ).isValid() )
        return;

    ExpertPrefsTableItem *item = myModel()->itemAt( index );

    QMenu *menu = new QMenu(this);
    menu->setAttribute(Qt::WA_DeleteOnClose);

    if( CONFIG_CLASS( item->getType() ) == CONFIG_ITEM_BOOL )
        menu->addAction( toggle_action );
    else
        menu->addAction( modify_action );
    menu->addSeparator();
    menu->addAction( copy_name_action );
    menu->addAction( copy_value_action );
    copy_value_action->setEnabled( item->getType() != CONFIG_ITEM_PASSWORD );
    menu->addSeparator();
    menu->addAction( reset_action );
    reset_action->setEnabled( !item->matchesDefault() );

    menu->popup( event->globalPos() );
}
#endif // QT_NO_CONTEXTMENU

void ExpertPrefsTable::resetItem()
{
    QModelIndex index = currentIndex();
    if( !index.isValid() )
        return;
    myModel()->setItemToDefault( index );
}

void ExpertPrefsTable::toggleItem()
{
    toggleItemPrivate( currentIndex() );
}

/* this obviously only applies to boolean options! */
void ExpertPrefsTable::toggleItemPrivate( const QModelIndex &index )
{
    if( !index.isValid() )
        return;
    myModel()->toggleBoolean( index );
}

void ExpertPrefsTable::modifyItem()
{
    QModelIndex index = currentIndex();
    if( !index.isValid() )
        return;
    modifyItemPrivate( index );
}

void ExpertPrefsTable::modifyItemPrivate( const QModelIndex &index )
{
    ExpertPrefsTableItem *item = myModel()->itemAt( index );
    module_config_t *cfg_item = item->getConfig();
    /* For colour items it's much cleaner here to directly show a `QColorDialog`
       than provide indirect access to one via an `ExpertPrefsEditDialog` with a
       `ColorConfigControl`. */
    if( item->getType() == CONFIG_ITEM_RGB )
    {
        QColor color = QColorDialog::getColor( QColor( cfg_item->value.i ) );
        if( color.isValid() )
        {
            cfg_item->value.i = (color.red() << 16) + (color.green() << 8) + color.blue();
            item->updateMatchesDefault();
            item->updateValueDisplayString();
            myModel()->notifyUpdatedRow( index.row() );
        }
    }
    else
    {
        ConfigControl *control = ConfigControl::createControl( cfg_item, nullptr );
        expert_edit->setControl( control, item );
        expert_edit->exec();
    }
}

void ExpertPrefsTable::copyItemName()
{
    QModelIndex index = currentIndex();
    if( !index.isValid() )
        return;

    QClipboard *clipboard = QGuiApplication::clipboard();

    QModelIndex name_index = myModel()->index( index.row(), ExpertPrefsTableModel::NameField );
    clipboard->setText( name_index.data( Qt::DisplayRole ).toString() );
}

void ExpertPrefsTable::copyItemValue()
{
    QModelIndex index = currentIndex();
    if( !index.isValid() )
        return;

    QClipboard *clipboard = QGuiApplication::clipboard();
    clipboard->setText( index.data( ExpertPrefsTableModel::CopyValueRole ).toString() );
}

void ExpertPrefsTable::doubleClicked( const QModelIndex &index )
{
    if( index.data( ExpertPrefsTableModel::TypeClassRole ).toInt() == CONFIG_ITEM_BOOL )
    {
        toggleItemPrivate( index );
        myModel()->notifyUpdatedRow( index.row() );
    }
    else
        modifyItemPrivate( index );
}

/*********************************************************************
 * The Edit Dialog
 *********************************************************************/
ExpertPrefsEditDialog::ExpertPrefsEditDialog( ExpertPrefsTable *_table ) :
    QDialog( _table ), table( _table )
{
    table_item = nullptr;
    control = nullptr;
    control_widget = nullptr;

    setWindowTitle( qtr( "Set option value" ) );
    setWindowRole( "vlc-preferences" );
    setWindowModality( Qt::WindowModal );

    setMinimumSize( 380, 110 );

    layout = new QVBoxLayout( this );

    QDialogButtonBox *buttonBox = new QDialogButtonBox();
    QPushButton *ok = new QPushButton( qtr( "&Ok" ) );
    QPushButton *cancel = new QPushButton( qtr( "&Cancel" ) );
    buttonBox->addButton( ok, QDialogButtonBox::AcceptRole );
    buttonBox->addButton( cancel, QDialogButtonBox::RejectRole );
    layout->addWidget( buttonBox );

    connect( buttonBox, &QDialogButtonBox::accepted, this, &ExpertPrefsEditDialog::accept );
    connect( buttonBox, &QDialogButtonBox::rejected, this, &ExpertPrefsEditDialog::reject );

    setLayout( layout );
}

void ExpertPrefsEditDialog::setControl( ConfigControl *control_, ExpertPrefsTableItem *table_item_ )
{
    table_item = table_item_;
    control = control_;
    control_widget = new QWidget( this );
    control_widget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
    QVBoxLayout *control_layout = new QVBoxLayout( control_widget );
    control->insertInto( control_layout );
    layout->insertWidget( 0, control_widget );
}

void ExpertPrefsEditDialog::clearControl()
{
    delete control;
    delete control_widget;
    control = nullptr;
    control_widget = nullptr;
    table_item = nullptr;
}

void ExpertPrefsEditDialog::accept()
{
    control->storeValue();
    table_item->updateMatchesDefault();
    table_item->updateValueDisplayString();
    clearControl();
    QDialog::accept();
}

void ExpertPrefsEditDialog::reject()
{
    clearControl();
    QDialog::reject();
}
