/***************************************************************************
     testqgsattributetable.cpp
     -------------------------
    Date                 : 2016-02-14
    Copyright            : (C) 2016 by Nyall Dawson
    Email                : nyall dot dawson at gmail dot com
 ***************************************************************************
 *                                                                         *
 *   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 <QtTest/QtTest>
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
#include "qgsattributetabledialog.h"
#include "qgsproject.h"
#include "qgsmapcanvas.h"
#include "qgsunittypes.h"
#include "qgsvectorfilewriter.h"

/** \ingroup UnitTests
 * This is a unit test for the attribute table dialog
 */
class TestQgsAttributeTable : public QObject
{
    Q_OBJECT
  public:
    TestQgsAttributeTable();

  private slots:
    void initTestCase();// will be called before the first testfunction is executed.
    void cleanupTestCase();// will be called after the last testfunction was executed.
    void init() {} // will be called before each testfunction is executed.
    void cleanup() {} // will be called after every testfunction.
    void testRegression15974();
    void testFieldCalculation();
    void testFieldCalculationArea();
    void testNoGeom();

  private:
    QgisApp * mQgisApp;
};

TestQgsAttributeTable::TestQgsAttributeTable()
    : mQgisApp( nullptr )
{

}

//runs before all tests
void TestQgsAttributeTable::initTestCase()
{
  qDebug() << "TestQgisAppClipboard::initTestCase()";
  // init QGIS's paths - true means that all path will be inited from prefix
  QgsApplication::init();
  QgsApplication::initQgis();
  mQgisApp = new QgisApp();

  // setup the test QSettings environment
  QCoreApplication::setOrganizationName( QString( "QGIS" ) );
  QCoreApplication::setOrganizationDomain( QString( "qgis.org" ) );
  QCoreApplication::setApplicationName( QString( "QGIS-TEST" ) );

  QSettings().setValue( QString( "/qgis/attributeTableBehavior" ), QgsAttributeTableFilterModel::ShowAll );
}

//runs after all tests
void TestQgsAttributeTable::cleanupTestCase()
{
  QgsApplication::exitQgis();
}

void TestQgsAttributeTable::testFieldCalculation()
{
  //test field calculation

  //create a temporary layer
  QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "LineString?crs=epsg:3111&field=pk:int&field=col1:double", "vl", "memory" ) );
  QVERIFY( tempLayer->isValid() );
  QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
  f1.setAttribute( "pk", 1 );
  f1.setAttribute( "col1", 0.0 );
  QgsPolyline line3111;
  line3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 );
  f1.setGeometry( QgsGeometry::fromPolyline( line3111 ) );
  tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );

  // set project CRS and ellipsoid
  QgisApp::instance()->mapCanvas()->setCrsTransformEnabled( true );
  QgsCoordinateReferenceSystem srs( 3111, QgsCoordinateReferenceSystem::EpsgCrsId );
  QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSProj4String", srs.toProj4() );
  QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSID", ( int ) srs.srsid() );
  QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCrs", srs.authid() );
  QgsProject::instance()->writeEntry( "Measure", "/Ellipsoid", QString( "WGS84" ) );
  QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Meters ) );

  // run length calculation
  QScopedPointer< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.data() ) );
  tempLayer->startEditing();
  dlg->runFieldCalculation( tempLayer.data(), "col1", "$length" );
  tempLayer->commitChanges();
  // check result
  QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
  QgsFeature f;
  QVERIFY( fit.nextFeature( f ) );
  double expected = 26932.156;
  QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );

  // change project length unit, check calculation respects unit
  QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
  QScopedPointer< QgsAttributeTableDialog > dlg2( new QgsAttributeTableDialog( tempLayer.data() ) );
  tempLayer->startEditing();
  dlg2->runFieldCalculation( tempLayer.data(), "col1", "$length" );
  tempLayer->commitChanges();
  // check result
  fit = tempLayer->dataProvider()->getFeatures();
  QVERIFY( fit.nextFeature( f ) );
  expected = 88360.0918635;
  QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}

void TestQgsAttributeTable::testFieldCalculationArea()
{
  //test $area field calculation

  //create a temporary layer
  QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double", "vl", "memory" ) );
  QVERIFY( tempLayer->isValid() );
  QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
  f1.setAttribute( "pk", 1 );
  f1.setAttribute( "col1", 0.0 );

  QgsPolyline polygonRing3111;
  polygonRing3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 ) << QgsPoint( 2520109, 2397715 ) << QgsPoint( 2520792, 2425494 ) << QgsPoint( 2484588, 2425722 );
  QgsPolygon polygon3111;
  polygon3111 << polygonRing3111;
  f1.setGeometry( QgsGeometry::fromPolygon( polygon3111 ) );
  tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );

  // set project CRS and ellipsoid
  QgisApp::instance()->mapCanvas()->setCrsTransformEnabled( true );
  QgsCoordinateReferenceSystem srs( 3111, QgsCoordinateReferenceSystem::EpsgCrsId );
  QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSProj4String", srs.toProj4() );
  QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSID", ( int ) srs.srsid() );
  QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCrs", srs.authid() );
  QgsProject::instance()->writeEntry( "Measure", "/Ellipsoid", QString( "WGS84" ) );
  QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMeters ) );

  // run area calculation
  QScopedPointer< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.data() ) );
  tempLayer->startEditing();
  dlg->runFieldCalculation( tempLayer.data(), "col1", "$area" );
  tempLayer->commitChanges();
  // check result
  QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
  QgsFeature f;
  QVERIFY( fit.nextFeature( f ) );
  double expected = 1009089817.0;
  QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 1.0 ) );

  // change project area unit, check calculation respects unit
  QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMiles ) );
  QScopedPointer< QgsAttributeTableDialog > dlg2( new QgsAttributeTableDialog( tempLayer.data() ) );
  tempLayer->startEditing();
  dlg2->runFieldCalculation( tempLayer.data(), "col1", "$area" );
  tempLayer->commitChanges();
  // check result
  fit = tempLayer->dataProvider()->getFeatures();
  QVERIFY( fit.nextFeature( f ) );
  expected = 389.6117565069;
  QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}

void TestQgsAttributeTable::testNoGeom()
{
  //test that by default the attribute table DOESN'T fetch geometries (because performance)
  QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( QString( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QString( "vl" ), QString( "memory" ) ) );
  QVERIFY( tempLayer->isValid() );

  QSettings().setValue( QString( "/qgis/attributeTableBehaviour" ), QgsAttributeTableFilterModel::ShowAll );
  QScopedPointer< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.data() ) );

  QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
  QVERIFY( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry );

  // but if we are requesting only visible features, then geometry must be fetched...

  QSettings().setValue( QString( "/qgis/attributeTableBehaviour" ), QgsAttributeTableFilterModel::ShowVisible );
  dlg.reset( new QgsAttributeTableDialog( tempLayer.data() ) );
  QVERIFY( dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
  QVERIFY( !( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry ) );

  // try changing existing dialog to no geometry mode
  dlg->filterShowAll();
  QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
  QVERIFY( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry );

  // and back to a geometry mode
  dlg->filterVisible();
  QVERIFY( dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
  QVERIFY( !( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry ) );

}


void TestQgsAttributeTable::testRegression15974()
{
  QString path = QDir::tempPath() + "/testshp15974.shp";
  std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( "polygon?crs=epsg:4326&field=id:integer" ,  "vl" ,  "memory" ) );
  QVERIFY( tempLayer->isValid() );
  QgsCoordinateReferenceSystem crs( 4326 );
  QgsVectorFileWriter::writeAsVectorFormat( tempLayer.get( ), path, "system", &crs );
  std::unique_ptr< QgsVectorLayer> shpLayer( new QgsVectorLayer( path,  "test" ,   "ogr" ) );
  QgsFeature f1( shpLayer->dataProvider()->fields(), 1 );
  QgsGeometry* geom1;
  geom1 = QgsGeometry().fromWkt( "polygon((0 0, 0 1, 1 1, 1 0, 0 0))" );
  QVERIFY( geom1->isGeosValid( ) );
  f1.setGeometry( geom1 );
  QgsFeature f2( shpLayer->dataProvider()->fields(), 2 );
  QgsGeometry* geom2;
  geom2 = QgsGeometry().fromWkt( "polygon((0 0, 0 1, 1 1, 1 0, 0 0))" );
  f2.setGeometry( geom2 );
  QgsFeature f3( shpLayer->dataProvider()->fields(), 3 );
  QgsGeometry* geom3;
  geom3 = QgsGeometry().fromWkt( "polygon((0 0, 0 1, 1 1, 1 0, 0 0))" );
  f3.setGeometry( geom3 );
  QVERIFY( shpLayer->startEditing( ) );
  QVERIFY( shpLayer->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
  std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( shpLayer.get() ) );
  QCOMPARE( shpLayer->featureCount( ), 3L );
  mQgisApp->saveEdits( shpLayer.get( ) );
  QCOMPARE( shpLayer->featureCount( ), 3L );
  QCOMPARE( dlg->mMainView->masterModel()->rowCount(), 3 );
  QCOMPARE( dlg->mMainView->mLayerCache->cachedFeatureIds( ).count(), 3 );
  QCOMPARE( dlg->mMainView->featureCount( ), 3 );
  // The following passes locally but fails on Travis
  // QCOMPARE( dlg->mMainView->mFilterModel->mFilteredFeatures.count(), 3 );
}


QTEST_MAIN( TestQgsAttributeTable )
#include "testqgsattributetable.moc"
