//
// $Id$
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The Original Code is the IDPicker project.
//
// The Initial Developer of the Original Code is Matt Chambers.
//
// Copyright 2017 Vanderbilt University
//
// Contributor(s):
//

#define PWIZ_SOURCE

#include "pwiz/utility/misc/Std.hpp"
#include "pwiz/utility/chemistry/Chemistry.hpp"
#include "IdpSqlExtensions.hpp"
#include "boost/crc.hpp"
#include "boost/range/algorithm/sort.hpp"
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/framework/accumulator_set.hpp>
#include "../../freicore/percentile.hpp"
#include "sqlite3ext.h" /* Do not use <sqlite3.h>! */
#include "sqlite3pp.h"
SQLITE_EXTENSION_INIT1

using namespace pwiz::util;
namespace sqlite = sqlite3pp;
namespace accs = boost::accumulators;

namespace {

    struct DistinctDoubleArraySum
    {
        typedef DistinctDoubleArraySum MyType;
        set<int> arrayIds;
        vector<double> result;
        boost::crc_32_type crc32;

        DistinctDoubleArraySum(int arrayLength) : result((size_t)arrayLength, 0.0) {}

        static void Step(sqlite3_context* context, int numValues, sqlite3_value** values)
        {
            void* aggContext = sqlite3_aggregate_context(context, sizeof(MyType*));
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (numValues > 1 || values[0] == NULL)
                return;

            int arrayByteCount = sqlite3_value_bytes(values[0]);
            int arrayLength = arrayByteCount / 8;
            const char* arrayBytes = static_cast<const char*>(sqlite3_value_blob(values[0]));
            if (arrayBytes == NULL)
                return;

            if (arrayByteCount % 8 > 0)
                throw runtime_error("distinct_double_array_sum only works with BLOBs of double precision floats");

            if (pThis == NULL)
                pThis = new DistinctDoubleArraySum(arrayLength);
            else
                pThis->crc32.reset();

            // if the arrayId was already in the set, ignore its values
            pThis->crc32.process_bytes(arrayBytes, arrayByteCount);
            int arrayId = pThis->crc32.checksum();
            if (!pThis->arrayIds.insert(arrayId).second)
                return;

            const double* arrayValues = reinterpret_cast<const double*>(arrayBytes);

            for (int i = 0; i < arrayLength; ++i)
                pThis->result[i] += arrayValues[i];
        }

        static void Final(sqlite3_context* context)
        {
            void* aggContext = sqlite3_aggregate_context(context, 0);
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (pThis == NULL)
                pThis = new DistinctDoubleArraySum(0);

            sqlite3_result_blob(context, pThis->result.empty() ? NULL : &pThis->result[0], pThis->result.size() * sizeof(double), SQLITE_TRANSIENT);

            delete pThis;
        }
    };

    struct DistinctDoubleArrayMean
    {
        typedef DistinctDoubleArrayMean MyType;
        set<int> arrayIds;
        vector<double> result;
        boost::crc_32_type crc32;

        DistinctDoubleArrayMean(int arrayLength) : result((size_t)arrayLength, 0.0) {}

        static void Step(sqlite3_context* context, int numValues, sqlite3_value** values)
        {
            DistinctDoubleArraySum::Step(context, numValues, values);
        }

        static void Final(sqlite3_context* context)
        {
            void* aggContext = sqlite3_aggregate_context(context, 0);
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (pThis == NULL)
                pThis = new DistinctDoubleArrayMean(0);

            // divide sum by number of elements
            if (!pThis->result.empty())
                for (auto& r : pThis->result)
                    r /= pThis->arrayIds.size();

            sqlite3_result_blob(context, pThis->result.empty() ? NULL : &pThis->result[0], pThis->result.size() * sizeof(double), SQLITE_TRANSIENT);

            delete pThis;
        }
    };

    struct DistinctDoubleArrayMedian
    {
        typedef DistinctDoubleArrayMedian MyType;
        typedef accs::accumulator_set<double, accs::stats<accs::tag::percentile> > MedianAccumulator;
        set<int> arrayIds;
        vector<MedianAccumulator> medianAccumulators;
        boost::crc_32_type crc32;

        DistinctDoubleArrayMedian(int arrayLength) : medianAccumulators((size_t)arrayLength) {}

        static void Step(sqlite3_context* context, int numValues, sqlite3_value** values)
        {
            void* aggContext = sqlite3_aggregate_context(context, sizeof(MyType*));
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (numValues > 1 || values[0] == NULL)
                return;

            int arrayByteCount = sqlite3_value_bytes(values[0]);
            int arrayLength = arrayByteCount / 8;
            const char* arrayBytes = static_cast<const char*>(sqlite3_value_blob(values[0]));
            if (arrayBytes == NULL)
                return;

            if (arrayByteCount % 8 > 0)
                throw runtime_error("distinct_double_array_sum only works with BLOBs of double precision floats");

            if (pThis == NULL)
                pThis = new DistinctDoubleArrayMedian(arrayLength);
            else
                pThis->crc32.reset();

            // if the arrayId was already in the set, ignore its values
            pThis->crc32.process_bytes(arrayBytes, arrayByteCount);
            int arrayId = pThis->crc32.checksum();
            if (!pThis->arrayIds.insert(arrayId).second)
                return;

            const double* arrayValues = reinterpret_cast<const double*>(arrayBytes);

            for (int i = 0; i < arrayLength; ++i)
                (pThis->medianAccumulators[i])(arrayValues[i]);
        }

        static void Final(sqlite3_context* context)
        {
            void* aggContext = sqlite3_aggregate_context(context, 0);
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (pThis == NULL)
                pThis = new DistinctDoubleArrayMedian(0);

            // divide sum by number of elements
            vector<double> result;
            result.reserve(pThis->medianAccumulators.size());
            if (!pThis->medianAccumulators.empty())
                for (auto& acc : pThis->medianAccumulators)
                    result.push_back(accs::percentile(acc, accs::percentile_number = 50));

            sqlite3_result_blob(context, result.empty() ? NULL : &result[0], result.size() * sizeof(double), SQLITE_TRANSIENT);

            delete pThis;
        }
    };

    struct DistinctTukeyBiweightAverage
    {
        typedef DistinctTukeyBiweightAverage MyType;
        set<int> arrayIds;
        vector<double> result;
        vector<vector<double> > tukeyBuffer;
        boost::crc_32_type crc32;

        DistinctTukeyBiweightAverage(int arrayLength) : result((size_t)arrayLength, 0.0), tukeyBuffer(arrayLength) {}

        static void Step(sqlite3_context* context, int numValues, sqlite3_value** values)
        {
            void* aggContext = sqlite3_aggregate_context(context, sizeof(MyType*));
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (numValues > 1 || values[0] == NULL)
                return;

            int arrayByteCount = sqlite3_value_bytes(values[0]);
            int arrayLength = arrayByteCount / 8;
            const char* arrayBytes = static_cast<const char*>(sqlite3_value_blob(values[0]));
            if (arrayBytes == NULL)
                return;

            if (arrayByteCount % 8 > 0)
                throw runtime_error("distinct_double_array_sum only works with BLOBs of double precision floats");

            if (pThis == NULL)
                pThis = new DistinctTukeyBiweightAverage(arrayLength);
            else
                pThis->crc32.reset();

            // if the arrayId was already in the set, ignore its values
            pThis->crc32.process_bytes(arrayBytes, arrayByteCount);
            int arrayId = pThis->crc32.checksum();
            if (!pThis->arrayIds.insert(arrayId).second)
                return;

            const double* arrayValues = reinterpret_cast<const double*>(arrayBytes);

            for (int i = 0; i < arrayLength; ++i)
                pThis->tukeyBuffer[i].push_back(arrayValues[i]);
        }

        static inline double weight_bisquare(double x)
        {
            return fabs(x) <= 1.0 ? (1 - x*x) * (1 - x*x) : 0;
        }

        static inline double safe_log(double x)
        {
            return x > 1 ? std::log(x) : 0.0;
        }

        static void Final(sqlite3_context* context) { Final(context, false); }

        static void FinalLog(sqlite3_context* context){ Final(context, true); }

        static void Final(sqlite3_context* context, bool logValues)
        {
            void* aggContext = sqlite3_aggregate_context(context, 0);
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            MyType** ppThis = static_cast<MyType**>(aggContext);
            MyType*& pThis = *ppThis;

            if (pThis == NULL)
                pThis = new DistinctTukeyBiweightAverage(0);
            else
            {
                double c = 5.0;
                double epsilon = 0.0001;

                vector<double> medians(pThis->tukeyBuffer[0].size());
                auto length = pThis->tukeyBuffer[0].size();
                for (int column = 0; column < pThis->tukeyBuffer.size(); ++column)
                {
                    vector<double>& values = pThis->tukeyBuffer[column];
                    if (logValues)
                        std::transform(values.begin(), values.end(), values.begin(), static_cast<double(*)(double)>(safe_log));

                    medians = values;
                    boost::range::sort(medians);

                    double median = (length % 2 == 0) ? (medians[length / 2 - 1] + medians[length / 2]) / 2.0 : medians[length / 2];

                    for (size_t i = 0; i < length; ++i)
                        medians[i] = fabs(values[i] - median);
                    boost::range::sort(medians);

                    double S = (length % 2 == 0) ? (medians[length / 2 - 1] + medians[length / 2]) / 2.0 : medians[length / 2];

                    for (size_t i = 0; i < length; ++i)
                        medians[i] = (values[i] - median) / (c*S + epsilon);

                    double sum = 0.0;
                    double sumw = 0.0;
                    for (size_t i = 0; i < length; ++i)
                    {
                        sum += weight_bisquare(medians[i]) * values[i];
                        sumw += weight_bisquare(medians[i]);
                    }

                    pThis->result[column] = sum / sumw;
                }
            }

            sqlite3_result_blob(context, pThis->result.empty() ? NULL : &pThis->result[0], pThis->result.size() * sizeof(double), SQLITE_TRANSIENT);

            delete pThis;
        }
    };

    void PrintDoubleArray(sqlite3_context* context, int numValues, sqlite3_value** values)
    {
        if (numValues != 1)
        {
            sqlite3_result_error(context, "[PRINT_DOUBLE_ARRAY] requires 1 double array argument", -1);
            return;
        }

        if (values[0] == NULL)
        {
            sqlite3_result_null(context);
            return;
        }

        const void* blob = sqlite3_value_blob(values[0]);
        int size = sqlite3_value_bytes(values[0]) / sizeof(double);

        char* str;
        int strSize;
        if (blob == NULL)
        {
            strSize = size * 2; // all 0s with commas between plus a null terminator
            str = (char*)sqlite3_malloc(strSize); // all 0s with commas between plus a null terminator
            for (size_t i = 0; i < size; ++i)
                str[i*2] = '0', str[i*2 + 1] = ',';
            str[size * 2 - 1] = 0;
        }
        else
        {
            strSize = size * 30;
            str = (char*)sqlite3_malloc(strSize);

            const double* blobArray = reinterpret_cast<const double*>(blob);

            int offset = 0;
            for (size_t i = 0; i < size; ++i)
                offset += sprintf(&str[offset], "%.5f,", blobArray[i]);
        }

        sqlite3_result_text(context, str, strSize, sqlite3_free);
    }

    struct GroupConcatEx
    {
        static void Step(sqlite3_context *context, int argc, sqlite3_value **argv)
        {
            const char *zVal;
            const char *zSep;
            int nVal, nSep;
            assert(argc == 1 || argc == 2);
            if (sqlite3_value_type(argv[0]) == SQLITE_NULL) return;

            void* aggContext = sqlite3_aggregate_context(context, sizeof(string*));
            if (aggContext == NULL)
                throw runtime_error(sqlite3_errmsg(sqlite3_context_db_handle(context)));

            string** ppAccum = static_cast<string**>(aggContext);
            string*& pAccum = *ppAccum;

            if (!pAccum)
                pAccum = new string();

            int firstTerm = pAccum->empty();
            if (!firstTerm){
                if (argc == 2){
                    zSep = (char*)sqlite3_value_text(argv[1]);
                    nSep = sqlite3_value_bytes(argv[1]);
                }
                else{
                    zSep = separator_.c_str();
                    nSep = separator_.length();
                }
                if (zSep) pAccum->append(zSep, zSep + nSep);
            }
            zVal = (char*)sqlite3_value_text(argv[0]);
            nVal = sqlite3_value_bytes(argv[0]);
            if (zVal) pAccum->append(zVal, zVal + nVal);
        }

        static void Final(sqlite3_context *context)
        {
            string **pAccum = (string**)sqlite3_aggregate_context(context, 0);
            if (pAccum && *pAccum)
            {
                sqlite3_result_text(context, (*pAccum)->c_str(), (*pAccum)->length(), SQLITE_TRANSIENT);
                delete *pAccum;
            }
        }

        static string separator_;
    };

    string GroupConcatEx::separator_ = ",";

    void SortUnmappedLast(sqlite3_context* context, int numValues, sqlite3_value** values)
    {
        if (numValues != 1)
        {
            sqlite3_result_error(context, "[SORT_UNMAPPED_LAST] requires 1 text argument", -1);
            return;
        }

        if (values[0] == NULL)
        {
            sqlite3_result_null(context);
            return;
        }

        const unsigned char* text = sqlite3_value_text(values[0]);
        auto textRange = boost::make_iterator_range(text, text + sqlite3_value_bytes(values[0]));

        if (textRange.size() < 3 || std::find(textRange.begin(), textRange.end(), GroupConcatEx::separator_[0]) == textRange.end())
        {
            sqlite3_result_text(context, (const char*) text, textRange.size(), NULL);
            return;
        }

        vector<string> tokens;
        bal::split(tokens, textRange, bal::is_any_of(GroupConcatEx::separator_));

        std::sort(tokens.begin(), tokens.end(), [&](const string& lhs, const string& rhs)
        {
            bool lhsUnmapped = bal::starts_with(lhs, "Unmapped_");
            bool rhsUnmapped = bal::starts_with(rhs, "Unmapped_");
            if (lhsUnmapped && rhsUnmapped)
                return lhs < rhs;
            if (lhsUnmapped)
                return false;
            if (rhsUnmapped)
                return true;
            return lhs < rhs;
        });

        char* result = (char*)sqlite3_malloc(textRange.size()+1);
        size_t offset = 0;
        for (const string& token : tokens)
        {
            std::copy(token.begin(), token.end(), result + offset);
            offset += token.length() + 1;
            result[offset-1] = GroupConcatEx::separator_[0];
        }
        result[textRange.size()] = 0;

        sqlite3_result_text(context, result, textRange.size()+1, sqlite3_free);
    };

    /// Automatically choose monoisotopic or average mass error based on the following logic:
    /// if the absolute value of monoisotopic error is less than absolute value of average error
    /// or if the monoisotopic error is nearly a multiple of a neutron mass,
    /// then return the monoisotopic error.
    void GetSmallerMassError(sqlite3_context* context, int numValues, sqlite3_value** values)
    {
        if (numValues != 2 || values[0] == NULL || values[1] == NULL)
        {
            sqlite3_result_error(context, "[GET_SMALLER_MASS_ERROR] requires 2 numeric arguments", -1);
            return;
        }

        double mono = sqlite3_value_double(values[0]);
        double avg = sqlite3_value_double(values[1]);

        bool monoisotopic = fabs(mono) < fabs(avg) || fmod(fabs(mono), pwiz::chemistry::Neutron) < fabs(avg);
        sqlite3_result_double(context, monoisotopic ? mono : avg);
    }

    /// Same as GetSmallerMassError(), but when monoisotopic error is used because it is nearly a multiple of a neutron mass,
    /// the returned error is adjusted to factor out the neutron contribution; the result is the true monoisotopic error.
    void GetSmallerMassErrorAdjusted(sqlite3_context* context, int numValues, sqlite3_value** values)
    {
        if (numValues != 2 || values[0] == NULL || values[1] == NULL)
        {
            sqlite3_result_error(context, "[GET_SMALLER_MASS_ERROR_ADJUSTED] requires 2 numeric arguments", -1);
            return;
        }

        double mono = sqlite3_value_double(values[0]);
        double avg = sqlite3_value_double(values[1]);

        if (fabs(mono) < fabs(avg))
            sqlite3_result_double(context, mono);
        else
        {
            double monoModNeutron = fmod(mono, pwiz::chemistry::Neutron);
            bool monoisotopic = fabs(monoModNeutron) < fabs(avg);
            sqlite3_result_double(context, monoisotopic ? monoModNeutron : avg);
        }
    }

    // WITHIN_MASS_TOLERANCE_MZ(observed, expected, tolerance)
    void WithinMassToleranceMZ(sqlite3_context* context, int numValues, sqlite3_value** values)
    {
        if (numValues != 3 || values[0] == NULL || values[1] == NULL || values[2] == NULL)
        {
            sqlite3_result_error(context, "[WITHIN_MASS_TOLERANCE_MZ] requires 3 numeric arguments", -1);
            return;
        }

        double observed = sqlite3_value_double(values[0]);
        double expected = sqlite3_value_double(values[1]);
        double tolerance = sqlite3_value_double(values[2]);
        double lower_bound = expected - tolerance;
        double upper_bound = expected + tolerance;

        sqlite3_result_int(context, (observed > lower_bound && observed < upper_bound) ? 1 : 0);
    }

    // WITHIN_MASS_TOLERANCE_PPM(observed, expected, tolerance)
    void WithinMassTolerancePPM(sqlite3_context* context, int numValues, sqlite3_value** values)
    {
        if (numValues != 3 || values[0] == NULL || values[1] == NULL || values[2] == NULL)
        {
            sqlite3_result_error(context, "[WITHIN_MASS_TOLERANCE_PPM] requires 3 numeric arguments", -1);
            return;
        }

        double observed = sqlite3_value_double(values[0]);
        double expected = sqlite3_value_double(values[1]);
        double tolerance = sqlite3_value_double(values[2]);
        double ppmDelta = fabs(expected) * tolerance * 1e-6;
        double lower_bound = expected - ppmDelta;
        double upper_bound = expected + ppmDelta;

        sqlite3_result_int(context, (observed > lower_bound && observed < upper_bound) ? 1 : 0);
    }

} // namespace

// no need to rename IDPicker namespace for these global functions
namespace IDPicker {

PWIZ_API_DECL void setGroupConcatSeparator(const std::string& separator) { GroupConcatEx::separator_ = separator; }

PWIZ_API_DECL const std::string& getGroupConcatSeparator() { return GroupConcatEx::separator_; }

} // IDPicker

extern "C" {

PWIZ_API_DECL int sqlite3_idpsqlextensions_init(sqlite3 *idpDbConnection, char **pzErrMsg, const sqlite3_api_routines *pApi)
{
    int rc = SQLITE_OK;
    SQLITE_EXTENSION_INIT2(pApi);

    rc += sqlite3_create_function(idpDbConnection, "distinct_double_array_sum", -1, SQLITE_ANY, 0, NULL, &DistinctDoubleArraySum::Step, &DistinctDoubleArraySum::Final);
    rc += sqlite3_create_function(idpDbConnection, "distinct_double_array_mean", -1, SQLITE_ANY, 0, NULL, &DistinctDoubleArrayMean::Step, &DistinctDoubleArrayMean::Final);
    rc += sqlite3_create_function(idpDbConnection, "distinct_double_array_median", -1, SQLITE_ANY, 0, NULL, &DistinctDoubleArrayMedian::Step, &DistinctDoubleArrayMedian::Final);

    rc += sqlite3_create_function(idpDbConnection, "distinct_double_array_tukey_biweight_average", -1, SQLITE_ANY, 0, NULL, &DistinctTukeyBiweightAverage::Step, &DistinctTukeyBiweightAverage::Final);

    rc += sqlite3_create_function(idpDbConnection, "distinct_double_array_tukey_biweight_log_average", -1, SQLITE_ANY, 0, NULL, &DistinctTukeyBiweightAverage::Step, &DistinctTukeyBiweightAverage::FinalLog);

    rc += sqlite3_create_function(idpDbConnection, "print_double_array", 1, SQLITE_ANY, 0, &PrintDoubleArray, NULL, NULL);

    rc += sqlite3_create_function(idpDbConnection, "group_concat", -1, SQLITE_ANY, 0, NULL, &GroupConcatEx::Step, &GroupConcatEx::Final);
    rc += sqlite3_create_function(idpDbConnection, "group_concat_ex", -1, SQLITE_ANY, 0, NULL, &GroupConcatEx::Step, &GroupConcatEx::Final);

    rc += sqlite3_create_function(idpDbConnection, "sort_unmapped_last", 1, SQLITE_ANY, 0, &SortUnmappedLast, NULL, NULL);

    rc += sqlite3_create_function(idpDbConnection, "get_smaller_mass_error", 2, SQLITE_ANY, 0, &GetSmallerMassError, NULL, NULL);

    rc += sqlite3_create_function(idpDbConnection, "get_smaller_mass_error_adjusted", 2, SQLITE_ANY, 0, &GetSmallerMassErrorAdjusted, NULL, NULL);

    rc += sqlite3_create_function(idpDbConnection, "within_mass_tolerance_mz", 3, SQLITE_ANY, 0, &WithinMassToleranceMZ, NULL, NULL);

    rc += sqlite3_create_function(idpDbConnection, "within_mass_tolerance_ppm", 3, SQLITE_ANY, 0, &WithinMassTolerancePPM, NULL, NULL);

    return rc;
}

} // extern C


// Copyright 2015-2022 The ALMA Project Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

#pragma once

/// @file
///
/// Detection and representation of allowed three-phonon processes.

#include <cstddef>
#include <cmath>
#include <array>
#include <vector>
#include <functional>
#include <boost/mpi.hpp>
#include <boost/math/distributions/normal.hpp>
#include <boost/serialization/array.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <structures.hpp>
#include <qpoint_grid.hpp>

// Forward declarations of elements documented later on.
// Note that serialization of std::vectors of objects without
// a default constructor is broken in Boost version 1.58.0. See:
// http://stackoverflow.com/a/30437359/85371
// https://svn.boost.org/trac/boost/ticket/11342
// This will prevent ALMA from compiling. We check for that
// specific version in our cmake configuration.

namespace alma {
/// Process type (emission or absorption).
enum class threeph_type { emission = -1, absorption = 1 };
/// Representation of a three-phonon process.
class Threeph_process {
private:
    friend class boost::serialization::access;

    friend void save_bulk_hdf5(const char* filename,
                               const std::string& description,
                               const Crystal_structure& cell,
                               const Symmetry_operations& symmetries,
                               const Gamma_grid& grid,
                               const std::vector<Threeph_process>& processes,
                               const boost::mpi::communicator& comm);

    friend std::tuple<std::string,
                      std::unique_ptr<Crystal_structure>,
                      std::unique_ptr<Symmetry_operations>,
                      std::unique_ptr<Gamma_grid>,
                      std::unique_ptr<std::vector<Threeph_process>>>
    load_bulk_hdf5(const char* filename, const boost::mpi::communicator& comm);

    /// Deviation from the conservation of energy.
    double domega;
    /// Standard deviation of the Gaussian.
    double sigma;
    /// Has the phase space of the process been computed yet?
    bool gaussian_computed;
    /// Gaussian factor of the process, coming from the
    /// regularized Dirac delta.
    double gaussian;
    /// Has the matrix element of the process been computed yet?
    bool vp2_computed;
    /// Modulus squared of the matrix element of the process.
    double vp2;
    /// Serialize non-const members of the class.
    ///

    template <class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        ar & this->domega;
        ar & this->sigma;
        ar & this->gaussian_computed;
        ar & this->gaussian;
        ar & this->vp2_computed;
        ar & this->vp2;
        ar & this->c;
        ar & this->q;
        ar & this->alpha;
        ar & this->type;
    }

public:
    /// Equivalence class of the first phonon.
    std::size_t c;
    /// q point indices of each of the three phonons involved.
    std::array<std::size_t, 3> q;
    /// Mode indices of the three phonons involved.
    std::array<std::size_t, 3> alpha;
    /// Type of process.
    threeph_type type;
    /// Default constructor this is to comply with DefaultConstructible required
    /// by boost for serialization maps with values of this class
    Threeph_process(){};
    /// Basic constructor.
    Threeph_process(std::size_t _c,
                    const std::array<std::size_t, 3>& _q,
                    const std::array<std::size_t, 3>& _alpha,
                    threeph_type _type,
                    double _domega,
                    double _sigma)
        : domega(_domega), sigma(_sigma), gaussian_computed(false),
          vp2_computed(false), c(_c), q(std::move(_q)),
          alpha(std::move(_alpha)), type(_type) {
    }

    /// Copy constructor.
    Threeph_process(const Threeph_process& original)
        : domega(original.domega), sigma(original.sigma),
          gaussian_computed(original.gaussian_computed),
          gaussian(original.gaussian), vp2_computed(original.vp2_computed),
          vp2(original.vp2), c(original.c), q(original.q),
          alpha(original.alpha), type(original.type) {
    }

    /// Copy assignament operator:
    Threeph_process& operator=(const Threeph_process& rhs) {
        this->domega = rhs.domega;
        this->sigma = rhs.sigma;
        this->gaussian_computed = rhs.gaussian_computed;
        this->gaussian = rhs.gaussian;
        this->vp2_computed = rhs.vp2_computed;
        this->vp2 = rhs.vp2;
        this->c = rhs.c;
        this->q = {rhs.q[0], rhs.q[1], rhs.q[2]};
        this->alpha = rhs.alpha;
        this->type = rhs.type;
        return *this;
    }

    /// Move constructor
    Threeph_process(Threeph_process&& rhs) {
        using std::swap;
        swap(this->domega, rhs.domega);
        swap(this->sigma, rhs.sigma);
        swap(this->gaussian_computed, rhs.gaussian_computed);
        swap(this->gaussian, rhs.gaussian);
        swap(this->vp2_computed, rhs.vp2_computed);
        swap(this->vp2, rhs.vp2);
        swap(this->c, rhs.c);
        swap(this->q, rhs.q);
        swap(this->alpha, rhs.alpha);
        swap(this->type, rhs.type);
    }

    /// Swap function
    /// It is required by sorting algorithms
    void swap(Threeph_process& lhs, Threeph_process& rhs) {
        using std::swap;
        swap(lhs.domega, rhs.domega);
        swap(lhs.sigma, rhs.sigma);
        swap(lhs.gaussian_computed, rhs.gaussian_computed);
        swap(lhs.gaussian, rhs.gaussian);
        swap(lhs.vp2_computed, rhs.vp2_computed);
        swap(lhs.vp2, rhs.vp2);
        swap(lhs.c, rhs.c);
        swap(lhs.q, rhs.q);
        swap(lhs.alpha, rhs.alpha);
        swap(lhs.type, rhs.type);
    }

    /// Move assignament operator:
    Threeph_process& operator=(Threeph_process&& rhs) {
        using std::swap;
        swap(this->domega, rhs.domega);
        swap(this->sigma, rhs.sigma);
        swap(this->gaussian_computed, rhs.gaussian_computed);
        swap(this->gaussian, rhs.gaussian);
        swap(this->vp2_computed, rhs.vp2_computed);
        swap(this->vp2, rhs.vp2);
        swap(this->c, rhs.c);
        swap(this->q, rhs.q);
        swap(this->alpha, rhs.alpha);
        swap(this->type, rhs.type);
        return *this;
    }

    /// Comparison operator
    /// It is built in top of tuples as they have the by construction the
    /// desired ordering
    bool operator<(const Threeph_process& rhs) const {
        return std::tuple<std::size_t,
                          std::size_t,
                          std::size_t,
                          std::size_t,
                          std::size_t,
                          std::size_t,
                          int>(this->q[0],
                               this->q[1],
                               this->q[2],
                               this->alpha[0],
                               this->alpha[1],
                               this->alpha[2],
                               static_cast<int>(this->type)) <
               std::tuple<std::size_t,
                          std::size_t,
                          std::size_t,
                          std::size_t,
                          std::size_t,
                          std::size_t,
                          int>(rhs.q[0],
                               rhs.q[1],
                               rhs.q[2],
                               rhs.alpha[0],
                               rhs.alpha[1],
                               rhs.alpha[2],
                               static_cast<int>(rhs.type));
    }

    /// Compute and return the Gaussian factor of the process,
    /// i.e., the amplitude of the regularized delta.
    ///
    /// The result is cached. It can be used to obtain the phase
    /// space volume of three-phonon processes.
    /// @return the Gaussian factor of the process
    double compute_gaussian() {
        if (!this->gaussian_computed) {
            auto distr = boost::math::normal(0., this->sigma);
            this->gaussian = boost::math::pdf(distr, this->domega);
            this->gaussian_computed = true;
        }
        return this->gaussian;
    }

    /// Compute and return a weighted version of the Gaussian factor
    /// of the process, containing essentially the same ingredients as
    /// the scattering rate except for the matrix element and some
    /// constants.
    ///
    /// The result can be used to obtain the weighted phase
    /// space volume of three-phonon processes. The Gaussian factor
    /// is cached.
    /// @param[in] grid - regular grid with phonon spectrum
    /// @param[in] T - temperature in K
    /// @return the Bose-Einstein weighted Gaussian factor of the process at the
    /// given temperature.
    double compute_weighted_gaussian(const Gamma_grid& grid, double T);

    /// Compute, return and store the modulus squared of the matrix
    /// element of the process.
    ///
    /// @param[in] cell - description of the unit cell
    /// @param[in] grid - regular grid with phonon spectrum
    /// @param[in] thirdorder - third-order ifcs
    /// @return the modulus squared of the matrix element
    /// of the process
    double compute_vp2(const Crystal_structure& cell,
                       const Gamma_grid& grid,
                       const std::vector<Thirdorder_ifcs>& thirdorder);

    /// Return the modulus squared of the matrix element of the
    /// process or throw an exception if it has not been calculated.
    ///
    /// @return the modulus squared of the matrix element
    /// of the process
    inline double get_vp2() const {
        if (!this->vp2_computed)
            throw exception("vp2 is not available");
        return this->vp2;
    }

    /// Return the modulus squared of the matrix element of the
    /// process or throw an exception if it has not been calculated.
    ///
    /// @return None
    inline void set_vp2(double A) {
        this->vp2_computed = true;
        this->vp2 = A;
    }

    /// @return the value of vp2_computed.
    inline bool is_vp2_computed() const {
        return this->vp2_computed;
    }

    /// Get the "partial scattering rate" Gamma for this process.
    ///
    /// vp2 must have been precomputed.
    /// @param[in] grid - regular grid with phonon spectrum
    /// @param[in] T - temperature in K
    //  @param[in] compact - bool indicating if vp2 is already multiplied by
    //  gaussian
    /// @return Gamma, the partial scattering rate
    double compute_gamma(const Gamma_grid& grid,
                         double T,
                         bool compact = false);

    /// Get the reduced "partial scattering rate", meaning Gamma
    /// without the scaling factor involving the BE occupations.
    ///
    /// vp2 must have been precomputed.
    /// @param[in] grid - regular grid with phonon spectrum
    /// @param[in] T - temperature in K
    /// @return Gamma, the partial scattering rate
    double compute_gamma_reduced(const Gamma_grid& grid, double T);

    /// Get the coefficients of the three contributions to the
    /// linearized scattering operator from this process.
    ///
    /// vp2 must have been precomputed.
    /// @param[in] grid - regular grid with phonon spectrum
    /// @param[in] n0 - precomputed Bose-Einstein distribution
    /// @return an array with the three coefficients
    Eigen::ArrayXd compute_collision(const Gamma_grid& grid,
                                     const Eigen::ArrayXXd& n0);
};
/// Look for allowed three-phonon processes in a regular grid.
///
/// Iterate over part of the irreducible q points in the grid
/// (trying to evenly split the equivalence classes over processes)
/// and look for allowed three-phonon processes involving one
/// phonon from that part and two other phonons from anywhere
/// in the grid.
/// @param[in] grid - a regular grid containing Gamma
/// @param[in] communicator - MPI communicator to use
/// @param[in] scalebroad - factor modulating all the broadenings
/// @return a vector of Threeph_process objects
std::vector<Threeph_process> find_allowed_threeph(
    const Gamma_grid& grid,
    const boost::mpi::communicator& communicator,
    double scalebroad = 1.0);

/// Compute and store the three-phonon contribution to the RTA
/// scattering rates for all vibrational modes on a grid.
///
/// @param[in] grid - phonon spectrum on a regular grid
/// @param[in] processes - a vector of allowed
/// three-phonon processes
/// @param[in] T - the temperature in K
/// @param[in] comm - an mpi communicator
Eigen::ArrayXXd calc_w0_threeph(const alma::Gamma_grid& grid,
                                std::vector<alma::Threeph_process>& processes,
                                double T,
                                const boost::mpi::communicator& comm);

/// Compute and store the three-phonon contribution to the RTA
/// scattering rates for all vibrational modes on a grid. Take into
/// account only those processes fullfilling a criterion.
///
/// @param[in] grid - phonon spectrum on a regular grid
/// @param[in] processes - a vector of allowed
/// three-phonon processes
/// @param[in] T - the temperature in K
/// @param[in] filter - boolean function returning true if a process must
///                     be taken into account
/// @param[in] comm - an mpi communicator
Eigen::ArrayXXd calc_w0_threeph(
    const alma::Gamma_grid& grid,
    std::vector<alma::Threeph_process>& processes,
    double T,
    std::function<bool(const Threeph_process&)> filter,
    const boost::mpi::communicator& comm);
} // namespace alma


/**
 * Copyright (c) 2018, University Osnabrück
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the University Osnabrück nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL University Osnabrück BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

 /*
 * AdaptiveKSearchSurface.cpp
 *
 *  Created on: 07.02.2011
 *      Author: Thomas Wiemann
 *   co-Author: Dominik Feldschnieders (dofeldsc@uos.de)
 */

// External libraries in lvr source tree
#include <Eigen/Dense>

// boost libraries
#include <boost/filesystem.hpp>

#include <fstream>
#include <set>
#include <random>
#include <algorithm>

#include <lvr2/util/Factories.hpp>
#include <lvr2/io/Progress.hpp>

namespace lvr2
{

template<typename BaseVecT>
AdaptiveKSearchSurface<BaseVecT>::AdaptiveKSearchSurface()
    : m_calcMethod(0)
{
    this->setKi(10);
    this->setKn(10);
    this->setKd(10);
}

template<typename BaseVecT>
AdaptiveKSearchSurface<BaseVecT>::AdaptiveKSearchSurface(
    PointBufferPtr buffer,
    std::string searchTreeName,
    int kn,
    int ki,
    int kd,
    int calcMethod,
    string posefile
) :
    PointsetSurface<BaseVecT>(buffer),
    m_searchTreeName(searchTreeName),
    m_calcMethod(calcMethod)
{
    this->setKi(ki);
    this->setKn(kn);
    this->setKd(kd);

    init();

    this->m_searchTree = getSearchTree<BaseVecT>(m_searchTreeName, buffer);

    if(!this->m_searchTree)
    {
       this->m_searchTree = getSearchTree<BaseVecT>("flann", buffer);
       cout << timestamp.getElapsedTime() << "No valid search tree specified (" << searchTreeName << ")." << endl;
       cout << timestamp.getElapsedTime() << "Maybe you did not install the required library." << endl;
       cout << timestamp.getElapsedTime() << "Defaulting to flann." << endl;
    }

    if(posefile != "")
    {
        //panic_unimplemented("posefile handling");
        parseScanPoses(posefile);
    }

}

 template<typename BaseVecT>
 void AdaptiveKSearchSurface<BaseVecT>::parseScanPoses(string posefile)
 {
     cout << timestamp << "Parsing scan poses." << endl;
     std::ifstream in(posefile.c_str());
     if(!in.good())
     {
         cout << timestamp << "Unable to open scan pose file " << posefile << endl;
         return;
     }

     // Read vertex information
     float x, y, z;
     std::vector<BaseVecT> v;
     while(in.good())
     {
         in >> x >> y >> z;
         v.push_back(BaseVecT(x, y, z));
     }

     if(v.size() > 0)
     {
         PointBufferPtr loader (new PointBuffer);
         floatArr points(new float[3 * v.size()]);
         for(size_t i = 0; i < v.size(); i++)
         {
             points[3 * i]       = v[i][0];
             points[3 * i + 1]   = v[i][1];
             points[3 * i + 2]   = v[i][2];
         }

         loader->setPointArray(points, v.size());
         size_t n = v.size();

         cout << timestamp << "Creating pose search tree(" << m_searchTreeName << ") with "
              << n << " poses." << endl;

         this->m_poseTree = getSearchTree<BaseVecT>(m_searchTreeName, loader);

         if( !this->m_poseTree )
         {
             cout << timestamp << "No Valid Searchtree class specified!" << endl;
             cout << timestamp << "Class: " << m_searchTreeName << endl;
         }
     }
 }

template<typename BaseVecT>
void AdaptiveKSearchSurface<BaseVecT>::init()
{
    cout << timestamp.getElapsedTime() << "##### Dataset statatistics: ##### " << endl << endl;
    cout << timestamp << "Num points \t: " << this->m_pointBuffer->numPoints() << endl;
    cout << timestamp << this->m_boundingBox << endl;
    cout << endl;
    this->m_centroid = BaseVecT(0.0, 0.0, 0.0);
}

template<typename BaseVecT>
void AdaptiveKSearchSurface<BaseVecT>::calculateSurfaceNormals()
{
    int k_0 = this->m_kn;
    size_t numPoints = this->m_pointBuffer->numPoints();
    FloatChannel pts = *(this->m_pointBuffer->getFloatChannel("points"));

    cout << timestamp.getElapsedTime() << "Initializing normal array..." << endl;

    floatArr normals = floatArr( new float[numPoints * 3] );
    this->m_pointBuffer->setNormalArray(normals, numPoints);

    // Create a progress counter
    string comment = timestamp.getElapsedTime() + "Estimating normals ";
    lvr2::ProgressBar progress(numPoints, comment);

    #pragma omp parallel for schedule(static)
    for(size_t i = 0; i < numPoints; i++) {
        // We have to fit these vector to have the
        // correct return values when performing the
        // search on the stann kd tree. So we don't use
        // the template parameter T for di
        vector<size_t> id;
        vector<float> di;

        int n = 0;
        size_t k = k_0;

        while(n < 5)
        {
            n++;
            /**
             *  @todo Maybe this should be done at the end of the loop
             *        after the bounding box check
             */
            k = k * 2;

            //T* point = this->m_points[i];

            id.clear();
            di.clear();

            this->m_searchTree->kSearch(pts[i], k, id, di);

            float min_x = 1e15f;
            float min_y = 1e15f;
            float min_z = 1e15f;
            float max_x = - min_x;
            float max_y = - min_y;
            float max_z = - min_z;

            float dx, dy, dz;
            dx = dy = dz = 0;

            // Calculate the bounding box of found point set
            /**
             * @todo Use the bounding box object from the old model3d
             *       library for bounding box calculation...
             */
            for(size_t j = 0; j < k; j++) {
                min_x = std::min(min_x, pts[id[j]][0]);
                min_y = std::min(min_y, pts[id[j]][1]);
                min_z = std::min(min_z, pts[id[j]][2]);

                max_x = std::max(max_x, pts[id[j]][0]);
                max_y = std::max(max_y, pts[id[j]][1]);
                max_z = std::max(max_z, pts[id[j]][2]);

                dx = max_x - min_x;
                dy = max_y - min_y;
                dz = max_z - min_z;
            }

            if(boundingBoxOK(dx, dy, dz))
            {
                break;
            }
        }

        // Create a query point for the current point
        auto queryPoint = pts[i];

        // Interpolate a plane based on the k-neighborhood
        Plane<BaseVecT> p;
        bool ransac_ok;

        if(m_calcMethod == 1)
        {
            p = calcPlaneRANSAC(queryPoint, k, id, ransac_ok);
            // Fallback if RANSAC failed
            if(!ransac_ok)
            {
                // compare speed
                p = calcPlane(queryPoint, k, id);
            }
        }
        else if(m_calcMethod == 2)
        {
            p = calcPlaneIterative(queryPoint, k, id);
        }
        else
        {
            p = calcPlane(queryPoint, k, id);
        }
        // Get the mean distance to the tangent plane
        //mean_distance = meanDistance(p, id, k);
        Normal<typename BaseVecT::CoordType> normal(0, 0, 1);
        normal = p.normal;

        // Flip normals towards the center of the scene or nearest scan pose
        if(m_poseTree)
        {
            vector<size_t> nearestPoseIds;
            m_poseTree->kSearch(queryPoint, 1, nearestPoseIds);
            if(nearestPoseIds.size() == 1)
            {
                BaseVecT nearest = pts[nearestPoseIds[0]];
                Normal<typename BaseVecT::CoordType> dir(queryPoint - nearest);
                if(normal.dot(dir) > 0)
                {
                    normal = -normal;
                }
            }
            else
            {
                cout << timestamp.getElapsedTime() << "Could not get nearest scan pose. Defaulting to centroid." << endl;
                Normal<typename BaseVecT::CoordType> dir(queryPoint - m_centroid);
                if(normal.dot(dir) > 0)
                {
                    normal = -normal;
                }
            }
        }
        else
        {
            Normal<typename BaseVecT::CoordType> dir(queryPoint - m_centroid);
            if(normal.dot(dir) > 0)
            {
                normal = -normal;
            }
        }

        // Save result in normal array
        normals[i*3 + 0] = normal.x;
        normals[i*3 + 1] = normal.y;
        normals[i*3 + 2] = normal.z;

        ++progress;
    }
    cout << endl;

    if(this->m_ki)
    {
        interpolateSurfaceNormals();
    }
}

template<typename BaseVecT>
void AdaptiveKSearchSurface<BaseVecT>::interpolateSurfaceNormals()
{
    size_t numPoints     = this->m_pointBuffer->numPoints();
    FloatChannel pts     = *(this->m_pointBuffer->getFloatChannel("points"));
    FloatChannel normals = *(this->m_pointBuffer->getFloatChannel("normals"));
    // Create a temporal normal array for the
    vector<Normal<typename BaseVecT::CoordType>> tmp(
        numPoints,
        Normal<typename BaseVecT::CoordType>(0, 0, 1)
    );

    // Create progress output
    string comment = timestamp.getElapsedTime() + "Interpolating normals ";
    lvr2::ProgressBar progress(numPoints, comment);

    // Interpolate normals
    #pragma omp parallel for schedule(static)
    for( int i = 0; i < (int)numPoints; i++){

        vector<size_t> id;
        vector<float> di;

        this->m_searchTree->kSearch(pts[i], this->m_ki, id, di);

        BaseVecT mean;

        for(int j = 0; j < this->m_ki; j++)
        {
            mean += normals[id[j]];
        }
        auto mean_normal = mean.normalized();

        tmp[i] = mean_normal;

        ///todo Try to remove this code. Should improve the results at all.
        for(int j = 0; j < this->m_ki; j++)
        {
            Normal<typename BaseVecT::CoordType> n = normals[id[j]];

            // Only override existing normals if the interpolated
            // normals is significantly different from the initial
            // estimation. This helps to avoid a too smooth normal
            // field
            if(fabs(n.dot(mean_normal)) > 0.2 )
            {
                normals[id[j]] = mean_normal;
            }
        }
        ++progress;
    }
    cout << endl;
    cout << timestamp.getElapsedTime() << "Copying normals..." << endl;

    for(size_t i = 0; i < numPoints; i++){
        normals[i] = tmp[i];
    }
}

template<typename BaseVecT>
bool AdaptiveKSearchSurface<BaseVecT>::boundingBoxOK(float dx, float dy, float dz)
{
    /**
     * @todo Replace magic number here.
     */
    float e = 0.05f;
    if(dx < e * dy) return false;
    else if(dx < e * dz) return false;
    else if(dy < e * dx) return false;
    else if(dy < e * dz) return false;
    else if(dz < e * dx) return false;
    else if(dz < e * dy) return false;
    return true;
}

// template<typename BaseVecT>
// void AdaptiveKSearchSurface<BaseVecT>::getkClosestVertices(const VertexT &v,
//         const size_t &k, vector<VertexT> &nb)
// {
//     vector<int> id;

//     //Allocate ANN point
//     {
//         coord<float> p;
//         p[0] = v[0];
//         p[1] = v[1];
//         p[2] = v[2];

//         //Find nearest tangent plane
//         // m_pointTree.ksearch( p, k, id, 0 );
//         this->m_searchTree->kSearch( p, k, id );
//     }

//     //parse result
//     if ( this->m_colors )
//     {
//         for ( size_t i = 0; i < k; i++ )
//         {
//             VertexT tmp;
//             nb.push_back( tmp );
//             nb[i].x = this->m_points[id[i]][0];
//             nb[i].y = this->m_points[id[i]][1];
//             nb[i].z = this->m_points[id[i]][2];
//     /*      nb[i].r = this->m_colors[id[i]][0];
//             nb[i].g = this->m_colors[id[i]][1];
//             nb[i].b = this->m_colors[id[i]][2]; */
//         }
//     }
//     else
//     {
//         for ( size_t i = 0; i < k; i++ )
//         {
//             VertexT tmp( this->m_points[id[i]][0], this->m_points[id[i]][1],
//                     this->m_points[id[i]][2] );
//             nb.push_back( tmp );
//         }
//     }
// }

// template<typename BaseVecT>
// float AdaptiveKSearchSurface<BaseVecT>::meanDistance(const Plane<BaseVecT> &p,
//         const vector<unsigned long> &id, const int &k)
// {
//     float sum = 0;
//     for(int i = 0; i < k; i++){
//         sum += distance(fromID(id[i]), p);
//     }
//     sum = sum / k;
//     return sum;
// }

// template<typename BaseVecT>
// float AdaptiveKSearchSurface<BaseVecT>::distance(VertexT v, Plane<BaseVecT> p)
// {
//     return fabs((v - p.p) * p.n);
// }

template<typename BaseVecT>
pair<typename BaseVecT::CoordType, typename BaseVecT::CoordType>
    AdaptiveKSearchSurface<BaseVecT>::distance(BaseVecT p) const
{

    FloatChannel pts     = *(this->m_pointBuffer->getFloatChannel("points"));
    FloatChannel normals = *(this->m_pointBuffer->getFloatChannel("normals"));
    size_t numPoints     = pts.numElements();
    int k = this->m_kd;

    vector<size_t> id;
    vector<float> di;

    //Allocate ANN point
    {
        // Find nearest tangent plane
        this->m_searchTree->kSearch( p, k, id, di );
    }

    BaseVecT nearest;
    BaseVecT avg_normal;

    for ( int i = 0; i < k; i++ )
    {
        //Get nearest tangent plane
        auto vq = pts[id[i]];

        //Get normal
        auto n = normals[id[i]];

        nearest += vq;
        avg_normal += n;
    }

    avg_normal /= k;
    nearest /= k;
    auto normal = avg_normal.normalized();

    //Calculate distance
    auto projectedDistance = (p - BaseVecT(nearest)).dot(normal);
    auto euklideanDistance = (p - BaseVecT(nearest)).length();

    return std::make_pair(projectedDistance, euklideanDistance);
    // return make_pair(euklideanDistance, projectedDistance);
}

// template<typename BaseVecT>
// VertexT AdaptiveKSearchSurface<BaseVecT>::fromID(int i){
//     return VertexT(
//             this->m_points[i][0],
//             this->m_points[i][1],
//             this->m_points[i][2]);
// }

template<typename BaseVecT>
Plane<BaseVecT> AdaptiveKSearchSurface<BaseVecT>::calcPlane(
    const BaseVecT &queryPoint,
    int k,
    const vector<size_t> &id
)
{
    FloatChannel pts     = *(this->m_pointBuffer->getFloatChannel("points"));
    size_t numPoints     = pts.numElements();

    /**
     * @todo Think of a better way to code this magic number.
     */
    const float epsilon = 100.0;

    // Calculate a least sqaures fit to the given points
    Eigen::Vector3f C;
    Eigen::VectorXf F(k);
    Eigen::MatrixXf B(k, 3);

    for(int j = 0; j < k; j++) {
        BaseVecT p = pts[id[j]];
        F(j)    = p.y;
        B(j, 0) = 1.0f;
        B(j, 1) = p.x;
        B(j, 2) = p.z;
    }

    C = B.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(F);

    // Calculate to vectors in the fitted plane
    auto z1 = C(0) + C(1) * (queryPoint.x + epsilon) + C(2) * queryPoint.z;
    auto z2 = C(0) + C(1) * queryPoint.x + C(2) * (queryPoint.z + epsilon);

    // Calculcate the plane's normal via the cross product
    auto diff1 = BaseVecT(queryPoint.x + epsilon, z1, queryPoint.z) - queryPoint;
    auto diff2 = BaseVecT(queryPoint.x, z2, queryPoint.z + epsilon) - queryPoint;

    auto normal = diff1.cross(diff2).normalized();

    if(isnan(normal.getX()) || isnan(normal.getY()) || isnan(normal.getZ()))
    {
        cout << "Warning: Nan-coordinate in plane normal." << endl;
    }

    // Create a plane representation and return the result
    Plane<BaseVecT> p;
    // p.a = C(0);
    // p.b = C(1);
    // p.c = C(2);
    p.normal = normal;
    p.pos = queryPoint;

    return p;
}

template<typename BaseVecT>
Plane<BaseVecT> AdaptiveKSearchSurface<BaseVecT>::calcPlaneIterative(
    const BaseVecT &queryPoint,
    int k,
    const vector<size_t> &id
)
{

    FloatChannel pts     = *(this->m_pointBuffer->getFloatChannel("points"));
    FloatChannel normals = *(this->m_pointBuffer->getFloatChannel("normals"));
    size_t numPoints     = pts.numElements();

    Plane<BaseVecT> p;
    BaseVecT normal;

    //x
    float xx = 0.0;
    float xy = 0.0;
    float xz = 0.0;

    //y
    float yy = 0.0;
    float yz = 0.0;

    //z
    float zz = 0.0;

    for(int j = 0; j < k; j++) {
        auto p = pts[id[j]];

        auto r = p - queryPoint;

        xx += r.x * r.x;
        xy += r.x * r.y;
        xz += r.x * r.z;
        yy += r.y * r.y;
        yz += r.y * r.z;
        zz += r.z * r.z;

    }

    //determinante
    float det_x = yy * zz - yz * yz;
    float det_y = xx * zz - xz * xz;
    float det_z = xx * yy - xy * xy;

    float dir_x;
    float dir_y;
    float dir_z;
    // det X biggest
    if( det_x >= det_y && det_x >= det_z){

        if(det_x <= 0.0){
            //not a plane
        }

        dir_x = 1.0;
        dir_y = (xz * yz - xy * zz) / det_x;
        dir_z = (xy * yz - xz * yy) / det_x;
    } //det Y biggest
    else if( det_y >= det_x && det_y >= det_z){

        if(det_y <= 0.0){
            // not a plane
        }

        dir_x = (yz * xz - xy * zz) / det_y;
        dir_y = 1.0;
        dir_z = (xy * xz - yz * xx) / det_y;
    } // det Z biggest
    else{
        if(det_z <= 0.0){
            // not a plane
        }

        dir_x = (yz * xy - xz * yy ) / det_z;
        dir_y = (xz * xy - yz * xx ) / det_z;
        dir_z = 1.0;
    }

    float invnorm = 1/sqrtf( dir_x * dir_x + dir_y * dir_y + dir_z * dir_z );

    normal.x = dir_x * invnorm;
    normal.y = dir_y * invnorm;
    normal.z = dir_z * invnorm;

    p.normal = normal;
    p.pos = queryPoint;

    return p;
}

// template<typename BaseVecT>
// const VertexT AdaptiveKSearchSurface<BaseVecT>::operator[]( const size_t& index ) const
// {
//     return VertexT(
//             m_points[index].x, m_points[index].y, m_points[index].z,
//             m_colors[index].r, m_colors[index].g, m_colors[index].b );
// }

//    template<typename BaseVecT>
// size_t AdaptiveKSearchSurface<BaseVecT>::getNumPoints()
// {
//     return m_numPoints;
// }

template<typename BaseVecT>
Plane<BaseVecT> AdaptiveKSearchSurface<BaseVecT>::calcPlaneRANSAC(
    const BaseVecT &queryPoint,
    int k,
    const vector<size_t> &id,
    bool &ok
)
{
    FloatChannel pts     = *(this->m_pointBuffer->getFloatChannel("points"));
    FloatChannel normals = *(this->m_pointBuffer->getFloatChannel("normals"));
    size_t numPoints     = pts.numElements();

   Plane<BaseVecT> p;

   //representation of best regression plane by point and normal
   BaseVecT bestPoint;
   Normal<typename BaseVecT::CoordType> bestNorm(0, 0, 1);

   float bestdist = numeric_limits<float>::max();
   float dist     = 0;

   int iterations              = 0;
   int nonimproving_iterations = 0;

   //  int max_nonimproving = max(5, k / 2);
   int max_interations  = 10;

   while((nonimproving_iterations < 5) && (iterations < max_interations))
   {
       // randomly choose 3 disjoint points
       int c = 0;

       std::set<unsigned long> ids;
       std::default_random_engine generator;
       std::uniform_int_distribution<unsigned long> distribution(0, id.size() - 1);
       auto number = std::bind(distribution, generator);
       do
       {
           ids.insert(number());
           c++;
           if (c == 20) cout << "Deadlock" << endl;
       }
       while (ids.size() < 3 && c <= 20);

       vector<unsigned long> sample_ids(ids.size());
       std::copy(ids.begin(), ids.end(), sample_ids.begin());

       BaseVecT point1 = pts[sample_ids[0]];
       BaseVecT point2 = pts[sample_ids[1]];
       BaseVecT point3 = pts[sample_ids[2]];

       auto n0 = (point1 - point2).cross(point1 - point3).normalized();

       //compute error to at most 50 other randomly chosen points
       dist = 0;
       int n = std::min(50, k);
       for(int i = 0; i < n; i++)
       {
           int index = id[rand() % k];
           BaseVecT refpoint = pts[index];
           dist += fabs(refpoint.dot(n0) - point1.dot(n0));
       }
       if(n != 0) dist /= n;

       //a new optimum is found
       if(dist < bestdist)
       {
           bestdist = dist;

           bestPoint = point1;
           bestNorm = n0;

           nonimproving_iterations = 0;
       }
       else
       {
           nonimproving_iterations++;
       }

       iterations++;
   }

   // Save plane parameters
   // p.a = 0;
   // p.b = 0;
   // p.c = 0;
   p.normal = bestNorm;
   p.pos = bestPoint;

   return p;
}

// template<typename BaseVecT>
// Plane<BaseVecT> AdaptiveKSearchSurface<BaseVecT>::calcPlaneRANSACfromPoints(const VertexT &queryPoint,
//         const int &k,
//         const vector<VertexT> points,
//         Normal<typename BaseVecT::CoordType> c_normal,
//         bool &ok)
// {
//     // the resulting plane
//     Plane<BaseVecT> p;

//     VertexT point1;
//     VertexT point2;
//     VertexT point3;

//     //representation of best regression plane by point and normal
//     VertexT bestpoint;
//     Normal<typename BaseVecT::CoordType> bestNorm;

//     float bestdist = numeric_limits<float>::max();
//     float dist     = 0;

//     int iterations              = 0;
//     int nonimproving_iterations = 0;

//     int max_interations = max(10, k / 2);
//     //int max_interations  = 10;

//     bool first_it = true;
//     while((nonimproving_iterations < 5) && (iterations < max_interations))
//     {
//         Normal<typename BaseVecT::CoordType> n0;
//         //randomly choose 3 disjoint points
//         int c = 0;
//         do{
//             int index[3];
//             for(int i = 0; i < 3; i++)
//             {
//                 float f = 1.0 * rand() / RAND_MAX;
//                 int r = (int)(f * points.size() - 1);
//                 index[i] = r;
//             }

//             point1 = VertexT(this->m_points[index[0]][0],this->m_points[index[0]][1], this->m_points[index[0]][2]);
//             point2 = VertexT(this->m_points[index[1]][0],this->m_points[index[1]][1], this->m_points[index[1]][2]);
//             point3 = VertexT(this->m_points[index[2]][0],this->m_points[index[2]][1], this->m_points[index[2]][2]);

//             //compute normal of the plane given by the 3 points (look at the end)
//             n0 = (point1 - point2).cross(point1 - point3);
//             n0.normalize();
//             c++;

//             // check if the three points are disjoint
//             if( (point1 != point2) && (point2 != point3) && (point3 != point1) )
//             {
//                 // at first, use interpolated normal
//                 Normal<typename BaseVecT::CoordType> check(0.0, 0.0, 0.0);
//                 if(first_it && !(check == c_normal))
//                 {
//                     n0 = c_normal;
//                     n0.normalize();
//                     first_it = false;
//                 }
//                 break;
//             }
//             // Check for deadlock
//             if(c > 50)
//             {
//                 cout << "DL " << k << endl;
//                 ok = false;
//                 return p;
//             }
//         }
//         while(true);

//         //compute error to at most 10 other randomly chosen points
//         dist = 0;
//         int n = min(10,k);
//         for(int i = 0; i < n; i++)
//         {
//             int index = rand() % points.size();
//             VertexT refpoint = VertexT(points[index][0], points[index][1] ,points[index][2]);
//             dist += fabs(refpoint * n0 - point1 * n0);
//         }
//         if(n != 0) dist /= n;

//         //a new optimum is found
//         if(dist < bestdist)
//         {
//             bestdist = dist;
//             bestpoint = point1;
//             bestNorm = n0;

//             nonimproving_iterations = 0;
//         }
//         else
//         {
//             nonimproving_iterations++;
//         }

//         iterations++;
//     } // end while

//     // Save plane parameters
//     p.a = 0;
//     p.b = 0;
//     p.c = 0;
//     p.n = bestNorm;
//     p.p = bestpoint;

//     return p;
// }

// template<typename BaseVecT>
// void AdaptiveKSearchSurface<BaseVecT>::colorizePointCloud(
//       typename AdaptiveKSearchSurface<BaseVecT>::Ptr pcm, const float& sqrtMaxDist,
//       const unsigned char* blankColor)
// {
// //    if( !m_colors )
// //    {
// //        m_colors = color3bArr( new color<uchar>[ m_numPoints ] );
// //    }
// //
// //#pragma omp parallel for schedule(dynamic)
// //    for( size_t i = 0; i < m_numPoints; i++ )
// //    {
// //        std::vector< VertexT > nearestPoint( 1 );
// //
// //        VertexT p( this->getPoint( i ) );
// //        pcm->getkClosestVertices( p, 1, nearestPoint );
// //        if(nearestPoint.size() )
// //        {
// //            if( p.sqrDistance( nearestPoint[0] ) < sqrtMaxDist )
// //            {
// //                m_colors[i][0] = nearestPoint[0].r;
// //                m_colors[i][1] = nearestPoint[0].g;
// //                m_colors[i][2] = nearestPoint[0].b;
// //            }
// //            else if( blankColor )
// //            {
// //                m_colors[i][0] = blankColor[0];
// //                m_colors[i][1] = blankColor[1];
// //                m_colors[i][2] = blankColor[2];
// //            }
// //        }
// //    }
// }

} // namespace lvr2


/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2021, Christoph Neuhauser
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <boost/algorithm/string/case_conv.hpp>

#include <Utils/File/Logfile.hpp>
#include <Utils/File/FileLoader.hpp>
#include <Utils/StringUtils.hpp>
#include <Utils/Convert.hpp>

#include "../StreamlineTracingDefines.hpp"
#include "../StreamlineTracingGrid.hpp"
#include "GridLoader.hpp"
#include "StructuredGridVtkLoader.hpp"

void StructuredGridVtkLoader::_readLines(
        ReadMode readMode, int numObjects, float* fieldData,
        size_t& charPtr, size_t& length, const char* fileBuffer) {
    int lineNum = 0;
    std::string lineBuffer;
    std::vector<std::string> splitLineString;
    while (charPtr < length && lineNum < numObjects) {
        lineBuffer.clear();
        while (charPtr < length) {
            char currentChar = fileBuffer[charPtr];
            if (currentChar == '\n' || currentChar == '\r') {
                charPtr++;
                break;
            }
            lineBuffer.push_back(currentChar);
            charPtr++;
        }

        if (lineBuffer.empty()) {
            sgl::Logfile::get()->throwError(
                    "Error in RbcBinFileLoader::_readLines: Encountered an empty line.");
        }

        if (readMode == ReadMode::SCALAR) {
            fieldData[lineNum] = sgl::fromString<float>(lineBuffer);
        } else if (readMode == ReadMode::VECTOR) {
            splitLineString.clear();
            sgl::splitStringWhitespace(lineBuffer, splitLineString);
            if (splitLineString.size() != 3) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::_readLines: Vector mode, but line contains less than three "
                        "items.");
            }
            fieldData[lineNum * 3] = sgl::fromString<float>(splitLineString.at(0));
            fieldData[lineNum * 3 + 1] = sgl::fromString<float>(splitLineString.at(1));
            fieldData[lineNum * 3 + 2] = sgl::fromString<float>(splitLineString.at(2));
        }

        lineNum++;
    }

    if (lineNum < numObjects) {
        sgl::Logfile::get()->throwError(
                "Error in RbcBinFileLoader::_readLines: The file has ended before all values could be read.");
    }
}

void StructuredGridVtkLoader::_convertScalarFieldCellToPointMode(
        const float* scalarFieldCell, float* scalarFieldPoint, int xs, int ys, int zs) {
    #pragma omp parallel for shared(xs, ys, zs, scalarFieldCell, scalarFieldPoint)  default(none)
    for (int z = 0; z < zs; z++) {
        for (int y = 0; y < ys; y++) {
            for (int x = 0; x < xs; x++) {
                int numNeighboringCells = 0;
                float valueSum = 0.0f;
                for (int zc = z - 1; zc <= z; zc++) {
                    for (int yc = y - 1; yc <= y; yc++) {
                        for (int xc = x - 1; xc <= x; xc++) {
                            if (xc >= 0 && yc >= 0 && zc >= 0 && xc < xs - 1 && yc < ys - 1 && zc < zs - 1) {
                                numNeighboringCells++;
                                valueSum += scalarFieldCell[IDXS_C(xc, yc, zc)];
                            }
                        }
                    }
                }
                scalarFieldPoint[IDXS(x, y, z)] = valueSum / float(numNeighboringCells);
            }
        }
    }
}

void StructuredGridVtkLoader::load(const std::string& dataSourceFilename, StreamlineTracingGrid* grid) {
    int xs = 0, ys = 0, zs = 0;

    uint8_t* buffer = nullptr;
    size_t length = 0;
    bool loaded = sgl::loadFileFromSource(dataSourceFilename, buffer, length, false);
    if (!loaded) {
        sgl::Logfile::get()->throwError(
                "Error in RbcBinFileLoader::load: Couldn't open file \"" + dataSourceFilename + "\".");
    }
    char* fileBuffer = reinterpret_cast<char*>(buffer);

    std::string lineBuffer;
    std::string stringBuffer;
    std::vector<std::string> splitLineString;
    bool isBinaryMode = false;
    bool pointDataMode = true; //< Point data or cell data.
    std::string scalarFieldName;
    bool ignoreNextScalarData = false; //< For ignoring everything that is not of type float.
    int nextScalarDataBytesPerElement = 4; //< For ignoring everything that is not of type float.
    int numPoints = 0;
    int numCells = 0;

    glm::vec3* gridPoints = nullptr;
    float* points = nullptr;
    std::map<std::string, float*> vectorFields;
    std::map<std::string, float*> scalarFields;

    for (size_t charPtr = 0; charPtr < length; ) {
        lineBuffer.clear();
        while (charPtr < length) {
            char currentChar = fileBuffer[charPtr];
            if (currentChar == '\n' || currentChar == '\r') {
                charPtr++;
                break;
            }
            lineBuffer.push_back(currentChar);
            charPtr++;
        }

        if (lineBuffer.empty()) {
            continue;
        }

        splitLineString.clear();
        sgl::splitStringWhitespace(lineBuffer, splitLineString);
        if (splitLineString.empty()) {
            continue;
        }

        if (splitLineString.front() == "BINARY") {
            isBinaryMode = true;
        } else if (splitLineString.front() == "ASCII") {
            isBinaryMode = false;
        } else if (splitLineString.front() == "DATASET") {
            if (splitLineString.size() != 2 || splitLineString.at(1) != "STRUCTURED_GRID") {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid DATASET mode in file \""
                        + dataSourceFilename + "\".");
            }
        } else if (splitLineString.front() == "DIMENSIONS") {
            if (splitLineString.size() != 4) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid DIMENSIONS string in file \""
                        + dataSourceFilename + "\".");
            }
            xs = sgl::fromString<int>(splitLineString.at(1));
            ys = sgl::fromString<int>(splitLineString.at(2));
            zs = sgl::fromString<int>(splitLineString.at(3));
            numPoints = xs * ys * zs;
            numCells = (xs - 1) * (ys - 1) * (zs - 1);
        } else if (splitLineString.front() == "POINTS") {
            if (splitLineString.size() != 3) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid POINTS string in file \""
                        + dataSourceFilename + "\".");
            }
            if (splitLineString.at(2) != "float") {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid POINTS string in file \""
                        + dataSourceFilename + "\". The loader only supports float data.");
            }
            int numPointsLocal = sgl::fromString<int>(splitLineString.at(1));
            if (numPointsLocal != numPoints) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid POINTS string in file \""
                        + dataSourceFilename + "\". The number of points does not match the dimensions.");
            }
            if (gridPoints) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: The file \""
                        + dataSourceFilename + "\" contains more than one POINTS statement.");
            }

            gridPoints = new glm::vec3[numPoints];
            points = reinterpret_cast<float*>(gridPoints);
            if (isBinaryMode) {
                size_t numBytes = sizeof(float) * numPoints * 3;
                if (charPtr + numBytes > length) {
                    sgl::Logfile::get()->throwError(
                            "Error in RbcBinFileLoader::load: The file \"" + dataSourceFilename
                            + "\" ended before all data from a POINTS statement could be read.");
                }
                memcpy(points, fileBuffer + charPtr, sizeof(float) * numPoints * 3);
                swapEndianness(points, numPoints * 3);
                charPtr += sizeof(float) * numPoints * 3;
            } else {
                _readLines(ReadMode::VECTOR, numPoints, points, charPtr, length, fileBuffer);
            }
        } else if (splitLineString.front() == "POINT_DATA") {
            if (splitLineString.size() != 2) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid POINT_DATA string in file \""
                        + dataSourceFilename + "\".");
            }
            int numPointsLocal = sgl::fromString<int>(splitLineString.at(1));
            if (numPointsLocal != numPoints) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid POINT_DATA string in file \""
                        + dataSourceFilename + "\". The number of points does not match the dimensions.");
            }

            pointDataMode = true;
        } else if (splitLineString.front() == "CELL_DATA") {
            if (splitLineString.size() != 2) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid CELL_DATA string in file \""
                        + dataSourceFilename + "\".");
            }
            int numCellsLocal = sgl::fromString<int>(splitLineString.at(1));
            if (numCellsLocal != numCells) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid CELL_DATA string in file \""
                        + dataSourceFilename + "\". The number of points does not match the dimensions.");
            }

            pointDataMode = false;
        } else if (splitLineString.front() == "VECTORS") {
            if (splitLineString.size() != 3) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid VECTORS string in file \""
                        + dataSourceFilename + "\".");
            }
            if (splitLineString.at(2) != "float") {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid VECTORS string in file \""
                        + dataSourceFilename + "\". The loader only supports float data.");
            }

            std::string vectorFieldName = splitLineString.at(1);

            int numVectors = pointDataMode ? numPoints : numCells;
            auto* vectorField = new float[numVectors * 3];
            if (isBinaryMode) {
                size_t numBytes = sizeof(float) * numVectors * 3;
                if (charPtr + numBytes > length) {
                    sgl::Logfile::get()->throwError(
                            "Error in RbcBinFileLoader::load: The file \"" + dataSourceFilename
                            + "\" ended before all data from a POINTS statement could be read.");
                }
                memcpy(vectorField, fileBuffer + charPtr, sizeof(float) * numVectors * 3);
                swapEndianness(vectorField, numVectors * 3);
                charPtr += sizeof(float) * numVectors * 3;
            } else {
                _readLines(
                        ReadMode::VECTOR, numVectors, vectorField,
                        charPtr, length, fileBuffer);
            }

            if (vectorFields.find(vectorFieldName) != vectorFields.end()) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: The vector field \"" + vectorFieldName
                        + "\" exists more than one time in the file \"" + dataSourceFilename + "\".");
            }

            if (pointDataMode) {
                vectorFields.insert(std::make_pair(vectorFieldName, vectorField));
            } else {
                // Ignoring cell data for now.
                delete[] vectorField;
            }
        } else if (splitLineString.front() == "SCALARS") {
            if (splitLineString.size() != 4) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid SCALARS string in file \""
                        + dataSourceFilename + "\".");
            }
            if (splitLineString.at(2) != "float" && splitLineString.at(2) != "unsigned_char") {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid SCALARS string in file \"" + dataSourceFilename +
                        "\". The loader only supports float data. Additionally, unsigned_char data can be ignored.");
            }
            if (splitLineString.at(3) != "1") {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Invalid SCALARS string in file \""
                        + dataSourceFilename + "\". The loader only supports scalars with one value.");
            }
            if (!scalarFieldName.empty()) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Encountered another SCALARS string in file \""
                        + dataSourceFilename + "\" even though no LOOKUP_TABLE statement was given.");
            }

            scalarFieldName = splitLineString.at(1);
            ignoreNextScalarData = splitLineString.at(2) != "float";
            if (splitLineString.at(2) == "float") {
                nextScalarDataBytesPerElement = 4;
            } else if (splitLineString.at(2) == "unsigned_char") {
                nextScalarDataBytesPerElement = 1;
            }
        } else if (splitLineString.front() == "LOOKUP_TABLE") {
            if (scalarFieldName.empty()) {
                sgl::Logfile::get()->throwError(
                        "Error in RbcBinFileLoader::load: Encountered a LOOKUP_TABLE string in file \""
                        + dataSourceFilename + "\" before a SCALARS string.");
            }

            int numScalars = pointDataMode ? numPoints : numCells;

            if (!ignoreNextScalarData) {
                auto* scalarField = new float[numScalars];
                if (isBinaryMode) {
                    size_t numBytes = sizeof(float) * numScalars;
                    if (charPtr + numBytes > length) {
                        sgl::Logfile::get()->throwError(
                                "Error in RbcBinFileLoader::load: The file \"" + dataSourceFilename
                                + "\" ended before all data from a SCALARS statement could be read.");
                    }
                    memcpy(scalarField, fileBuffer + charPtr, sizeof(float) * numScalars);
                    swapEndianness(scalarField, numScalars);
                    charPtr += sizeof(float) * numScalars;
                } else {
                    _readLines(
                            ReadMode::SCALAR, numScalars, scalarField,
                            charPtr, length, fileBuffer);
                }

                if (scalarFields.find(scalarFieldName) != scalarFields.end()) {
                    sgl::Logfile::get()->throwError(
                            "Error in RbcBinFileLoader::load: The scalar field \"" + scalarFieldName
                            + "\" exists more than one time in the file \"" + dataSourceFilename + "\".");
                }
                if (pointDataMode) {
                    scalarFields.insert(std::make_pair(scalarFieldName, scalarField));
                } else {
                    // Convert cell data to point data.
                    auto* scalarFieldPoint = new float[numPoints];
                    _convertScalarFieldCellToPointMode(scalarField, scalarFieldPoint, xs, ys, zs);
                    scalarFields.insert(std::make_pair(scalarFieldName, scalarFieldPoint));
                    delete[] scalarField;
                }
            } else {
                if (isBinaryMode) {
                    size_t numBytes = nextScalarDataBytesPerElement * numScalars;
                    if (charPtr + numBytes > length) {
                        sgl::Logfile::get()->throwError(
                                "Error in RbcBinFileLoader::load: The file \"" + dataSourceFilename
                                + "\" ended before all data from a SCALARS statement could be read.");
                    }
                    charPtr += sizeof(float) * numScalars;
                } else {
                    _readLines(
                            ReadMode::SKIP, numScalars, nullptr,
                            charPtr, length, fileBuffer);
                }
            }
            scalarFieldName.clear();
        }
    }

    if (!gridPoints) {
        sgl::Logfile::get()->throwError(
                "Error in RbcBinFileLoader::load: The file \"" + dataSourceFilename
                + "\" does not contain a POINTS statement.");
    }

    /*sgl::AABB3 domainExtents;
    for (int i = 0; i < numPoints; i++) {
        domainExtents.combine(gridPoints[i]);
    }

    glm::vec3 dimensions = domainExtents.getDimensions();
    float maxDimension = std::max(dimensions.x, std::max(dimensions.y, dimensions.z));
    float cellStep = 1.0f / maxDimension;*/

    float maxDimension = float(std::max(xs - 1, std::max(ys - 1, zs - 1)));
    float cellStep = 1.0f / maxDimension;

    auto itVelocity = vectorFields.find("velocity");
    if (itVelocity == vectorFields.end()) {
        sgl::Logfile::get()->throwError(
                "Error in RbcBinFileLoader::load: The file \"" + dataSourceFilename
                + "\" does not contain a vector field called 'velocity'.");
    }
    float* velocityField = itVelocity->second;

    grid->setGridMetadata(xs, ys, zs, cellStep, cellStep, cellStep);

    if (scalarFields.find("velocityMagnitude") == scalarFields.end()) {
        auto* velocityMagnitudeField = new float[numPoints];
        computeVectorMagnitudeField(velocityField, velocityMagnitudeField, xs, ys, zs);
        scalarFields.insert(std::make_pair("Velocity Magnitude", velocityMagnitudeField));
    }

    if (vectorFields.find("vorticity") == vectorFields.end()
            || scalarFields.find("vorticityMagnitude") == scalarFields.end()
            || scalarFields.find("helicity") == scalarFields.end()) {
        float* vorticityField;
        auto it = vectorFields.find("vorticity");
        if (it == vectorFields.end()) {
            vorticityField = new float[numPoints * 3];
            computeVorticityField(velocityField, vorticityField, xs, ys, zs, cellStep, cellStep, cellStep);
            vectorFields.insert(std::make_pair("Vorticity", vorticityField));
        } else {
            vorticityField = it->second;
        }
        if (scalarFields.find("vorticityMagnitude") == scalarFields.end()) {
            auto* vorticityMagnitudeField = new float[numPoints];
            computeVectorMagnitudeField(vorticityField, vorticityMagnitudeField, xs, ys, zs);
            scalarFields.insert(std::make_pair("Vorticity Magnitude", vorticityMagnitudeField));
        }
        if (scalarFields.find("helicity") == scalarFields.end()) {
            auto* helicityField = new float[numPoints];
            computeHelicityField(velocityField, vorticityField, helicityField, xs, ys, zs);
            scalarFields.insert(std::make_pair("Helicity", helicityField));
        }
    }

    for (auto& it : vectorFields) {
        // Convert first letter to upper case.
        std::string vectorDisplayName;
        vectorDisplayName = boost::to_upper_copy(it.first.substr(0, 1)) + it.first.substr(1);
        grid->addVectorField(it.second, vectorDisplayName);
    }

    for (auto& it : scalarFields) {
        // Convert first letter to upper case.
        std::string scalarDisplayName;
        scalarDisplayName = boost::to_upper_copy(it.first.substr(0, 1)) + it.first.substr(1);
        std::string::size_type magnitudePos = scalarDisplayName.find("Magnitude");
        if (scalarDisplayName.find(" Magnitude") == std::string::npos
                && magnitudePos != std::string::npos && magnitudePos > 0) {
            scalarDisplayName =
                    scalarDisplayName.substr(0, magnitudePos) + " " + scalarDisplayName.substr(magnitudePos);
        }
        grid->addScalarField(it.second, scalarDisplayName);
    }

    delete[] gridPoints;
    delete[] buffer;
    buffer = nullptr;
}


//
// Created by Markus on 19.01.17.
//
#include <iostream>
#include <memory>
#include <vector>
#include <boost/variant.hpp>
#include <map>

#include "LCFRS.h"
#include "LCFRS_Parser.h"
#include "LCFRS_util.h"
#include "../Names.h"

using namespace std;
namespace LCFR {

    void manual_parse(const LCFRS<string, string> &grammar, const vector<string> &word);

    int main() {
        LCFRS<string, string> grammar("S", "Test");

//    grammar.add_rule(construct_rule("S", vector<string>{"a a x{0,0}"}, "S"));
//    grammar.add_rule(construct_rule("S", vector<string>{"a"}, ""));
//    vector<string> word{"a", "a", "a", "a"};
//    manual_parse(grammar, word);

//    grammar.add_rule(construct_rule("S", vector<string>{"x{0,0} x{0,1}"}, "A"));
//    grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A"));
//    grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, "A"));
//    vector<string> word;
//    tokenize<vector<string>>("a a a b b b", word);

//    grammar.add_rule(construct_rule("S", vector<string>{"x{0,0} x{0,1}"}, "A"));
//    grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A"));
//    grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, ""));
//    vector<string> word;
//    tokenize<vector<string>>("a a a a a a a a a b b b b b b b b b", word);

//    grammar.add_rule(construct_rule("S", vector<string>{"x{0,0} x{1,0} x{0,1} x{1,1}"}, "A B"));
//    grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A"));
//    grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, ""));
//    grammar.add_rule(construct_rule("B", vector<string>{"a", "b"}, ""));
//    grammar.add_rule(construct_rule("B", vector<string>{"x{0,0} a", "x{0,1} b"}, "B"));
//    vector<string> word;
//    tokenize<vector<string>>("a a a a b b b b", word);

        grammar.add_rule(construct_rule("S", vector<string>{"x{0,1} x{1,1} x{0,0} x{1,0}"}, "A B", 0));
        grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A", 1));
        grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, "", 2));
        grammar.add_rule(construct_rule("B", vector<string>{"", ""}, "", 3)); // ε-rule
        grammar.add_rule(construct_rule("B", vector<string>{"x{0,0} a", "x{0,1} b"}, "B", 4));
        vector<string> word;
        tokenize<vector<string>>("b b b b b a a a a a", word);

//        // grammar that contains a chain rule
//        grammar.add_rule(construct_rule("S", vector<string>{"x{0,0} x{0,1}"}, "A", 0));
//        grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A", 1));
//        grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, "", 2));
//        grammar.add_rule(construct_rule("A", vector<string>{"x{0,0}", "x{0,1}"}, "A", 3));
//        vector<string> word;
//        tokenize<vector<string>>("a b", word);

//        // grammar that contains chain rules
//        grammar.add_rule(construct_rule("S", vector<string>{"x{0,1} x{1,1} x{0,0} x{1,0}"}, "A B", 0));
//        grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A", 1));
//        grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, "", 2));
//        grammar.add_rule(construct_rule("B", vector<string>{"x{0,0} a", "x{0,1} b"}, "B", 3));
//        grammar.add_rule(construct_rule("B", vector<string>{"a", "b"}, "", 2));
//        grammar.add_rule(construct_rule("A", vector<string>{"x{0,0}", "x{0,1}"}, "B", 4));
//        grammar.add_rule(construct_rule("B", vector<string>{"x{0,0}", "x{0,1}"}, "A", 5));
//        grammar.add_rule(construct_rule("S", vector<string>{"x{0,0}"}, "S", 6));
//        vector<string> word;
//        tokenize<vector<string>>("b b b a a a", word);

//        // chain rules in upper position
//        grammar.add_rule(construct_rule("S", vector<string>{"x{0,0}"}, "A", 0));
//        grammar.add_rule(construct_rule("S", vector<string>{"x{0,0}"}, "S", 1));
//        grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a"}, "A", 2));
//        grammar.add_rule(construct_rule("A", vector<string>{"a"}, "", 3));
//        vector<string> word;
//        tokenize<vector<string>>("a", word);

//    grammar.add_rule(construct_rule("S", vector<string>{"x{0,0} x{1,0}"}, "A B")); // deleting rule
//    grammar.add_rule(construct_rule("A", vector<string>{"x{0,0} a", "x{0,1} b"}, "A")); // as many a's as b's
//    grammar.add_rule(construct_rule("A", vector<string>{"a", "b"}, ""));
//    grammar.add_rule(construct_rule("B", vector<string>{"", ""}, "")); // ε-rule
//    grammar.add_rule(construct_rule("B", vector<string>{"x{0,0} b b"}, "B")); // an even number of b's
//    vector<string> word;
//    tokenize<vector<string>>("a a a a a a b b b b b b", word);

        clog << grammar << endl;

        LCFRS_Parser<string, string> parser(grammar, word);
        parser.do_parse();

        if(parser.recognized()) {

            std::clog << "Recognizing finished successfully!" << std::endl;

            map<PassiveItem<string>, TraceItem<string, string>> trace = parser.get_trace();
//            clog << "Parses:" << endl;

//        print_top_trace(grammar, trace, word);

            std::vector<std::string> nodeLabels {"S", "A", "B", "C"};
            auto const nodeLabelsPtr = std::make_shared<const std::vector<string>>(nodeLabels);
            std::vector<EdgeLabelT> edgeLabels {0, 1, 2, 3, 4, 5, 6};
            auto const edgeLabelsPtr = std::make_shared<const std::vector<EdgeLabelT>>(edgeLabels);

            parser.prune_trace();

            std::clog << "Trace was pruned." << std::endl;

            auto hg{
                    parser.convert_trace_to_hypergraph(
                            nodeLabelsPtr
                            , edgeLabelsPtr
                    )};

            std::clog << std::endl;
            std::clog << "Nodes and outgoing egdes in the pruned trace: " << std::endl;
            std::clog << "Initial node: " << hg.second << std::endl;
            for (auto const& element : *(hg.first)) {
                std::clog << element << ": ";
                for(auto const& edge : (hg.first)->get_incoming_edges(element)) {
                    for (auto const &source : edge->get_sources())
                        std::clog << source << " ";
                    std::clog << ";  ";
                }
                std::clog << std::endl;
            }
        } else { // parser.recognized() is false
            std::clog << "There was no succesfull parse!" << std::endl;
        }
        return 0;
    }
}

int main(){
        return LCFR::main();
}