#include "gaussfft.hpp"

#include <boost/python/numpy.hpp>

//#include <chrono>
#include <iostream>

#include "nrlib/iotools/stringtools.hpp"
#include "nrlib/grid/grid.hpp"
#include "nrlib/grid/grid2d.hpp"
#include "nrlib/math/constants.hpp"
#include "nrlib/random/random.hpp"
#include "nrlib/variogram/variogram.hpp"
#include "nrlib/variogram/gaussianfield.hpp"

namespace bp = boost::python;

/***************************/
std::string GaussFFT::Quote()
{
  return "Arc, amplitude, and curvature sustain a similar relation to each other as time, motion, and velocity, or as volume, mass, and density.";
}

/**********************************************************************************/
std::vector<size_t> GaussFFT::FindGridSizeAfterPadding(NRLib::Variogram * variogram,
                                                       size_t             nx,
                                                       double             dx,
                                                       size_t             ny,
                                                       double             dy,
                                                       size_t             nz,
                                                       double             dz)
{
  std::vector<size_t> out = NRLib::FindNDimPadding(*variogram,
                                                   nx,
                                                   dx,
                                                   ny,
                                                   dy,
                                                   nz,
                                                   dz);
  // Add simulation grid size to that output reflects final grid size
  // and not padding only
  out[0] += nx;
  if (out.size() > 1) {
    out[1] += ny;
    if (out.size() > 2) {
      out[2] += nz;
    }
  }
  return out;
}

/*********************************************************************/
NRLib::Variogram * GaussFFT::CreateVariogram(const std::string & type,
                                             double              range_x,
                                             double              range_y,
                                             double              range_z,
                                             double              azimuth_angle,
                                             double              dip_angle,
                                             double              power)
{
  if (range_y < 0.0)
    range_y = range_x;
  if (range_z < 0.0)
    range_z = range_x;

  azimuth_angle *= NRLib::Degree;
  dip_angle *= NRLib::Degree;

  NRLib::Variogram::Type t;
  std::string utype = NRLib::Uppercase(type);
  if (utype == "CONSTANT")
    t = NRLib::Variogram::CONSTANT;
  else if (utype == "EXPONENTIAL")
    t = NRLib::Variogram::EXPONENTIAL;
  else if (utype == "GAUSSIAN")
    t = NRLib::Variogram::GAUSSIAN;
  else if (utype == "GENERAL_EXPONENTIAL")
    t = NRLib::Variogram::GENERAL_EXPONENTIAL;
  else if (utype == "MATERN32")
    t = NRLib::Variogram::MATERN32;
  else if (utype == "MATERN52")
    t = NRLib::Variogram::MATERN52;
  else if (utype == "MATERN72")
    t = NRLib::Variogram::MATERN72;
  else if (utype == "SPHERICAL")
    t = NRLib::Variogram::SPHERICAL;
  else
    t = NRLib::Variogram::SPHERICAL;

  return NRLib::Variogram::Create(t, power, range_x, range_y, range_z, azimuth_angle, dip_angle, 1.0);
}

/******************************************************************/
bp::numpy::ndarray  GaussFFT::Simulate(NRLib::Variogram * variogram,
                                       size_t             nx,
                                       double             dx,
                                       size_t             ny,
                                       double             dy,
                                       size_t             nz,
                                       double             dz)
{
  return SimulateWithAdvancedSettings(variogram, nx, dx, ny, dy, nz, dz, -1, -1,-1, 1.0, 1.0, 1.0);
}

/***********************************************************************************/
bp::numpy::ndarray  GaussFFT::SimulateWithAdvancedSettings(NRLib::Variogram * variogram,
                                                           size_t             nx,
                                                           double             dx,
                                                           size_t             ny,
                                                           double             dy,
                                                           size_t             nz,
                                                           double             dz,
                                                           int                padding_x,
                                                           int                padding_y,
                                                           int                padding_z,
                                                           double             scaling_x,
                                                           double             scaling_y,
                                                           double             scaling_z)
{
  try {
    NRLib::Random::GetStartSeed();
  }
  catch (NRLib::Exception e) {
    // NRLib::Random is not initialized yet. Use empty initializer:
    NRLib::Random::Initialize();
  }
  std::vector<double> result;
  if (ny <= 1U || dy < 0.0) {
    result = GaussFFT::Simulate1D(variogram, nx, dx,                 padding_x,                       scaling_x                      );
  }
  else if (nz <= 1U || dz < 0.0) {
    result = GaussFFT::Simulate2D(variogram, nx, dx, ny, dy,         padding_x, padding_y,            scaling_x, scaling_y           );
  }
  else {
    result = GaussFFT::Simulate3D(variogram, nx, dx, ny, dy, nz, dz, padding_x, padding_y, padding_z, scaling_x, scaling_y, scaling_z);
  }

  bp::numpy::dtype dt = bp::numpy::dtype::get_builtin<double>();
  bp::tuple shape = bp::make_tuple(result.size());
  bp::tuple stride = bp::make_tuple(sizeof(double));
  bp::object owner;
  bp::numpy::ndarray np_result = bp::numpy::from_data(&result[0], dt, shape, stride, owner);
  return np_result.copy();
}

/********************************************************************/
std::vector<double> GaussFFT::Simulate1D(NRLib::Variogram * variogram,
                                         size_t             nx,
                                         double             dx,
                                         int                padding_x,
                                         double             scaling_x)
{
  std::vector<double> values;
  // Have to allocate memory (for some reason) before running the simulation
  values.resize(nx);
  NRLib::Simulate1DGaussianField(*variogram,
                                 nx,
                                 dx,
                                 values,
                                 NULL, // Will use NRLib::Random state
                                 padding_x,
                                 scaling_x);
  return values;
}

/********************************************************************/
std::vector<double> GaussFFT::Simulate2D(NRLib::Variogram * variogram,
                                         size_t             nx,
                                         double             dx,
                                         size_t             ny,
                                         double             dy,
                                         int                padding_x,
                                         int                padding_y,
                                         double             scaling_x,
                                         double             scaling_y)
{
  std::vector<NRLib::Grid2D<double> > fields;
  NRLib::Simulate2DGaussianField(*variogram,
                                 nx,
                                 dx,
                                 ny,
                                 dy,
                                 1,
                                 fields,
                                 NULL, // Will use NRLib::Random state
                                 padding_x,
                                 padding_y,
                                 scaling_x,
                                 scaling_y);
  return fields[0].GetStorage();
}

/*********************************************************************/
std::vector<double> GaussFFT::Simulate3D(NRLib::Variogram * variogram,
                                         size_t             nx,
                                         double             dx,
                                         size_t             ny,
                                         double             dy,
                                         size_t             nz,
                                         double             dz,
                                         int                padding_x,
                                         int                padding_y,
                                         int                padding_z,
                                         double             scaling_x,
                                         double             scaling_y,
                                         double             scaling_z)
{
  std::vector<NRLib::Grid<double> > fields;
  NRLib::Simulate3DGaussianField(*variogram,
                                 nx,
                                 dx,
                                 ny,
                                 dy,
                                 nz,
                                 dz,
                                 1,
                                 fields,
                                 padding_x,
                                 padding_y,
                                 padding_z,
                                 scaling_x,
                                 scaling_y,
                                 scaling_z);
  return fields[0].GetStorage();
}


#include "../utility/log.h"
#include "calcfeatures.h"
#include <mutex>
#include <random>
#include <numeric>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/imgcodecs.hpp>
#include "csvreader.h"

#ifdef DEBUG_CALCULATE_FEATS
#undef DEBUG_CALCULATE_FEATS
static const string DEBUG_FOLDER = "/var/tmp/water/";
#endif

using namespace cv;
using namespace std;
using namespace boost;

namespace fs = filesystem;

static mutex g_image_read_mutex;

static bool is_number(const string& s)
{
    return !s.empty() && find_if(s.begin(),
        s.end(), [](char c) { return !(isdigit(c)||c=='.'||c=='-'); }) == s.end();
}
double DISTANCE( const Point2d a, const Point2d b ) { return static_cast< double >( sqrt( ( b.x - a.x ) * ( b.x - a.x ) +
                                                                                ( b.y - a.y ) * ( b.y - a.y ) ) ); }

namespace gc
{

GC_STATUS PickNRandom( const int total, const int numToPick, vector< int > &picks );

CalcFeatures::CalcFeatures()
{

}
GC_STATUS CalcFeatures::WriteFeatSetToCSV( const string filepath, const FeatureSet &featSet )
{
    GC_STATUS retVal = CreateCSVFileAndHeader( filepath, featSet );
    if ( GC_OK == retVal )
    {
        try
        {
            ofstream outFile( filepath, ios_base::out | ios_base::app );
            if ( !outFile.is_open() )
            {
                FILE_LOG( logERROR ) << "[CalcFeatures::WriteFeatSetToCSV] Could not open CSV: " << filepath;
                retVal = GC_ERR;
            }
            else
            {
                outFile << std::fixed;
                outFile << featSet.sensor.timeStamp << ",";
                outFile << featSet.exif.captureTime << ",";
                outFile << featSet.imageFilename << ",";
                outFile << featSet.sensor.agency << ",";
                outFile << featSet.sensor.siteNumber << ",";
                outFile << featSet.sensor.timeZone << ",";
                outFile << featSet.sensor.stage << ",";
                outFile << featSet.sensor.discharge << ",";
                outFile << featSet.calcTimestamp << ",";
                outFile << featSet.wholeImage.imageSize.width << ",";
                outFile << featSet.wholeImage.imageSize.height << ",";
                outFile << featSet.exif.exposureTime << ",";
                outFile << featSet.exif.fNumber << ",";
                outFile << featSet.exif.isoSpeedRating << ",";
                outFile << featSet.exif.shutterSpeed << ",";
                outFile << featSet.wholeImage.grayStats.average << ",";
                outFile << featSet.wholeImage.grayStats.sigma << ",";
                outFile << featSet.wholeImage.entropyStats.average << ",";
                outFile << featSet.wholeImage.entropyStats.sigma << ",";
                if ( 3 != featSet.wholeImage.hsvStats.size() )
                {
                    outFile << "N/A,N/A,N/A,N/A,N/A,N/A," << endl;
                }
                else
                {
                    outFile << featSet.wholeImage.hsvStats[ 0 ].average << ",";
                    outFile << featSet.wholeImage.hsvStats[ 0 ].sigma << ",";
                    outFile << featSet.wholeImage.hsvStats[ 1 ].average << ",";
                    outFile << featSet.wholeImage.hsvStats[ 1 ].sigma << ",";
                    outFile << featSet.wholeImage.hsvStats[ 2 ].average << ",";
                    outFile << featSet.wholeImage.hsvStats[ 2 ].sigma << ",";
                }
                outFile << featSet.areaFeats.size() << ",";
                for ( size_t i = 0; i < featSet.areaFeats.size(); ++i )
                {
                    outFile << featSet.areaFeats[ i ].grayStats.average << ",";
                    outFile << featSet.areaFeats[ i ].grayStats.sigma << ",";
                    outFile << featSet.areaFeats[ i ].entropyStats.average << ",";
                    outFile << featSet.areaFeats[ i ].entropyStats.sigma;
                    if ( 3 != featSet.areaFeats[ i ].hsvStats.size() )
                    {
                        outFile << "N/A,N/A,N/A,N/A,N/A,N/A" << endl;
                    }
                    else
                    {
                        outFile << featSet.areaFeats[ i ].hsvStats[ 0 ].average << ",";
                        outFile << featSet.areaFeats[ i ].hsvStats[ 0 ].sigma << ",";
                        outFile << featSet.areaFeats[ i ].hsvStats[ 1 ].average << ",";
                        outFile << featSet.areaFeats[ i ].hsvStats[ 1 ].sigma << ",";
                        outFile << featSet.areaFeats[ i ].hsvStats[ 2 ].average << ",";
                        outFile << featSet.areaFeats[ i ].hsvStats[ 2 ].sigma;
                    }
                    if ( featSet.areaFeats.size() - 1 > i )
                        outFile << ",";
                }
            }
            outFile.close();
        }
        catch( const boost::exception &e )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::WriteFeatSetToCSV] " << diagnostic_information( e );
            retVal = GC_EXCEPT;
        }
    }
    return retVal;
}
GC_STATUS CalcFeatures::CreateCSVFileAndHeader( const string filepath, const FeatureSet &featSet )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        ofstream outFile;
        if ( !fs::exists( filepath ) )
        {
            if ( !fs::exists( fs::path( filepath ).parent_path() ) )
            {
                 bool bRet = fs::create_directories( fs::path( filepath ).parent_path() );
                 if ( !bRet )
                 {
                     FILE_LOG( logERROR ) << "[CalcFeatures::CreateCSVFileAndHeader] Could not create folder for CSV file: " << filepath;
                     retVal = GC_ERR;
                 }
            }
            if ( GC_OK == retVal )
            {
                outFile.open( filepath, ios_base::out );
                if ( !outFile.is_open() )
                {
                    FILE_LOG( logERROR ) << "[CalcFeatures::CreateCSVFileAndHeader] Could not create CSV: " << filepath;
                    retVal = GC_ERR;
                }
                else
                {
                    outFile << "SensorTime, CaptureTime, Filename, Agency, SiteNumber, TimeZone, Stage, Discharge, ";
                    outFile << "CalcTimestamp, width, height, exposure, fNumber, isoSpeed, shutterSpeed, grayMean, ";
                    outFile << "graySigma, entropyMean, entropySigma, hMean, hSigma, sMean, sSigma, vMean, vSigma, ";
                    outFile << "areaFeatCount";
                    if ( 0 == featSet.areaFeats.size() )
                    {
                        outFile << endl;
                    }
                    else
                    {
                        outFile << ", ";
                        for ( size_t i = 0; i < featSet.areaFeats.size(); ++i )
                        {
                            outFile << "grayMean " << i << ", graySigma " << i << ", ";
                            outFile << "entropyMean " << i << ", entropySigma " << i << ", ";
                            outFile << "hMean " << i << ", hSigma " << i << ", ";
                            outFile << "sMean " << i << ", sSigma " << i << ", ";
                            outFile << "vMean " << i << ", vSigma " << i << ", ";
                        }
                    }
                    outFile << "WeirAngle, WeirPt1X, WeirPt1Y, WeirPt2X, WeirPt2Y, WwRawLineMin, WwRawLineMax, WwRawLineMean, ";
                    outFile << "WwRawLineSigma, WwCurveLineMin, WwCurveLineMax, WwCurveLineMean, WwCurveLineSigma" << endl;
                }
                outFile.close();
            }
        }
    }
    catch( const boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::CreateCSVFileAndHeader] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }
    return retVal;
}
GC_STATUS CalcFeatures::ParseRow( const vector< string > data, FeatureSet &feat )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        int j = 0;
        PixelStats pixStats;

        feat.clear();
        feat.sensor.timeStamp = data[ j++ ];
        feat.exif.captureTime = data[ j++ ];
        feat.imageFilename = data[ j++ ];
        feat.sensor.agency = data[ j++ ];
        feat.sensor.siteNumber = data[ j++ ];
        feat.sensor.timeZone = data[ j++ ];
        feat.sensor.stage = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.sensor.discharge = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;

        feat.calcTimestamp = data[ j++ ];
        feat.wholeImage.imageSize.width = is_number( data[ j ] ) ? stoi( data[ j ] ) : -9999999; ++j;
        feat.wholeImage.imageSize.height = is_number( data[ j ] ) ? stoi( data[ j ] ) : -9999999; ++j;
        feat.exif.exposureTime = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.exif.fNumber = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.exif.isoSpeedRating = is_number( data[ j ] ) ? stoi( data[ j ] ) : -9999999; ++j;
        feat.exif.shutterSpeed = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;

        feat.wholeImage.grayStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.wholeImage.grayStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;

        feat.wholeImage.entropyStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.wholeImage.entropyStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;

        pixStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        pixStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.wholeImage.hsvStats.push_back( pixStats );

        pixStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        pixStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.wholeImage.hsvStats.push_back( pixStats );

        pixStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        pixStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
        feat.wholeImage.hsvStats.push_back( pixStats );

        ImageAreaFeatures areaFeat;
        int areaFeatCount  = is_number( data[ j ] ) ? stoi( data[ j ] ) : 0; ++j;
        for ( size_t i = 0; i < static_cast< size_t >( areaFeatCount ); ++i )
        {
            areaFeat.clear();

            areaFeat.grayStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            areaFeat.grayStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;

            areaFeat.entropyStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            areaFeat.entropyStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;

            pixStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            pixStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            areaFeat.hsvStats.push_back( pixStats );

            pixStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            pixStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            areaFeat.hsvStats.push_back( pixStats );

            pixStats.average = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            pixStats.sigma = is_number( data[ j ] ) ? stod( data[ j ] ) : -9999999; ++j;
            areaFeat.hsvStats.push_back( pixStats );

            feat.areaFeats.push_back( areaFeat );
        }
    }
    catch( const boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::ReadCSV] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }
    return retVal;
}
GC_STATUS CalcFeatures::ReadCSV( const string filepath, vector< FeatureSet > &featSets )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        CSVReader reader( filepath );
        vector< vector< string > > data = reader.getData();
        if ( data.empty() )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::ReadCSV] No data in file " << filepath;
            retVal = GC_ERR;
        }
        else
        {
            FeatureSet feat;
            size_t count = data[ 0 ].size();
            for ( size_t i = 1; i < data.size(); ++i )
            {
                cout << "Parse features: " << i << " of " << data.size() << "\r";
                if ( count <= data[ i ].size() )
                {
                    retVal = ParseRow( data[ i ], feat );
                    if ( GC_OK == retVal )
                    {
                        featSets.push_back( feat );
                    }
                }
            }
        }
    }
    catch( const boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::ReadCSV] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }
    return retVal;
}
// TODO: Fix this later -- KWC
/*
GC_STATUS CalcFeatures::MergeSensorData( const std::string featFilepath, const std::string sensorDataFilepath,
                                         const std::string mergeFilepath, const bool isSensorData, const std::string noSensorDataFilepath )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        SensorData sensor;
        retVal = isSensorData ? sensor.Load( sensorDataFilepath, site ) :
                                sensor.LoadManualData( sensorDataFilepath, site );
        if ( GC_OK == retVal )
        {
            CSVReader reader( featFilepath );
            vector< vector< string > > data = reader.getData();
            if ( data.empty() )
            {
                FILE_LOG( logERROR ) << "[CalcFeatures::MergeSensorData] No data in file " << featFilepath;
                retVal = GC_ERR;
            }
            else
            {
                FeatureSet feat;
                size_t startIdx = 0;

                size_t count = data[ 0 ].size();
                double elapsed_seconds = 0.0;
                auto start = std::chrono::system_clock::now();
                fs::remove( fs::path( mergeFilepath ) );

                for ( size_t i = 1; i < data.size(); ++i )
                {
                    cout << "Parse features: " << i << " of " << data.size() << "\r";
                    if ( data[ i ].size() != count )
                    {
                        FILE_LOG( logERROR ) << "[CalcFeatures::MergeSensorData] row data columns not equal to header columns for row " << i;
                        retVal = GC_ERR;
                        break;
                    }
                    else
                    {
                        retVal = ParseRow( data[ i ], feat );
                        if ( GC_OK != retVal )
                            break;

                        retVal = sensor.GetNearestSensorData( feat, startIdx, site );
                        if ( GC_OK != retVal )
                        {
                            startIdx = 0;
                            retVal = sensor.GetNearestSensorData( feat, startIdx, site );
                            if ( GC_OK == retVal )
                            {
                                retVal = sensor.GetNearestSensorData( feat, startIdx, site );
                            }
                        }
                        if ( GC_OK == retVal )
                        {
                            retVal = WriteFeatSetToCSV( mergeFilepath, feat );
                            if ( GC_OK != retVal )
                                break;

                            elapsed_seconds = std::chrono::duration_cast< std::chrono::seconds >( std::chrono::system_clock::now() - start ).count();
                            FILE_LOG( logINFO ) << "[" << i << " of " << data.size() << " Secs=" << elapsed_seconds << "] " << feat.imageFilename << ": " << ( GC_OK == retVal ? "SUCCESS" : "FAILURE" );
                        }
                        else if ( GC_WARN == retVal )
                        {
                            retVal = WriteFeatSetToCSV( noSensorDataFilepath, feat );
                            if ( GC_OK != retVal )
                                break;
                        }
                    }
                }
            }
        }
    }
    catch( const boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::ReadCSV] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }
    return retVal;
}
*/
GC_STATUS CalcFeatures::Calculate( const string filepath, FeatureSet &featSet, const std::string saveResultFolder )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        if ( !fs::exists( filepath ) )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] File does not exist: " << filepath;
            retVal = GC_ERR;
        }
        else if( !fs::is_regular_file( filepath ) )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] File is not a regular file: " << filepath;
            retVal = GC_ERR;
        }
        else if ( ".jpg" != filepath.substr( filepath.size() - 4 ) &&
                  ".JPG" != filepath.substr( filepath.size() - 4 ) )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] File is not an image file: " << filepath;
            retVal = GC_ERR;
        }
        else
        {
            Mat src, raw, scratch, imgColor;
            string data;

            {
                lock_guard< mutex > guard( g_image_read_mutex );
                retVal = m_exif.Retrieve( filepath, data, featSet.exif );
                if ( GC_OK == retVal )
                {
                    raw = imread( filepath, IMREAD_COLOR );
                    if ( raw.empty() )
                    {
                        FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] Could not read image : " << filepath;
                        retVal = GC_ERR;
                    }
                    else
                    {
                        imgColor = raw.clone();
                        cvtColor( imgColor, src, COLOR_BGR2GRAY );
                    }
                }
            }
#if 0
            if ( GC_OK == retVal )
            {
#ifdef DEBUG_CALCULATE_FEATS
                imwrite( DEBUG_FOLDER + "weir_original.png", imgColor );
#endif
                featSet.imageFilename = fs::path( filepath ).filename().string();
                Rect rectDarkness = ( 0 == settings.darknessROI.width || 0 == settings.darknessROI.height ) ?
                            Rect( 0, src.rows >> 1, src.cols, ( src.rows >> 1 ) - 1 ) : settings.darknessROI;
                double darknessMean = mean( src( rectDarkness ) ).val[ 0 ];
                Canny( src, scratch, 35, 70, 3 );
                double edgesMean = mean( scratch ).val[ 0 ] / 255.0;
//                FILE_LOG( logINFO ) << "**********************";
//                FILE_LOG( logINFO ) << "Edges=" << edgesMean;
//                FILE_LOG( logINFO ) << "**********************";

                if ( m_settings.darknessMin > darknessMean || m_settings.darknessMax < darknessMean )
                {
                    featSet.status = FIND_BAD_TOO_DARK;
                    putText( imgColor, "FAIL: Image too dark to process", Point( 100, 100 ), FONT_HERSHEY_PLAIN, 2.0, Scalar( 0, 0, 255 ), 3 );
                    FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] Image too dark to process for " << fs::path( filepath ).filename().string();
                    retVal = GC_ERR;
                }
                else if ( m_settings.edgesMin > edgesMean )
                {
                    featSet.status = FIND_LOW_EDGE_COUNT;
                    putText( imgColor, "FAIL: Image too low edge count to process", Point( 100, 100 ), FONT_HERSHEY_PLAIN, 2.0, Scalar( 0, 0, 255 ), 3 );
                    FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] Image too dark to process for " << fs::path( filepath ).filename().string();
                    retVal = GC_ERR;
                }
                else
                {
                    if ( WHOLE_IMAGE_FEATURE_TYPE & featureTypes )
                    {
                        Mat mask;
                        featSet.wholeImage.imageSize = src.size();
                        retVal = m_imgFeats.CalcImageFeatures( imgColor, featSet.wholeImage, mask );
                        if ( GC_OK != retVal )
                        {
                            putText( imgColor, "FAIL: Whole image feature calculation", Point( 100, 100 ), FONT_HERSHEY_PLAIN, 2.0, Scalar( 0, 0, 255 ), 3 );
                            FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] Could not calculate whole image features for " << filepath;
                        }
                    }

                    bool foundAreaFeature = false;
                    if ( GC_OK == retVal && ( AREA_FEATURE_TYPE & featureTypes ) )
                    {
                        retVal = m_imgFeats.CalcMaskedFeatures( imgColor, settings.areaFeatRois, settings.areaFeatShapes, featSet.areaFeats );
                        if ( GC_OK == retVal )
                        {
                            foundAreaFeature = true;
                        }
                        else
                        {
                            putText( imgColor, "FAIL: Area features", Point( 100, 200 ), FONT_HERSHEY_PLAIN, 2.0, Scalar( 0, 0, 255 ), 3 );
                            FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] Could not calculate whole image features for " << filepath;
                        }
                    }

                    bool weirFound = false;
                    if ( GC_OK == retVal && ( WEIR_FEATURE_TYPE & featureTypes ) )
                    {
                        Mat belowWeirMask;
                        retVal = m_weir.Calculate( src, featSet.weir, settings.weirSettings, belowWeirMask );
                        if ( GC_OK == retVal )
                        {
                            weirFound = true;
                            if ( !saveResultFolder.empty() )
                            {
                                line( imgColor, featSet.weir.weirLine.start, featSet.weir.weirLine.end, Scalar( 255, 0, 0 ), 3 );
                                for ( size_t k = 0; k < featSet.weir.whitewater.lineFeats.size(); ++k )
                                {
                                    line( imgColor, featSet.weir.whitewater.lineFeats[ k ].ends.start,
                                          featSet.weir.whitewater.lineFeats[ k ].ends.end, Scalar( 0, 255, 255 ), 1 );
                                }
                                vector< Point > wwOutline;
                                for ( size_t k = 0; k < featSet.weir.whitewater.lines.size(); ++k )
                                {
                                    line( imgColor, featSet.weir.whitewater.lines[ k ].start,
                                          featSet.weir.whitewater.lines[ k ].end, Scalar( 0, 255, 0 ), 1 );
                                    wwOutline.push_back( featSet.weir.whitewater.lines[ k ].end );
                                }
                                polylines( imgColor, wwOutline, true, Scalar( 0, 0, 255 ), 3 );
                            }

                            // FILE_LOG( logINFO ) << "Right bank point x=" << featSet.weir.rightBankPt.x << " y=" << featSet.weir.rightBankPt.y;
                            if ( m_settings.weirSettings.weirAngleMin > featSet.weir.angle ||
                                 m_settings.weirSettings.weirAngleMax < featSet.weir.angle )
                            {
                                featSet.status = FIND_BAD_WEIR_ANGLE;
                                putText( imgColor, "FAIL: Bad weir angle found", Point( 100, 100 ), FONT_HERSHEY_PLAIN, 2.0, Scalar( 0, 0, 255 ), 3 );
                                FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] Bad weir angle=" << featSet.weir.angle << " found for " << fs::path( filepath ).filename().string();
                                retVal = GC_ERR;
                            }
                        }
                        if ( GC_OK != retVal )
                        {
                            featSet.weir.clear();
                            retVal = GC_OK;
                        }
                        featSet.status = FIND_OK;
                        if ( !saveResultFolder.empty() )
                        {
                            if ( foundAreaFeature )
                            {
                                for ( size_t i = 0; i < settings.areaFeatRois.size(); ++i )
                                    polylines( imgColor, settings.areaFeatRois[ i ], true,
                                               i % 2 ? Scalar( 255, 0, 0 ) :  Scalar( 0, 255, 255 ), 3 );
                            }

                            string fileResult = saveResultFolder;
                            if ( '/' != fileResult[ fileResult.size() - 1 ] )
                                fileResult += '/';
#if 0
                            fileResult += string( GC_OK == retVal ? "OK_" : "FAIL_" ) + featSet.imageFilename;
                            imwrite( fileResult, imgColor );
#else

                            Mat resizeScratch;
                            Mat scratch( Size( 1072, 1444 ), CV_8UC3 );
                            scratch = 255;
                            Mat top = scratch( Rect( 0, 0, 1072, 712 ) );
                            Mat bot = scratch( Rect( 0, 732, 1072, 712 ) );
                            resize( raw, resizeScratch, Size( 1072, 712 ) );
                            resizeScratch.copyTo( top );
                            if ( featSet.status == FIND_BAD_TOO_DARK )
                            {
                                circle( bot, Point( bot.cols >> 1, bot.rows >> 1 ), 250, Scalar( 0, 0, 255 ), 50 );
                                line( bot, Point( ( bot.cols >> 1 ) - 175, ( bot.rows >> 1 ) - 175 ), Point( ( bot.cols >> 1 ) + 175, ( bot.rows >> 1 ) + 175 ), Scalar( 0, 0, 255 ), 50 );
                                putText( bot, "Could not process", Point( 100, 100 ), FONT_HERSHEY_PLAIN, 2.4, Scalar( 0, 255, 255 ), 5 );
                            }
                            else
                            {
                                resize( imgColor, resizeScratch, Size( 1072, 712 ) );
                                resizeScratch.copyTo( bot );
                                if ( !weirFound )
                                {
                                    putText( bot, "Weir not found", Point( 100, 100 ), FONT_HERSHEY_PLAIN, 2.0, Scalar( 0, 255, 255 ), 3 );
                                }
                            }
                            fileResult += featSet.imageFilename;
                            imwrite( fileResult, scratch );
#endif
                        }
                    }
                }
            }
#endif
        }
    }
    catch( const Exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] " << e.what();
        retVal = GC_EXCEPT;
    }
    catch( const boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::Calculate] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }
    return retVal;
}

GC_STATUS CalcFeatures::SplitTestTrainSets( const string allCSV, const string setFolder, const int beforeCount,
                                               const int afterCount, const size_t timeStampCol,
                                               const string testStartTimeStamp, const string testEndTimeStamp )
{
    GC_STATUS retVal = GC_OK;

    try
    {
        CSVReader reader( allCSV );
        vector< vector< string > > data = reader.getData();

        if ( data.empty() )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not load data from " << allCSV;
            retVal = GC_ERR;
        }
        else
        {
            int endTest = -1;
            int startTest = -1;
            for ( size_t i = 0; i < data.size(); ++i )
            {
                if ( testStartTimeStamp == data[ i ][ timeStampCol ] )
                    startTest = static_cast< int >( i );
                if ( testEndTimeStamp == data[ i ][ timeStampCol ] )
                    endTest = static_cast< int >( i );
                if ( -1 != startTest && -1 != endTest )
                    break;
            }
            if ( -1 == startTest || -1 == endTest )
            {
                FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not find start and/or end dates: " << testStartTimeStamp << " to " << testEndTimeStamp;
                retVal = GC_ERR;
            }
            else
            {
                int beforeStart = startTest - beforeCount;
                int beforeEnd = startTest;
                int afterStart = endTest + 1;
                int afterEnd = endTest + afterCount + 1;

                string resultFolder = setFolder;
                if ( '/' != resultFolder[ resultFolder.size() - 1 ] )
                    resultFolder += '/';

                if ( !fs::exists( resultFolder ) )
                    fs::create_directories( resultFolder );

                string trainCSV = resultFolder + "train_" + to_string( beforeCount ) + "_" +
                        to_string( afterCount ) + "_" + fs::path( allCSV ).filename().string();
                string testCSV = resultFolder + "test_" + to_string( beforeCount ) + "_" +
                        to_string( afterCount ) + "_" + fs::path( allCSV ).filename().string();

                ofstream outFile( trainCSV );
                if ( !outFile.is_open() )
                {
                    FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not open train set file for writing: " << trainCSV;
                    retVal = GC_ERR;
                }
                else
                {
                    for ( size_t i = 0; i < data[ 0 ].size(); ++i )
                    {
                        outFile << data[ 0 ][ i ];
                        if ( data[ 0 ].size() - 1 == i )
                            outFile << endl;
                        else
                            outFile << ",";
                    }
                    for ( int j = beforeStart; j < beforeEnd; ++j )
                    {
                        for ( size_t i = 0; i < data[ j ].size(); ++i )
                        {
                            outFile << data[ j ][ i ];
                            if ( data[ j ].size() - 1 == i )
                                outFile << endl;
                            else
                                outFile << ",";
                        }
                    }
                    for ( int j = afterStart; j < afterEnd; ++j )
                    {
                        for ( size_t i = 0; i < data[ j ].size(); ++i )
                        {
                            outFile << data[ j ][ i ];
                            if ( data[ j ].size() - 1 == i )
                                outFile << endl;
                            else
                                outFile << ",";
                        }
                    }
                    outFile.close();
                    ofstream outFile( testCSV );
                    if ( !outFile.is_open() )
                    {
                        FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not open test set file for writing: " << testCSV;
                        retVal = GC_ERR;
                    }
                    else
                    {
                        for ( size_t i = 0; i < data[ 0 ].size(); ++i )
                        {
                            outFile << data[ 0 ][ i ];
                            if ( data[ 0 ].size() - 1 == i )
                                outFile << endl;
                            else
                                outFile << ",";
                        }
                        for ( int j = startTest; j < endTest; ++j )
                        {
                            for ( size_t i = 0; i < data[ j ].size(); ++i )
                            {
                                outFile << data[ j ][ i ];
                                if ( data[ j ].size() - 1 == i )
                                    outFile << endl;
                                else
                                    outFile << ",";
                            }
                        }
                        outFile.close();
                    }
                }
            }
        }
    }
    catch( boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }

    return retVal;
}

GC_STATUS CalcFeatures::SplitTestTrainSets( const string allCSV, const double percentTrain,
                                               const string trainCSV, const string testCSV )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        ifstream inFile( allCSV );
        if ( !inFile.is_open() )
        {
            FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not open for reading " << allCSV;
            retVal = GC_ERR;
        }
        else
        {
            string line;
            vector< string > rows;
            while( getline( inFile, line ) )
                rows.push_back( line );
            inFile.close();

            if ( rows.empty() )
            {
                FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] No data inspecified source file " << allCSV;
                retVal = GC_ERR;
            }
            else
            {
                vector< int > trainRows;
                string header = rows[ 0 ];
                retVal = PickNRandom( static_cast< int >( rows.size() ), cvRound( percentTrain * rows.size() ), trainRows );
                if ( GC_OK == retVal )
                {
                    vector< int > allRows;
                    for( size_t i = 1; i < rows.size(); ++i )
                    {
                        allRows.push_back( static_cast< int >( i ) );
                    }

                    for( size_t i = 1; i < trainRows.size(); ++i )
                    {
                        allRows[ trainRows[ i ] ] *= -1;
                    }

                   vector< string > rowSet;
                    for ( size_t i = 0; i < allRows.size(); ++i )
                    {
                        if ( 0 > allRows[ i ] )
                            rowSet.push_back( rows[ -allRows[ i ] ] );
                    }

                    ofstream outFile( trainCSV, ios_base::out );
                    if ( !outFile.is_open() )
                    {
                        FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not open CSV: " << trainCSV;
                        retVal = GC_ERR;
                    }
                    else
                    {
                        outFile << rows[ 0 ] << endl;
                        for ( size_t i = 0; i < rowSet.size(); ++i )
                        {
                            outFile << rowSet[ i ] << endl;
                        }
                        outFile.close();
                    }
                    FILE_LOG( logINFO ) << "Training set=" << rowSet.size();

                    rowSet.clear();
                    for ( size_t i = 0; i < allRows.size(); ++i )
                    {
                        if ( 0 < allRows[ i ] )
                            rowSet.push_back( rows[ allRows[ i ] ] );
                    }

                    outFile.open( testCSV, ios_base::out );
                    if ( !outFile.is_open() )
                    {
                        FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] Could not open CSV: " << testCSV;
                        retVal = GC_ERR;
                    }
                    else
                    {
                        outFile << rows[ 0 ] << endl;
                        for ( size_t i = 0; i < rowSet.size(); ++i )
                        {
                            outFile << rowSet[ i ] << endl;
                        }
                        outFile.close();
                    }
                    FILE_LOG( logINFO ) << "Test set=" << rowSet.size();
                }
            }
        }
    }
    catch( const boost::exception &e )
    {
        FILE_LOG( logERROR ) << "[CalcFeatures::SplitTestTrainSets] " << diagnostic_information( e );
        retVal = GC_EXCEPT;
    }
    return retVal;
}
GC_STATUS PickNRandom( const int total, const int numToPick, vector< int > &picks )
{
    GC_STATUS retVal = GC_OK;
    try
    {
        random_device rd;
        mt19937 rng( rd() );                       // seed rng using rd
        vector< int > data( total );               // create a 3-entry vector
        iota( data.begin(), data.end(), 0 );       // fill with sequence
        shuffle( data.begin(), data.end(), rng );  // mix entries using rng

        for ( size_t i = 0; i < static_cast< size_t >( numToPick ); ++i )
            picks.push_back( data[ i ] );
    }
    catch( const std::exception &e )
    {
        FILE_LOG( logERROR ) << "[PickNRandom] " << e.what();
        retVal = GC_EXCEPT;
    }
    return retVal;
}

} // namespace gc


#include "comfi.hpp"
#include <armadillo>
#include "viennacl/matrix.hpp"
#include "viennacl/matrix_proxy.hpp"
#include "viennacl/forwards.h"

using namespace arma;

vcl_mat comfi::operators::jm1(const vcl_mat &xn, comfi::types::Context ctx) {
  //make sure the dimension exists
  if ((ctx.bc_down == comfi::types::DIMENSIONLESS) ||
      (ctx.nz == 1)) {
    return xn;
  }
  //shift values
  vcl_mat xn_jm1(xn.size1(), xn.size2());
  viennacl::range eq(0, ctx.num_of_eq);
  static viennacl::range jm1(0, ctx.num_of_grid()-ctx.nx);
  static viennacl::range j(ctx.nx, ctx.num_of_grid());
  viennacl::project(xn_jm1, j, eq) = viennacl::project(xn, jm1, eq);

  //boundary conditions
  if (ctx.bc_down == comfi::types::NEUMANN) {
    static viennacl::range edge(0, ctx.nx);
    viennacl::project(xn_jm1, edge, eq) = viennacl::project(xn, edge, eq);
  }
  if (ctx.bc_down == comfi::types::PERIODIC) {
    static viennacl::range start(ctx.num_of_grid()-ctx.nx, ctx.num_of_grid());
    static viennacl::range edge(0, ctx.nx);
    viennacl::project(xn_jm1, edge, eq) = viennacl::project(xn, start, eq);
  }

  return xn_jm1;
}

vcl_mat comfi::operators::jp1(const vcl_mat &xn, comfi::types::Context ctx) {

  //make sure the dimension exists
  if ((ctx.bc_up == comfi::types::DIMENSIONLESS) ||
      (ctx.nz == 1)) {
    return xn;
  }

  //shift values
  vcl_mat xn_jp1(xn.size1(), xn.size2());
  static viennacl::range eq(0, ctx.num_of_eq);
  static viennacl::range jp1(inds(0, 1, ctx), ctx.num_of_grid());
  static viennacl::range j(0, ctx.num_of_grid()-ctx.nx);
  viennacl::project(xn_jp1, j, eq) = viennacl::project(xn, jp1, eq);

  //boundary conditions
  if (ctx.bc_up == comfi::types::NEUMANN) {
    static viennacl::range edge(ctx.num_of_grid()-ctx.nx, ctx.num_of_grid());
    viennacl::project(xn_jp1, edge, eq) = viennacl::project(xn, edge, eq);
  }
  if (ctx.bc_up == comfi::types::PERIODIC) {
    static viennacl::range edge(ctx.num_of_grid()-ctx.nx, ctx.num_of_grid());
    static viennacl::range start(0, ctx.nx);
    viennacl::project(xn_jp1, edge, eq) = viennacl::project(xn, start, eq);
  }

  return xn_jp1;
}

vcl_mat comfi::operators::ip1(const vcl_mat &xn, comfi::types::Context ctx) {

  //make sure the dimension exists
  if ((ctx.bc_right == comfi::types::DIMENSIONLESS) ||
      (ctx.nx == 1)) {
    return xn;
  }

  /* // TODO: Fix viennacl so that this method is working (preferred) */
  /* //shift values */
  /* vcl_mat xn_ip1(xn.size1(), xn.size2()); */
  /* viennacl::range eq(0, xn.size1()); */
  /* viennacl::range ip1[ctx.nz]; */
  /* viennacl::range i[ctx.nz]; */
  /* #pragma omp parallel for schedule(dynamic) */
  /* for(uint j = 0; j < ctx.nz; j++) { */
  /*    ip1[j] = viennacl::range(inds(1, j, ctx), inds(ctx.nx-1, j, ctx)+1); */
  /*    i[j] = viennacl::range(inds(0, j, ctx), inds(ctx.nx-2, j, ctx)+1); */
  /* } */
  /* /1* #pragma omp parallel for *1/ */
  /* for(uint j = 0; j < ctx.nz; j++) { */
  /*   viennacl::project(xn_ip1, eq, i[j]) = viennacl::project(xn, eq, ip1[j]); */
  /* } */

  /* //boundary conditions */
  /* if (ctx.bc_right == comfi::types::NEUMANN) { */
  /*   viennacl::slice eq(0, 1, ctx.num_of_eq); */
  /*   viennacl::slice edge(inds(ctx.nx-1, 0, ctx), ctx.nx, ctx.nz); */
  /*   viennacl::project(xn_ip1, edge, eq) = viennacl::project(xn, edge, eq); */
  /* } */
  /* if (ctx.bc_right == comfi::types::PERIODIC) { */
  /*   viennacl::slice eq(0, 1, ctx.num_of_eq); */
  /*   viennacl::slice edge(inds(ctx.nx-1, 0, ctx), ctx.nx, ctx.nz); */
  /*   viennacl::slice start(0, ctx.nx, ctx.nz); */
  /*   viennacl::project(xn_ip1, edge, eq) = viennacl::project(xn, start, eq); */
  /* } */

  static const sp_mat cpu_Pip1 = comfi::operators::buildPip1(ctx);
  static vcl_sp_mat Pip1(ctx.num_of_grid(), ctx.num_of_grid());
  static bool created = false;
  if (!created) { viennacl::copy(cpu_Pip1, Pip1); created = true; }
  vcl_mat xn_ip1 = viennacl::linalg::prod(Pip1, xn);

  return xn_ip1;
}

vcl_mat comfi::operators::im1(const vcl_mat &xn, comfi::types::Context ctx) {
  //make sure the dimension exists
  if ((ctx.bc_left == comfi::types::DIMENSIONLESS) ||
      (ctx.nx == 1)) {
    return xn;
  }

  /* // TODO: Make this method work through changing viennacl (preferred) */
  /* //shift values */
  /* vcl_mat xn_im1(xn.size1(), xn.size2()); */
  /* viennacl::range eq(0, xn.size1()); */
  /* viennacl::range im1[ctx.nz]; */
  /* viennacl::range i[ctx.nz]; */
  /* #pragma omp parallel for schedule(dynamic) */
  /* for(uint j = 0; j < ctx.nz; j++) { */
  /*   im1[j] = viennacl::range(inds(0, j, ctx), inds(ctx.nx()-2, j, ctx)+1); */
  /*   i[j] = viennacl::range(inds(1, j, ctx), inds(ctx.nx()-1, j, ctx)+1); */
  /* } */
  /* for(uint j = 0; j < ctx.nz(); j++) { */
  /*   viennacl::project(xn_im1, eq, i[j]) = viennacl::project(xn, eq, im1[j]); */
  /* } */

  /* //boundary conditions */
  /* if (ctx.bc_left == comfi::types::NEUMANN) { */
  /*   viennacl::slice eq(0, 1, ctx.num_of_eq); */
  /*   viennacl::slice start(0, ctx.nx, ctx.nz); */
  /*   viennacl::project(xn_im1, start, eq) = viennacl::project(xn, start, eq); */
  /* } */
  /* if (ctx.bc_left == comfi::types::PERIODIC) { */
  /*   viennacl::slice eq(0, 1, ctx.num_of_eq); */
  /*   viennacl::slice edge(inds(ctx.nx-1, 0, ctx), ctx.nx, ctx.nz); */
  /*   viennacl::slice start(0, ctx.nx, ctx.nz); */
  /*   viennacl::project(xn_im1, start, eq) = viennacl::project(xn, edge, eq); */
  /* } */

  static const sp_mat cpu_Pim1 = comfi::operators::buildPim1(ctx);
  static vcl_sp_mat Pim1(ctx.num_of_grid(), ctx.num_of_grid());
  static bool created = false;
  if (!created) { viennacl::copy(cpu_Pim1, Pim1); created = true; }
  vcl_mat xn_im1 = viennacl::linalg::prod(Pim1, xn);

  return xn_im1;
}

const sp_mat comfi::operators::buildPip1(comfi::types::Context &ctx) {
  umat locations;
  urowvec loci = zeros<urowvec>(ctx.num_of_grid());
  urowvec locj = zeros<urowvec>(ctx.num_of_grid());
  vec values = ones<vec>(ctx.num_of_grid());
  const comfi::types::BoundaryCondition BC = ctx.bc_right;

  #pragma omp parallel for collapse(2)
  for (uint i=0; i<ctx.nx; i++){ for(uint j=0; j<ctx.nz; j++) {
    // indexing
    const int           ij = inds(i, j, ctx);
    int                 ip1j = inds(i+1, j, ctx);

    //BC
    if (i==ctx.nx-1 && (BC==comfi::types::MIRROR || BC==comfi::types::NEUMANN)) { ip1j = ij; }
    else if (i==ctx.nx-1 && BC==comfi::types::PERIODIC) { ip1j = inds(0, j, ctx); }
    else if (BC==comfi::types::DIMENSIONLESS) { ip1j = inds(0, j, ctx); }

    loci(ij) = ip1j;
    locj(ij) = ij;
  }}

  locations.insert_rows(0, loci);
  locations.insert_rows(1, locj);
  return sp_mat(true, locations, values, ctx.num_of_grid(), ctx.num_of_grid());
}

const sp_mat comfi::operators::buildPim1(comfi::types::Context &ctx) {
  umat locations;
  urowvec loci = zeros<urowvec>(ctx.num_of_grid());
  urowvec locj = zeros<urowvec>(ctx.num_of_grid());
  vec values = ones<vec>(ctx.num_of_grid());
  const comfi::types::BoundaryCondition BC = ctx.bc_left;

  #pragma omp parallel for collapse(2)
  for (uint i=0; i<ctx.nx; i++){ for(uint j=0; j<ctx.nz; j++) {
    // indexing
    const int           ij = inds(i, j, ctx);
    int                 im1j = inds(i-1, j, ctx);

    //BC
    if (i==0 && (BC==comfi::types::MIRROR || BC==comfi::types::NEUMANN)) { im1j = ij; }
    else if (i==0 && BC==comfi::types::PERIODIC) { im1j = inds(ctx.nx-1, j, ctx);}
    else if (BC==comfi::types::DIMENSIONLESS) { im1j = inds(0, j, ctx);}

    loci(ij) = im1j;
    locj(ij) = ij;
  }}

  locations.insert_rows(0, loci);
  locations.insert_rows(1, locj);
  return sp_mat(true, locations, values, ctx.num_of_grid(), ctx.num_of_grid());
}

/*
vim: tabstop=2
vim: shiftwidth=2
vim: smarttab
vim: expandtab
*/


#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/class.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/copy_const_reference.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/str.hpp>
#include <boost/python/operators.hpp>
#include <boost/rational.hpp>

namespace {

  struct rational_int_wrappers
  {
    typedef boost::rational<int> w_t;

    static int
    as_int(w_t const& o)
    {
      if (o.denominator() != 1) {
        throw std::runtime_error(
          "boost.rational: as_int() conversion error:"
          " denominator is different from one.");
      }
      return o.numerator();
    }

    static double
    as_double(w_t const& o)
    {
      return double(o.numerator()) / o.denominator();
    }

    static boost::python::tuple
    as_tuple(w_t const& o)
    {
      return boost::python::make_tuple(o.numerator(), o.denominator());
    }

    static boost::python::str
    as_str(w_t const& o)
    {
      using boost::python::str;
      if (o.denominator() == 1) {
        return str(o.numerator());
      }
      return str(str(o.numerator()) + "/" + str(o.denominator()));
    }

    static long
    hash(w_t const& o)
    {
      // http://docs.python.org/ref/customization.html
      // Python-2.4.3/Objects/intobject.c, int_hash()
      long result;
      if (o.denominator() == 1) {
        result = o.numerator();
      }
      else {
        result = o.denominator() << 16 ^ o.numerator();
      }
      if (result == -1) result = -2;
      return result;
    }

    static w_t
    abs(w_t const& o)
    {
      if (o < 0) return -o;
      return o;
    }

    static w_t div_rr(w_t const& lhs, w_t const& rhs) { return lhs/rhs; }
    static w_t div_ri(w_t const& lhs, int rhs) { return lhs/rhs; }
    static w_t rdiv_ir(w_t const& rhs, int lhs) { return lhs/rhs; }

    static int floordiv_rr(w_t const& lhs, w_t const& rhs)
    {
      w_t div = lhs / rhs;
      int n = div.numerator();
      int d = div.denominator();
      int result = n / d;
      if (n < 0 && result * d != n) result--;
      return result;
    }
    static int floordiv_ri(w_t const& lhs, int rhs)
    {
      return floordiv_rr(lhs, rhs);
    }
    static int rfloordiv_ir(w_t const& rhs, int lhs)
    {
      return floordiv_rr(lhs, rhs);
    }

    static w_t mod_rr(w_t const& lhs, w_t const& rhs)
    {
      return lhs - rhs * floordiv_rr(lhs, rhs);
    }
    static w_t mod_ri(w_t const& lhs, int rhs) { return mod_rr(lhs, rhs); }
    static w_t rmod_ir(w_t const& rhs, int lhs) { return mod_rr(lhs, rhs); }

    static double add_rd(w_t const& lhs, double rhs)
    {
      return as_double(lhs) + rhs;
    }
    static double sub_rd(w_t const& lhs, double rhs)
    {
      return as_double(lhs) - rhs;
    }
    static double sub_dr(w_t const& rhs, double lhs)
    {
      return lhs - as_double(rhs);
    }
    static double mul_rd(w_t const& lhs, double rhs)
    {
      return as_double(lhs) * rhs;
    }
    static double div_dd(double lhs, double rhs)
    {
      if (rhs == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "float division by zero");
        boost::python::throw_error_already_set();
      }
      return lhs / rhs;
    }
    static double div_rd(w_t const& lhs, double rhs)
    {
      return div_dd(as_double(lhs), rhs);
    }
    static double div_dr(w_t const& rhs, double lhs)
    {
      return div_dd(lhs, as_double(rhs));
    }

    static bool eq_rr(w_t const& lhs, w_t const& rhs) { return lhs == rhs; }
    static bool ne_rr(w_t const& lhs, w_t const& rhs) { return lhs != rhs; }
    static bool lt_rr(w_t const& lhs, w_t const& rhs) { return lhs < rhs; }
    static bool gt_rr(w_t const& lhs, w_t const& rhs) { return lhs > rhs; }
    static bool le_rr(w_t const& lhs, w_t const& rhs) { return lhs <= rhs; }
    static bool ge_rr(w_t const& lhs, w_t const& rhs) { return lhs >= rhs; }

    static bool eq_ri(w_t const& lhs, int rhs) { return lhs == rhs; }
    static bool ne_ri(w_t const& lhs, int rhs) { return lhs != rhs; }
    static bool lt_ri(w_t const& lhs, int rhs) { return lhs < rhs; }
    static bool gt_ri(w_t const& lhs, int rhs) { return lhs > rhs; }
    static bool le_ri(w_t const& lhs, int rhs) { return lhs <= rhs; }
    static bool ge_ri(w_t const& lhs, int rhs) { return lhs >= rhs; }

    static void
    wrap()
    {
      using namespace boost::python;
// From this version onward, numerator() and denominator() are constexpr
// and this requires adjustments to Boost Python binding.
#define BOOST_ADAPTBX_RATIONAL_EXT_BOOST_VERSION_API_CHANGE 105800
#if BOOST_VERSION >= BOOST_ADAPTBX_RATIONAL_EXT_BOOST_VERSION_API_CHANGE
      return_value_policy<copy_const_reference> ccr;
#endif
      class_<w_t>("int")
        .def(init<w_t>())
        .def(init<int, optional<int> >())
#if BOOST_VERSION >= BOOST_ADAPTBX_RATIONAL_EXT_BOOST_VERSION_API_CHANGE
        .def("numerator", &w_t::numerator, ccr)
        .def("denominator", &w_t::denominator, ccr)
#else
        .def("numerator", &w_t::numerator)
        .def("denominator", &w_t::denominator)
#endif
        .def("__int__", as_int)
        .def("__float__", as_double)
        .def("as_tuple", as_tuple)
        .def("__str__", as_str)
        .def("__repr__", as_str)
        .def("__hash__", hash)
        .def("__abs__", abs)
        //
        .def("__add__", add_rd)
        .def("__radd__", add_rd)
        .def("__sub__", sub_rd)
        .def("__rsub__", sub_dr)
        .def("__mul__", mul_rd)
        .def("__rmul__", mul_rd)
        .def("__div__", div_rd)
        .def("__rdiv__", div_dr)
        .def("__truediv__", div_rd)
        .def("__rtruediv__", div_dr)
        //
        .def(-self)
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def("__div__", div_rr)
        .def("__truediv__", div_rr)
        .def("__floordiv__", floordiv_rr)
        .def("__mod__", mod_rr)
        .def(self + int())
        .def(self - int())
        .def(self * int())
        .def("__div__", div_ri)
        .def("__truediv__", div_ri)
        .def("__floordiv__", floordiv_ri)
        .def("__mod__", mod_ri)
        .def(int() + self)
        .def(int() - self)
        .def(int() * self)
        .def("__rdiv__", rdiv_ir)
        .def("__rtruediv__", rdiv_ir)
        .def("__rfloordiv__", rfloordiv_ir)
        .def("__rmod__", rmod_ir)
        .def("__eq__", eq_rr)
        .def("__ne__", ne_rr)
        .def("__lt__", lt_rr)
        .def("__gt__", gt_rr)
        .def("__le__", le_rr)
        .def("__ge__", ge_rr)
        .def("__eq__", eq_ri)
        .def("__ne__", ne_ri)
        .def("__lt__", lt_ri)
        .def("__gt__", gt_ri)
        .def("__le__", le_ri)
        .def("__ge__", ge_ri)
        .enable_pickling()
        .def("__getinitargs__", as_tuple)
      ;
    }
  };

  void init_module()
  {
    using namespace boost::python;

    rational_int_wrappers::wrap();
    def("gcd", (int(*)(int,int))boost::gcd);
    def("lcm", (int(*)(int,int))boost::lcm);
  }

} // namespace <anonymous>

BOOST_PYTHON_MODULE(boost_rational_ext)
{
  init_module();
}


//
// Created by michel on 21-08-19.
//

#include <simple-dsp/util/delay.h>

#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
#include <iostream>

namespace {
constexpr const size_t SIZE = 16;
simpledsp::WrappedIndexFor<true, metric(SIZE);
using ReadFirstDelay =
    simpledsp::util::DelayBasics<simpledsp::util::DelayAccessType::READ_THEN_WRITE>;
using WriteFirstDelay =
    simpledsp::util::DelayBasics<simpledsp::util::DelayAccessType::WRITE_THEN_READ>;

enum class Configuration { WRITE_THEN_DELTA_READ, READ_THEN_DELTA_WRITE };

struct Delay {
  void configure(size_t value, size_t delay_) {
    if (config == Configuration::READ_THEN_DELTA_WRITE) {
      readPtr = value;
      writePtr = setWriteForDelay(delay_);
    } else {
      writePtr = value;
      readPtr = setReadForDelay(delay_);
    }
    delay = delay_;
  }

  void test() {
    int failures = 0;
    int expected = 0;
    int actual = 0;
    for (size_t time = 0; time <= delay + metric.size(); time++) {
      int delayed = access(time);
      if (time >= delay) {
        if (delayed != int(time - delay)) {
          if (failures == 0) {
            expected = time - delay;
            actual = delayed;
            std::cout << "FAIL time=" << time << "; write=" << writePtr
                      << "; read=" << readPtr << "; buffer:";
            for (size_t i = 0; i < SIZE; i++) {
              if (i == writePtr) {
                if (i == readPtr) {
                  std::cout << " RW";
                } else {
                  std::cout << "  W";
                }
              } else if (i == readPtr) {
                std::cout << "  Result";
              } else {
                std::cout << "   ";
              }

              std::cout << "[" << i << "]=" << act_values[i];
            }
            std::cout << std::endl;
          }
          failures++;
        }
      }
      metric.setNext(writePtr);
      metric.setNext(readPtr);
    }
    if (failures) {
      BOOST_CHECK_EQUAL(expected, actual);
    }
  }

protected:
  virtual size_t setReadForDelay(size_t delay) = 0;
  virtual size_t setWriteForDelay(size_t delay) = 0;
  virtual int access(int newValue) = 0;
  Configuration config;
  size_t readPtr = 0;
  size_t writePtr = 0;
  size_t delay;
  int act_values[SIZE];
};

struct ReadThenWriteDelay : public Delay {
protected:
  size_t setReadForDelay(size_t delay_) override {
    return ReadFirstDelay::getReadPtrForDelay(metric, writePtr, delay_);
  }
  size_t setWriteForDelay(size_t delay_) override {
    return ReadFirstDelay::getWritePtrForDelay(metric, readPtr, delay_);
  };
  int access(int newValue) override {
    return ReadFirstDelay::access(act_values + writePtr, act_values + readPtr,
                                  newValue);
  }
};

struct WriteThenReadDelay : public Delay {
protected:
  size_t setReadForDelay(size_t delay_) override {
    return WriteFirstDelay::getReadPtrForDelay(metric, writePtr, delay_);
  }
  size_t setWriteForDelay(size_t delay_) override {
    return WriteFirstDelay::getWritePtrForDelay(metric, readPtr, delay_);
  };
  int access(int newValue) override {
    return WriteFirstDelay::access(act_values + writePtr, act_values + readPtr,
                                   newValue);
  }
};

struct DelayTestParameters {
  size_t firstValue;
  size_t delay;
  simpledsp::util::DelayAccessType type;
  Configuration config;

  std::ostream &print(std::ostream &out) const {
    out << (type == simpledsp::util::DelayAccessType::READ_THEN_WRITE
                ? "ReadFirstDelay"
                : "WriteFirstDelay");
    out << "(";
    out << (config == Configuration::READ_THEN_DELTA_WRITE ? "read="
                                                           : "write=");
    out << firstValue;
    out << "; delay=";
    out << delay;
    out << ")";
    return out;
  }

  void test() const {
    ReadThenWriteDelay rtwDelay;
    WriteThenReadDelay wtrDelay;
    Delay &delayImpl = (type == simpledsp::util::DelayAccessType::READ_THEN_WRITE)
                           ? *static_cast<Delay *>(&rtwDelay)
                           : *static_cast<Delay *>(&wtrDelay);
    delayImpl.configure(firstValue, delay);
    delayImpl.test();
  }
};

class TestCaseGenerator {
  std::vector<DelayTestParameters> parameters;

public:
  TestCaseGenerator() {
    for (size_t start = 0; start < metric.size(); start++) {
      for (Configuration config : {Configuration::READ_THEN_DELTA_WRITE,
                                   Configuration::WRITE_THEN_DELTA_READ}) {
        for (size_t delay = 1; delay <= metric.size(); delay++) {
          parameters.push_back({start, delay,
                                simpledsp::util::DelayAccessType::READ_THEN_WRITE,
                                config});
        }
        for (size_t delay = 0; delay < metric.size(); delay++) {
          parameters.push_back({start, delay,
                                simpledsp::util::DelayAccessType::WRITE_THEN_READ,
                                config});
        }
      }
    }
  }

  const std::vector<DelayTestParameters> testSamples() { return parameters; }
} TEST_GENERATOR;
} // namespace

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
namespace std {
std::ostream &operator<<(std::ostream &stream, const DelayTestParameters &s) {
  s.print(stream);
  return stream;
}
} // namespace std
#pragma clang diagnostic pop

BOOST_AUTO_TEST_SUITE(DelayTests)

// TODO: need many more tests, and useful ones at that...

BOOST_AUTO_TEST_CASE(testReadFirstZeroDelayInvalid) {
  size_t r;
  BOOST_CHECK_THROW(r = ReadFirstDelay::getValidDelay(metric, size_t(0)),
                    std::invalid_argument);
}

BOOST_AUTO_TEST_CASE(testWriteFirstZeroDelayIsZero) {
  BOOST_CHECK_EQUAL(WriteFirstDelay::getValidDelay(metric, size_t(0)), 0);
}

BOOST_AUTO_TEST_CASE(testReadFirstMaxDelayIsMax) {
  BOOST_CHECK_EQUAL(ReadFirstDelay::getValidDelay(metric, metric.size()),
                    metric.size());
}

BOOST_AUTO_TEST_CASE(testWriteFirsMaxDelayIsInvalid) {
  size_t r;
  BOOST_CHECK_THROW(
      r = WriteFirstDelay::getValidDelay(metric, metric.size()),
      std::invalid_argument);
}

BOOST_DATA_TEST_CASE(sample, TEST_GENERATOR.testSamples()) { sample.test(); }

BOOST_AUTO_TEST_SUITE_END()


#include <boost/graph/random_layout.hpp>


//////////////////////////////////////////////////////////////////////////////////
// survival::models::exponential::scalar::include.hpp                         	//
//                                                                              //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                   //
//  Software License, Version 1.0. (See accompanying file                       //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)            //
//////////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_EXPONENTIAL_SCALAR_INCLUDE_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_EXPONENTIAL_SCALAR_INCLUDE_HPP_ER_2009

#include <boost/statistics/detail/distribution/survival/models/exponential/scalar/model.hpp>
#include <boost/statistics/detail/distribution/survival/models/exponential/scalar/meta/include.hpp>
#include <boost/statistics/detail/distribution/survival/models/exponential/scalar/log_rate/include.hpp>
#include <boost/statistics/detail/distribution/survival/models/exponential/scalar/function/include.hpp>

#endif


//#define NO_OPERATORS
#define BOOST_USE_WINDOWS_H
#include <render/vdbPolyRender/vdb_poly_render.h>
#include <render/util/color_map.h>

static const char *vertexShaderSource = R"(#version 450
in vec4 posAttr;
in vec3 normAttr;

uniform mat4 perspective_matrix;
uniform mat4 view_matrix;

out vec4 eyeSpacePos;
out vec3 normal;

void main() {
	eyeSpacePos = view_matrix * vec4(posAttr.xyz ,1.f);
	gl_Position = perspective_matrix * eyeSpacePos;
	normal = normalize(normAttr);
})";

static const char *fragmentShaderSource = R"(#version 450
in vec3 normal;
in vec4 eyeSpacePos;

uniform mat4 perspective_matrix;

void main() {
	vec3 lightDir = normalize(vec3(0, 0, -1));
	vec3 color = vec3(1,1,1);
	float diffuse = abs(max(0.f, dot(normal, lightDir)));

	gl_FragColor = vec4(color * diffuse,1.f);
})";

bool volumeBoundaryRender::valid() { return get<parameters::volumeBoundary>() == true; }

void volumeBoundaryRender::update() { colorMap::instance().update(); }
#undef foreach
#ifdef _WIN32
#pragma warning(push, 0)
#endif
#include <openvdb/Platform.h>
#include <openvdb/openvdb.h>
#include <openvdb/tools/LevelSetFilter.h>
#include <openvdb/tools/ParticlesToLevelSet.h>
#include <openvdb/tools/VolumeToMesh.h>
#include <openvdb/tree/ValueAccessor.h>
#ifdef _WIN32
#pragma warning(pop)
#endif
#ifdef _WIN32
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#endif
#include <fstream>
#include <utility/include_all.h>

volumeBoundaryRender::volumeBoundaryRender(OGLWidget *parent) {
  initializeOpenGLFunctions();
  m_program = new QOpenGLShaderProgram(parent);
  m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
  m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  std::vector<uint32_t> indices;
  std::vector<openvdb::Vec3f> pyramidVertices;
  std::vector<openvdb::Vec3f> pyramidNormals;

  for (auto boundaryVolume : get<parameters::boundaryVolumes>()) {
    std::string vdbFile = get<parameters::config_folder>() + boundaryVolume.fileName.value;
    auto std_file = fs::path(vdbFile);
    auto tmp_file = std_file;
    tmp_file.replace_extension(".vdb_tmp");

    openvdb::Vec3d min, max;
    int4 dimension;

    logger(log_level::info) << "Parsing VDB file: " << std_file;
    openvdb::initialize();
    openvdb::io::File file(vdbFile);

    openvdb::FloatGrid::Ptr levelSet = openvdb::FloatGrid::create();
    levelSet->setTransform(openvdb::math::Transform::createLinearTransform(0.5));
    levelSet->setGridClass(openvdb::GRID_LEVEL_SET);
    levelSet->setName("LevelSet");

    file.open();
    auto grids = file.getGrids();
    openvdb::GridBase::Ptr baseGrid;
    for (openvdb::io::File::NameIterator nameIter = file.beginName(); nameIter != file.endName();
         ++nameIter) {
      if (nameIter.gridName() == "surface") {
        baseGrid = file.readGrid(nameIter.gridName());
      }
    }
    file.close();

    levelSet = openvdb::gridPtrCast<openvdb::FloatGrid>(baseGrid);
    openvdb::FloatGrid::ConstAccessor c_accessor = levelSet->getConstAccessor();

    // openvdb::math::UniformScaleMap map(0.5);

    openvdb::FloatGrid::ConstAccessor naccessor = levelSet->getConstAccessor();
    openvdb::tools::GridSampler<openvdb::FloatGrid::ConstAccessor, openvdb::tools::BoxSampler>
        normalSampler(naccessor, levelSet->transform());

    openvdb::math::ScaleMap scalemap(levelSet->voxelSize());

    dimension = int4{256, 256, 256, 0};
    openvdb::CoordBBox box = levelSet->evalActiveVoxelBoundingBox();
    min = levelSet->indexToWorld(box.getStart());
    max = levelSet->indexToWorld(box.getEnd());

    openvdb::tools::GridSampler<openvdb::FloatGrid, openvdb::tools::BoxSampler> d_sampler(
        *levelSet);

    openvdb::tools::VolumeToMesh mesher(levelSet->getGridClass() == openvdb::GRID_LEVEL_SET ? 0.0
                                                                                : 0.01);
    mesher(*levelSet);

    // Copy points and generate point normals.
    std::vector<float> points(mesher.pointListSize() * 3);
    std::vector<float> normals(mesher.pointListSize() * 3);

    openvdb::tree::ValueAccessor<const typename openvdb::FloatGrid::TreeType> acc(levelSet->tree());
    openvdb::math::GenericMap map(levelSet->transform());
    openvdb::Coord ijk;

    for (openvdb::Index64 n = 0, i = 0, N = mesher.pointListSize(); n < N; ++n) {
      const openvdb::Vec3s &p = mesher.pointList()[n];
      points[i++] = p[0];
      points[i++] = p[1];
      points[i++] = p[2];
    }

    // Copy primitives
    openvdb::tools::PolygonPoolList &polygonPoolList = mesher.polygonPoolList();
    openvdb::Index64 numQuads = 0;
    for (openvdb::Index64 n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) {
      numQuads += polygonPoolList[n].numQuads();
    }

    indices.reserve(numQuads * 4);
    openvdb::Vec3d normal, e1, e2;

    for (openvdb::Index64 n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) {
      const openvdb::tools::PolygonPool &polygons = polygonPoolList[n];
      for (openvdb::Index64 i = 0, I = polygons.numQuads(); i < I; ++i) {
        const openvdb::Vec4I &quad = polygons.quad(i);
        indices.push_back(quad[0]);
        indices.push_back(quad[1]);
        indices.push_back(quad[2]);
        indices.push_back(quad[3]);

        e1 = mesher.pointList()[quad[1]];
        e1 -= mesher.pointList()[quad[0]];
        e2 = mesher.pointList()[quad[2]];
        e2 -= mesher.pointList()[quad[0]];
        normal = e1.cross(e2);

        const double length = normal.length();
        if (length > 1.0e-7)
          normal *= (1.0 / length);

        for (openvdb::Index64 v = 0; v < 4; ++v) {
          normals[quad[v] * 3] = static_cast<float>(normal[0]);
          normals[quad[v] * 3 + 1] = static_cast<float>(normal[1]);
          normals[quad[v] * 3 + 2] = static_cast<float>(normal[2]);
        }
      }
    }

    for (uint32_t it = 0; it < mesher.pointListSize(); ++it) {
      pyramidVertices.push_back(
          openvdb::Vec3f{points[it * 3 + 0], points[it * 3 + 1], points[it * 3 + 2]});
      pyramidNormals.push_back(
          openvdb::Vec3f{normals[it * 3 + 0], normals[it * 3 + 1], normals[it * 3 + 2]});
    }
    break;
  }

  m_program->link();
  m_posAttr = m_program->attributeLocation("posAttr");
  m_colAttr = m_program->attributeLocation("normAttr");

  std::vector<uint32_t> idx = {// front
                               0, 1, 2, 3,
                               // top
                               1, 5, 6, 2,
                               // back
                               7, 6, 5, 4,
                               // bottom
                               4, 0, 3, 7,
                               // left
                               4, 5, 1, 0,
                               // right
                               3, 2, 6, 7};
  glGenBuffers(1, &IBO);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(uint32_t), indices.data(),
               GL_STATIC_DRAW);

  parent->bind(m_program);

  glGenBuffers(1, &VXO);
  glBindBuffer(GL_ARRAY_BUFFER, VXO);
  glBufferData(GL_ARRAY_BUFFER, pyramidVertices.size() * sizeof(QVector3D), pyramidVertices.data(),
               GL_STATIC_DRAW);
  // glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  glEnableVertexAttribArray(m_posAttr);
  glVertexAttribPointer(m_posAttr, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  //GLfloat uvs[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};

  glGenBuffers(1, &VUV);
  glBindBuffer(GL_ARRAY_BUFFER, VUV);
  // glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW);
  glBufferData(GL_ARRAY_BUFFER, pyramidNormals.size() * sizeof(QVector3D), pyramidNormals.data(),
               GL_STATIC_DRAW);

  glEnableVertexAttribArray(m_colAttr);
  glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glBindVertexArray(0);
  colorMap::instance().bind(m_program, 0, "colorRamp");
  update();
}

void volumeBoundaryRender::render(bool pretty) {
  glBindVertexArray(vao);

  m_program->bind();

  int size;
  glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
  glDrawElements(GL_QUADS, size / sizeof(int32_t), GL_UNSIGNED_INT, 0);
  // glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0,
  // get<parameters::num_ptcls>());

  m_program->release();
  glBindVertexArray(0);
}


#ifndef PYTHONIC_INCLUDE_NUMPY_RECIPROCAL_HPP
#define PYTHONIC_INCLUDE_NUMPY_RECIPROCAL_HPP

#include "pythonic/include/utils/functor.hpp"
#include "pythonic/include/types/ndarray.hpp"
#include "pythonic/include/utils/numpy_traits.hpp"
#include <boost/simd/function/rec.hpp>

PYTHONIC_NS_BEGIN

namespace numpy
{
#define NUMPY_NARY_FUNC_NAME reciprocal
#define NUMPY_NARY_FUNC_SYM boost::simd::rec
#include "pythonic/include/types/numpy_nary_expr.hpp"
}
PYTHONIC_NS_END

#endif


#include <MetaValueImplementation.h>
#include <MetaScale.h>
#include <MetaFactory.h>
#include <IO.h>

#include <boost/mpl/range_c.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/back_inserter.hpp>

#include <memory>
#include <ostream>

using namespace std;
using Interface = MetaValueBaseImplementation::Interface;
using Ptr = MetaValueBaseImplementation::Ptr;
using Data = MetaValueBaseImplementation::Data;
using UnaryOp = MetaValueBaseImplementation::UnaryOp;
using BinaryOp = MetaValueBaseImplementation::BinaryOp;
using BinaryConstOp = MetaValueBaseImplementation::BinaryConstOp;
using MVBI = MetaValueBaseImplementation;

template<typename T, bool U>
Ptr inverseHelper(const Value<T, Eigen::Dynamic, Eigen::Dynamic, U>& v) {
  return Interface::instance();
}

template<bool U>
Ptr inverseHelper(const Value<float, Eigen::Dynamic, Eigen::Dynamic, U>& v) {
  using MVI = MetaValueImplementation<float, U>;
  using Base = typename MVI::Base;
  MVI temp(Base(v.inverse()));
  return temp.copy();
}

template<bool U>
Ptr inverseHelper(const Value<double, Eigen::Dynamic, Eigen::Dynamic, U>& v) {
  using MVI = MetaValueImplementation<double, U>;
  using Base = typename MVI::Base;
  MVI temp(Base(v.inverse()));
  return temp.copy();
}

template<typename T, bool U>
using MVI = MetaValueImplementation<T, U>;

template<bool U>
using Bool = MetaValueImplementation<bool, U>;

template<typename T, bool U>
const uint8_t* MetaValueImplementation<T, U>::begin() const {
  return reinterpret_cast< const uint8_t* >( mData.data().data() );
}

template<typename T, bool U>
const uint8_t* MetaValueImplementation<T, U>::end() const {
  return reinterpret_cast< const uint8_t* >( mData.data().data() )
          +ValueElement<T, U>::size() * mData.rows() * mData.cols();

}

template<typename T, bool U>
uint8_t* MetaValueImplementation<T, U>::begin() {
  return reinterpret_cast< uint8_t* >( mData.data().data() );
}

template<typename T, bool U>
uint8_t* MetaValueImplementation<T, U>::end() {
  return reinterpret_cast< uint8_t* >( mData.data().data() )
          +ValueElement<T, U>::size()  *mData.rows() * mData.cols();
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::factoryCreate(std::size_t rows, std::size_t cols, bool u) {
  return Ptr(new MetaValueImplementation<T, U>(rows, cols));
}

template<typename T, bool U>
MetaValueImplementation<T, U>::MetaValueImplementation(std::size_t rows, std::size_t cols)
  : mData(rows, cols)
{}

template<typename T, bool U>
MetaValueImplementation<T, U>::MetaValueImplementation(typename Base::InitType values)
  : mData(values)
{}

template<typename T, bool U>
MetaValueImplementation<T, U>::MetaValueImplementation(const typename MVI<T, U>::Base& data)
  : mData(data)
{}

template<typename T, bool U>
Interface& MetaValueImplementation<T, U>::operator=( Interface&& movee) {
 mData = move(reinterpret_cast<MVI<T, U>&&>(movee).mData);
 return *this;
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::copy() const {
  return Ptr(new MetaValueImplementation(*this));
}

template<typename T, bool U>
Data MetaValueImplementation<T, U>::get( Attributes a ) const {
  Data res;
  switch(a) {
    case(Attributes::TypeID):         res.typeID = id::type::id(T());
                                      break;
    case(Attributes::Cols):           res.cols = mData.cols();
                                      break;
    case(Attributes::Rows):           res.rows = mData.rows();
                                      break;
    case(Attributes::Size):           res.size = Elem::size()*mData.cols()*mData.rows();
                                      break;
    case(Attributes::HasUncertainty): res.hasUncertainty = U;
                                      break;
    default:                          return Interface::get(a);
  }
  return res;
}
template<typename T, bool U>
ValueElement<double, true> MetaValueImplementation<T, U>::get(size_t row, size_t col) const {
  if(row < (size_t)mData.rows() && col < (size_t)mData.cols())
    return mData(row, col);
  else
    return ValueElement<double, true>();
}

template<typename T, bool U>
bool MetaValueImplementation<T, U>::set(size_t row, size_t col, ElemInitType elem) {
  if(row < (size_t)mData.rows() && col < (size_t)mData.cols()) {
    mData(row, col) = ValueElement<double, true>(elem);
    return true;
  }
  return false;
}

template<typename T, bool U>
bool MetaValueImplementation<T, U>::set(Attributes a, Data d) {
  switch(a) {
    case(Attributes::TypeID):         return false;
    case(Attributes::Cols)  :         mData.conservativeResize(Eigen::NoChange_t() ,d.cols);
                                      return true;
    case(Attributes::Rows):           mData.conservativeResize(d.rows, Eigen::NoChange_t());
                                      return true;
    case(Attributes::Size):           return false;
    case(Attributes::HasUncertainty): return false;
    default:                          return Interface::set(a, d);
  }
}

template<typename T, bool U>
bool MetaValueImplementation<T, U>::unaryOp( UnaryOp op)  {
  switch(op) {
    case(UnaryOp::Sin): mData.sin();
                         break;
    case(UnaryOp::Cos): mData.cos();
                         break;
    case(UnaryOp::Tan): mData.tan();
                         break;
    case(UnaryOp::ASin): mData.asin();
                         break;
    case(UnaryOp::ACos): mData.acos();
                         break;
    case(UnaryOp::ATan): mData.atan();
                         break;
    case(UnaryOp::Abs): mData.abs();
                         break;
    case(UnaryOp::Sqrt): mData.sqrt();
                         break;
    case(UnaryOp::Min):
    case(UnaryOp::Max): /** \todo implement min and max**/
    default           : return Interface::unaryOp(op);
  }
  return true;
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::unaryConstOp( UnaryConstOp op) const {
  Ptr ptr;
  switch(op) {
    case(UnaryConstOp::Neg): {
      MetaValueImplementation<T, U> temp;
      temp.mData = -mData;
      ptr = temp.copy(); }
      break;
    case(UnaryConstOp::Prod): {
      MetaValueImplementation<T, U> temp = *this;
      temp.mData.resize(1,1);
      temp.mData(0)=mData.prod();
      ptr = temp.copy(); }
      break;
    case(UnaryConstOp::Sum): {
      MetaValueImplementation<T, U> temp = *this;
      temp.mData.resize(1,1);
      temp.mData(0)=mData.sum();
      ptr = temp.copy(); }
      break;
      case(UnaryConstOp::Norm): {
      MetaValueImplementation<T, U> temp;
      temp.mData.resize(1,1);
      temp.mData(0,0)=mData.norm();
      ptr = temp.copy(); }
      break;
    case(UnaryConstOp::Transpose): {
      MetaValueImplementation<T, U> temp(Base(mData.transpose()));
      ptr = temp.copy(); }
      break;
    case(UnaryConstOp::Inverse): {
      ptr = inverseHelper(mData);
      }
      break;
    case(UnaryConstOp::Zero): {
      MetaValueImplementation<T, U> temp(mData.zero());
      ptr = temp.copy(); }
      break;
    case(UnaryConstOp::Identity): {
      MetaValueImplementation<T, U> temp(mData.identity());
      ptr = temp.copy(); }
      break;
    case(UnaryConstOp::Value): {
      MetaValueImplementation<T, false> temp(mData.value());
      ptr = temp.copy(); };
      break;
    case(UnaryConstOp::Uncertainty): {
      MetaValueImplementation<T, false> temp(mData.uncertainty());
      ptr = temp.copy(); };
      break;
    case(UnaryConstOp::ToUncertainty): {
      MetaValueImplementation<T, true> temp(mData.toUncertainty());
      ptr = temp.copy(); };
      break;
    case(UnaryConstOp::ZeroValue):
      ptr = MetaValueImplementation(mData.zeroValue()).copy();
      break;
    case(UnaryConstOp::Ones):
      ptr = MetaValueImplementation(mData.ones()).copy();
      break;
    case(UnaryConstOp::ArgMin):
      ptr = MetaValueImplementation<uint32_t, false>(mData.argmin()).copy();
      break;
    default           : return Interface::unaryConstOp(op);
  }
  return ptr;
}

template<typename T, bool U>
bool MetaValueImplementation<T, U>::binaryOp( BinaryOp op, const Interface& bTemp)  {
  const MetaValueImplementation<T, U>& b = reinterpret_cast<const Impl&>(bTemp);
  switch(op) {
      case(BinaryOp::Add): if(b.mData.rows() == mData.rows() &&
                              b.mData.cols() == mData.cols()) {
                            mData += b.mData;
                            return true;
                           }
                           break;
      case(BinaryOp::Sub): if(b.mData.rows() == mData.rows() &&
                              b.mData.cols() == mData.cols()) {
                            mData -= b.mData;
                            return true;
                           }
                           break;
      case(BinaryOp::Mul): if(b.mData.rows() == 1 &&
                              b.mData.cols() == 1) {
                            mData *= b.mData(0,0);
                            return true;
                           }
                           if(mData.rows() == 1 &&
                              mData.cols() == 1) {
                            mData = b.mData*mData(0,0);
                            return true;
                           }
                           if(mData.cols() == b.mData.rows()) {
                            mData*= b.mData;
                            return true;
                           }
                           if((mData.cols() == 1 && b.mData.cols() == 1 && mData.rows() == b.mData.rows()) ||
                              (mData.rows() == 1 && b.mData.rows() == 1 && mData.cols() == b.mData.cols())) {
                            mData=mData.cwiseProduct(b.mData);
                            return true;
                           }
                           break;
      case(BinaryOp::Div): if(b.mData.rows() == 1 &&
                              b.mData.cols() == 1) {
                              mData /= b.mData(0,0);
                              return true;
                           }if(b.mData.rows() == mData.rows() &&
                              b.mData.cols() == mData.cols()) {
                              mData /= b.mData;
                              return true;
                           }
                           break;
      case(BinaryOp::EMul):if(b.mData.rows() == mData.rows() &&
                              b.mData.cols() == mData.cols()) {
                              mData=mData.cwiseProduct(b.mData);
                              return true;
                           }
      default:             return Interface::binaryOp(op, b);
   }
   return false;
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::binaryConstOp( BinaryConstOp op, const Interface& b ) const {
  const Impl& typedB = reinterpret_cast<const Impl&>(b);
  switch(op) {
   case(BinaryConstOp::Equal)      : return Ptr(new Bool<U>(mData == typedB.mData));
   case(BinaryConstOp::NotEqual)   : return Ptr(new Bool<U>(mData != typedB.mData));
   case(BinaryConstOp::ApproxEqual): return Ptr(new Bool<U>(approxEqual(mData, typedB.mData)));
   case(BinaryConstOp::Smaller)    : return Ptr(new Bool<U>(mData < typedB.mData));
   case(BinaryConstOp::Greater)    : return Ptr(new Bool<U>(mData > typedB.mData));
   case(BinaryConstOp::SmallEqual) : return Ptr(new Bool<U>(mData <= typedB.mData));
   case(BinaryConstOp::GreatEqual) : return Ptr(new Bool<U>(mData >= typedB.mData));
   case(BinaryConstOp::CDot)       : return MetaValueImplementation<T, U>(mData.cwiseDot(typedB.mData)).copy();
   default                         : return Interface::binaryConstOp(op, b);
  }
}

template<typename T, bool U>
bool MetaValueImplementation<T, U>::scale(const MetaScale& scale, bool invert) {
  if(invert) {
    mData  *= scale.denom();
    mData  /= scale.num();
  } else {
    mData  *= scale.num();
    mData  /= scale.denom();
  }
  return true;
}

template<typename T, bool U>
bool MetaValueImplementation<T, U>::block(size_t i, size_t j, Ptr&& ptr) {
  Data typeID = ptr->get(Attributes::TypeID);
  Data u = ptr->get(Attributes::HasUncertainty);
  if(ValueType(mData).typeId() != typeID.typeID || ValueType(mData).hasUncertainty() != u.hasUncertainty)
    return false;
  MetaValueImplementation<T, U>& b = reinterpret_cast<MetaValueImplementation<T, U>&>(*ptr);
  mData.block(i,j, b.mData.rows(), b.mData.cols()) = b.mData.data();
  return true;
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::block(size_t i, size_t j, size_t numI, size_t numJ) const {
  return Ptr(new MetaValueImplementation<T, U>(Base(mData.block(i,j,numI, numJ))));
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::col(size_t col) const {
  return Ptr(new MetaValueImplementation<T, U>(Base(mData.col(col))));
}

template<typename T, bool U>
Ptr MetaValueImplementation<T, U>::row(size_t row) const {
  return Ptr(new MetaValueImplementation<T, U>(Base(mData.row(row))));
}

template<typename T, bool U>
ostream& MetaValueImplementation<T, U>::print(ostream& o) const {
  return o << mData;
}

template class MetaValueImplementation<uint8_t, true>;
template class MetaValueImplementation<uint16_t, true>;
template class MetaValueImplementation<uint32_t, true>;
//template class MetaValueImplementation<uint64_t, true>;
template class MetaValueImplementation<int8_t, true>;
template class MetaValueImplementation<int16_t, true>;
template class MetaValueImplementation<int32_t, true>;
//template class MetaValueImplementation<int64_t, true>;
template class MetaValueImplementation<float, true>;
template class MetaValueImplementation<double, true>;
template class MetaValueImplementation<bool, true>;
template class MetaValueImplementation<uint8_t, false>;
template class MetaValueImplementation<uint16_t, false>;
template class MetaValueImplementation<uint32_t, false>;
//template class MetaValueImplementation<uint64_t, false>;
template class MetaValueImplementation<int8_t, false>;
template class MetaValueImplementation<int16_t, false>;
template class MetaValueImplementation<int32_t, false>;
//template class MetaValueImplementation<int64_t, false>;
template class MetaValueImplementation<float, false>;
template class MetaValueImplementation<double, false>;
template class MetaValueImplementation<bool, false>;

class MetaValueRegisterer {

  using Creator   = MetaFactory::Creator;
  using Converter = MetaFactory::Converter;
  using UInt8 = id::type::UInt8;
  using Bool  =  id::type::Bool;

  struct insertCreator {

    MetaFactory& f = MetaFactory::instance();

    template<typename T>
    void operator()(const T& t) {
      using Type = typename id::type::id2Type<T::value>::type::Type;
      ValueType vT(T::value, 0, 0, false);
      auto create = [](std::size_t rows, std::size_t cols){
        return Ptr(new MVI<Type,  false>(rows, cols));
      };

      f.insert(Creator(vT, create));

      ValueType vTu(T::value, 0, 0, true);
      auto createU = [](std::size_t rows, std::size_t cols){
        return Ptr(new MVI<Type,  true>(rows, cols));
      };

      f.insert(Creator(vTu, createU));
    }
  };
  template<typename IDList>
  struct insertConverter {
    template<typename InID>
    struct helper {
      MetaFactory& f = MetaFactory::instance();
      template<typename OutID>
      void operator()(const OutID&) {
        using In = typename id::type::id2Type<InID::value>::type::Type;
        using Out = typename id::type::id2Type<OutID::value>::type::Type;
        auto convertUU = [](const MVBI& from, MVBI& to){
          using InT = MVI<In, true>;
          using OutT = MVI<Out, true>;
          const InT&  in  = reinterpret_cast<const InT&>(from);
          OutT& out = reinterpret_cast<OutT&>(to);
          out.mData=in.mData.template cast<typename OutT::Base::BaseType>();
        };
        auto convertUC = [](const MVBI& from, MVBI& to){
          using InT = MVI<In, true>;
          using OutT = MVI<Out, false>;
          const InT&  in  = reinterpret_cast<const InT&>(from);
          OutT& out = reinterpret_cast<OutT&>(to);
          out.mData=in.mData.template cast<typename OutT::Base::BaseType>();
        };
        auto convertCU = [](const MVBI& from, MVBI& to){
          using InT = MVI<In, false>;
          using OutT = MVI<Out, true>;
          const InT&  in  = reinterpret_cast<const InT&>(from);
          OutT& out = reinterpret_cast<OutT&>(to);
          out.mData=in.mData.template cast<typename OutT::Base::BaseType>();
        };
        auto convertCC = [](const MVBI& from, MVBI& to){
          using InT = MVI<In, false>;
          using OutT = MVI<Out, false>;
          const InT&  in  = reinterpret_cast<const InT&>(from);
          OutT& out = reinterpret_cast<OutT&>(to);
          out.mData=in.mData.template cast<typename OutT::Base::BaseType>();
        };

        f.insert(Converter(make_pair(ValueType(InID::value, 0, 0, false), ValueType(OutID::value, 0, 0, false)), convertCC));
        f.insert(Converter(make_pair(ValueType(InID::value, 0, 0, true), ValueType(OutID::value, 0, 0, false)), convertUC));
        f.insert(Converter(make_pair(ValueType(InID::value, 0, 0, false), ValueType(OutID::value, 0, 0, true)), convertCU));
        f.insert(Converter(make_pair(ValueType(InID::value, 0, 0, true), ValueType(OutID::value, 0, 0, true)), convertUU));
      }
    };

    template<typename T>
    void operator()(const T& t) {
      helper<T> h;
      foreach<IDList>(h);
    }
  };
  public:
  MetaValueRegisterer() {
    using ids = boost::mpl::range_c<id::type::ID, UInt8::value(),  Bool::value()+1>;
    insertCreator creat;
    insertConverter<ids> conv;
    foreach<ids>(creat);
    foreach<ids>(conv);
  }
};

static MetaValueRegisterer registerer;