/***************************************************************************
    qgssourcefieldsproperties.cpp
    ---------------------
    begin                : July 2017
    copyright            : (C) 2017 by David Signer
    email                : david at opengis dot ch

 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "qgssourcefieldsproperties.h"

QgsSourceFieldsProperties::QgsSourceFieldsProperties( QgsVectorLayer *layer, QWidget *parent )
  : QWidget( parent )
  , mLayer( layer )
{
  if ( !layer )
    return;

  setupUi( this );
  layout()->setContentsMargins( 0, 0,0 ,0 );
  layout()->setMargin( 0 );

  //button appearance
  mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
  mDeleteAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
  mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
  mCalculateFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );

  //button signals
  connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::toggleEditing );
  connect( mAddAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::addAttributeClicked );
  connect( mDeleteAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::deleteAttributeClicked );
  connect( mCalculateFieldButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::calculateFieldClicked );

  //slots
  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsSourceFieldsProperties::editingToggled );
  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsSourceFieldsProperties::editingToggled );
  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsSourceFieldsProperties::attributeAdded );
  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsSourceFieldsProperties::attributeDeleted );

  //field list appearance
  mFieldsList->setColumnCount( AttrColCount );
  mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
  mFieldsList->setDragDropMode( QAbstractItemView::DragOnly );
  mFieldsList->setHorizontalHeaderItem( AttrIdCol, new QTableWidgetItem( tr( "Id" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrNameCol, new QTableWidgetItem( tr( "Name" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrTypeCol, new QTableWidgetItem( tr( "Type" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrTypeNameCol, new QTableWidgetItem( tr( "Type name" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrLengthCol, new QTableWidgetItem( tr( "Length" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrPrecCol, new QTableWidgetItem( tr( "Precision" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrCommentCol, new QTableWidgetItem( tr( "Comment" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrWMSCol, new QTableWidgetItem( QStringLiteral( "WMS" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrWFSCol, new QTableWidgetItem( QStringLiteral( "WFS" ) ) );
  mFieldsList->setHorizontalHeaderItem( AttrAliasCol, new QTableWidgetItem( tr( "Alias" ) ) );

  mFieldsList->setSortingEnabled( true );
  mFieldsList->sortByColumn( 0, Qt::AscendingOrder );
  mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
  mFieldsList->setSelectionMode( QAbstractItemView::ExtendedSelection );
  mFieldsList->verticalHeader()->hide();

  //load buttons and field list
  updateButtons();
}


QgsSourceFieldsProperties::~QgsSourceFieldsProperties()
{

}

void QgsSourceFieldsProperties::init()
{
  loadRows();
}

void QgsSourceFieldsProperties::loadRows()
{
  disconnect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
  const QgsFields &fields = mLayer->fields();

  mIndexedWidgets.clear();
  mFieldsList->setRowCount( 0 );

  for ( int i = 0; i < fields.count(); ++i )
    attributeAdded( i );

  mFieldsList->resizeColumnsToContents();
  connect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
  updateFieldRenamingStatus();
}

void QgsSourceFieldsProperties::updateFieldRenamingStatus()
{
  bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) && !mLayer->readOnly();

  for ( int row = 0; row < mFieldsList->rowCount(); ++row )
  {
    if ( canRenameFields )
      mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
    else
      mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
  }
}

void QgsSourceFieldsProperties::updateExpression()
{
  QToolButton *btn = qobject_cast<QToolButton *>( sender() );
  Q_ASSERT( btn );

  int index = btn->property( "Index" ).toInt();

  const QString exp = mLayer->expressionField( index );

  QgsExpressionContext context;
  context << QgsExpressionContextUtils::globalScope()
          << QgsExpressionContextUtils::projectScope( QgsProject::instance() );

  QgsExpressionBuilderDialog dlg( mLayer, exp, nullptr, QStringLiteral( "generic" ), context );

  if ( dlg.exec() )
  {
    mLayer->updateExpressionField( index, dlg.expressionText() );
    loadRows();
  }
}

void QgsSourceFieldsProperties::attributeAdded( int idx )
{
  bool sorted = mFieldsList->isSortingEnabled();
  if ( sorted )
    mFieldsList->setSortingEnabled( false );

  const QgsFields &fields = mLayer->fields();
  int row = mFieldsList->rowCount();
  mFieldsList->insertRow( row );
  setRow( row, idx, fields.at( idx ) );
  mFieldsList->setCurrentCell( row, idx );

  //in case there are rows following, there is increased the id to the correct ones
  for ( int i = idx + 1; i < mIndexedWidgets.count(); i++ )
    mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );

  if ( sorted )
    mFieldsList->setSortingEnabled( true );

  for ( int i = 0; i < mFieldsList->columnCount(); i++ )
  {
    switch ( mLayer->fields().fieldOrigin( idx ) )
    {
      case QgsFields::OriginExpression:
        if ( i == 7 ) continue;
        mFieldsList->item( row, i )->setBackgroundColor( QColor( 200, 200, 255 ) );
        break;

      case QgsFields::OriginJoin:
        mFieldsList->item( row, i )->setBackgroundColor( QColor( 200, 255, 200 ) );
        break;

      default:
        mFieldsList->item( row, i )->setBackgroundColor( QColor( 255, 255, 200 ) );
        break;
    }
  }
}


void QgsSourceFieldsProperties::attributeDeleted( int idx )
{
  mFieldsList->removeRow( mIndexedWidgets.at( idx )->row() );
  mIndexedWidgets.removeAt( idx );
  for ( int i = idx; i < mIndexedWidgets.count(); i++ )
  {
    mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
  }
}

void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field )
{
  QTableWidgetItem *dataItem = new QTableWidgetItem();
  dataItem->setData( Qt::DisplayRole, idx );

  switch ( mLayer->fields().fieldOrigin( idx ) )
  {
    case QgsFields::OriginExpression:
      dataItem->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
      break;

    case QgsFields::OriginJoin:
      dataItem->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/join.png" ) ) );
      break;

    default:
      dataItem->setIcon( mLayer->fields().iconForField( idx ) );
      break;
  }
  mFieldsList->setItem( row, AttrIdCol, dataItem );

  mIndexedWidgets.insert( idx, mFieldsList->item( row, 0 ) );
  mFieldsList->setItem( row, AttrNameCol, new QTableWidgetItem( field.name() ) );
  mFieldsList->setItem( row, AttrAliasCol, new QTableWidgetItem( field.alias() ) );
  mFieldsList->setItem( row, AttrTypeCol, new QTableWidgetItem( QVariant::typeToName( field.type() ) ) );
  mFieldsList->setItem( row, AttrTypeNameCol, new QTableWidgetItem( field.typeName() ) );
  mFieldsList->setItem( row, AttrLengthCol, new QTableWidgetItem( QString::number( field.length() ) ) );
  mFieldsList->setItem( row, AttrPrecCol, new QTableWidgetItem( QString::number( field.precision() ) ) );

  if ( mLayer->fields().fieldOrigin( idx ) == QgsFields::OriginExpression )
  {
    QWidget *expressionWidget = new QWidget;
    expressionWidget->setLayout( new QHBoxLayout );
    QToolButton *editExpressionButton = new QToolButton;
    editExpressionButton->setProperty( "Index", idx );
    editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
    connect( editExpressionButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::updateExpression );
    expressionWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
    expressionWidget->layout()->addWidget( editExpressionButton );
    expressionWidget->layout()->addWidget( new QLabel( mLayer->expressionField( idx ) ) );
    expressionWidget->setStyleSheet( "background-color: rgb( 200, 200, 255 )" );
    mFieldsList->setCellWidget( row, AttrCommentCol, expressionWidget );
  }
  else
  {
    mFieldsList->setItem( row, AttrCommentCol, new QTableWidgetItem( field.comment() ) );
  }

  QList<int> notEditableCols = QList<int>()
                               << AttrIdCol
                               << AttrNameCol
                               << AttrAliasCol
                               << AttrTypeCol
                               << AttrTypeNameCol
                               << AttrLengthCol
                               << AttrPrecCol
                               << AttrCommentCol;

  Q_FOREACH ( int i, notEditableCols )
  {
    if ( notEditableCols[i] != AttrCommentCol || mLayer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression )
      mFieldsList->item( row, i )->setFlags( mFieldsList->item( row, i )->flags() & ~Qt::ItemIsEditable );
    if ( notEditableCols[i] == AttrAliasCol )
      mFieldsList->item( row, i )->setToolTip( tr( "Edit alias in the Form config tab" ) );
  }
  bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) && !mLayer->readOnly();
  if ( canRenameFields )
    mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
  else
    mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );

  //published WMS/WFS attributes
  QTableWidgetItem *wmsAttrItem = new QTableWidgetItem();
  wmsAttrItem->setCheckState( mLayer->excludeAttributesWms().contains( field.name() ) ? Qt::Unchecked : Qt::Checked );
  wmsAttrItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable );
  mFieldsList->setItem( row, AttrWMSCol, wmsAttrItem );
  QTableWidgetItem *wfsAttrItem = new QTableWidgetItem();
  wfsAttrItem->setCheckState( mLayer->excludeAttributesWfs().contains( field.name() ) ? Qt::Unchecked : Qt::Checked );
  wfsAttrItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable );
  mFieldsList->setItem( row, AttrWFSCol, wfsAttrItem );
}

bool QgsSourceFieldsProperties::addAttribute( const QgsField &field )
{
  QgsDebugMsg( "inserting attribute " + field.name() + " of type " + field.typeName() );
  mLayer->beginEditCommand( tr( "Added attribute" ) );
  if ( mLayer->addAttribute( field ) )
  {
    mLayer->endEditCommand();
    return true;
  }
  else
  {
    mLayer->destroyEditCommand();
    QMessageBox::critical( this, tr( "Failed to add field" ), tr( "Failed to add field '%1' of type '%2'. Is the field name unique?" ).arg( field.name(), field.typeName() ) );
    return false;
  }
}

void QgsSourceFieldsProperties::apply()
{
  QSet<QString> excludeAttributesWMS, excludeAttributesWFS;

  for ( int i = 0; i < mFieldsList->rowCount(); i++ )
  {
    if ( mFieldsList->item( i, AttrWMSCol )->checkState() == Qt::Unchecked )
    {
      excludeAttributesWMS.insert( mFieldsList->item( i, AttrNameCol )->text() );
    }
    if ( mFieldsList->item( i, AttrWFSCol )->checkState() == Qt::Unchecked )
    {
      excludeAttributesWFS.insert( mFieldsList->item( i, AttrNameCol )->text() );
    }
  }

  mLayer->setExcludeAttributesWms( excludeAttributesWMS );
  mLayer->setExcludeAttributesWfs( excludeAttributesWFS );

}

//SLOTS

void QgsSourceFieldsProperties::editingToggled()
{
  updateButtons();
  updateFieldRenamingStatus();
}

void QgsSourceFieldsProperties::addAttributeClicked()
{
  QgsAddAttrDialog dialog( mLayer, this );
  if ( dialog.exec() == QDialog::Accepted )
  {
    addAttribute( dialog.field() );
    loadRows();
  }
}

void QgsSourceFieldsProperties::deleteAttributeClicked()
{
  QSet<int> providerFields;
  QSet<int> expressionFields;
  Q_FOREACH ( QTableWidgetItem *item, mFieldsList->selectedItems() )
  {
    if ( item->column() == 0 )
    {
      int idx = mIndexedWidgets.indexOf( item );
      if ( idx < 0 )
        continue;

      if ( mLayer->fields().fieldOrigin( idx ) == QgsFields::OriginExpression )
        expressionFields << idx;
      else
        providerFields << idx;
    }
  }

  if ( !expressionFields.isEmpty() )
    mLayer->deleteAttributes( expressionFields.toList() );

  if ( !providerFields.isEmpty() )
  {
    mLayer->beginEditCommand( tr( "Deleted attributes" ) );
    if ( mLayer->deleteAttributes( providerFields.toList() ) )
      mLayer->endEditCommand();
    else
      mLayer->destroyEditCommand();
  }
}

void QgsSourceFieldsProperties::calculateFieldClicked()
{
  if ( !mLayer )
  {
    return;
  }

  QgsFieldCalculator calc( mLayer, this );
  if ( calc.exec() == QDialog::Accepted )
  {
    loadRows();
  }
}

void QgsSourceFieldsProperties::attributesListCellChanged( int row, int column )
{
  if ( column == AttrNameCol && mLayer && mLayer->isEditable() )
  {
    int idx = mIndexedWidgets.indexOf( mFieldsList->item( row, AttrIdCol ) );

    QTableWidgetItem *nameItem = mFieldsList->item( row, column );
    //avoiding that something will be changed, just because this is triggered by simple re-sorting
    if ( !nameItem ||
         nameItem->text().isEmpty() ||
         !mLayer->fields().exists( idx ) ||
         mLayer->fields().at( idx ).name() == nameItem->text()
       )
      return;

    mLayer->beginEditCommand( tr( "Rename attribute" ) );
    if ( mLayer->renameAttribute( idx,  nameItem->text() ) )
    {
      mLayer->endEditCommand();
    }
    else
    {
      mLayer->destroyEditCommand();
      QMessageBox::critical( this, tr( "Failed to rename field" ), tr( "Failed to rename field to '%1'. Is the field name unique?" ).arg( nameItem->text() ) );
    }
  }
}


//NICE FUNCTIONS
void QgsSourceFieldsProperties::updateButtons()
{

  int cap = mLayer->dataProvider()->capabilities();

  mToggleEditingButton->setEnabled( ( cap & QgsVectorDataProvider::ChangeAttributeValues ) && !mLayer->readOnly() );

  if ( mLayer->isEditable() )
  {
    mDeleteAttributeButton->setEnabled( cap & QgsVectorDataProvider::DeleteAttributes );
    mAddAttributeButton->setEnabled( cap & QgsVectorDataProvider::AddAttributes );
    mToggleEditingButton->setChecked( true );
  }
  else
  {
    mToggleEditingButton->setChecked( false );
    mAddAttributeButton->setEnabled( false );

    // Enable delete button if items are selected
    mDeleteAttributeButton->setEnabled( !mFieldsList->selectedItems().isEmpty() );
    // and only if all selected items have their origin in an expression
    Q_FOREACH ( QTableWidgetItem *item, mFieldsList->selectedItems() )
    {
      if ( item->column() == 0 )
      {
        int idx = mIndexedWidgets.indexOf( item );
        if ( mLayer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression )
        {
          mDeleteAttributeButton->setEnabled( false );
          break;
        }
      }
    }
  }
}