#include <fstream>
#include <iostream>
//#include <boost/foreach.hpp>

#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Nef_polyhedron_3.h>
//#include <CGAL/iterator.h>
//#include <CGAL/squared_distance_3.h>

typedef CGAL::Exact_predicates_exact_constructions_kernel       Kernel;
typedef CGAL::Polyhedron_3<Kernel>                              Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel>                          Nef_polyhedron;
typedef Polyhedron::HalfedgeDS                                  HalfedgeDS;
//typedef Polyhedron::Vertex_iterator                             Vertex_iterator;
//typedef Polyhedron::Halfedge_iterator                           Halfedge_iterator;
typedef Polyhedron::Facet_iterator                              Facet_iterator;
typedef Kernel::Point_3                                         Point_3;

//typedef Polyhedron::Halfedge_handle                             Halfedge_handle;
//typedef Polyhedron::Facet_handle                                Facet_handle;
//typedef Polyhedron::Vertex_handle                               Vertex_handle;
typedef Polyhedron::Halfedge_around_facet_circulator            Halfedge_facet_circulator;
//typedef CGAL::Inverse_index<Vertex_iterator>                    Index;

#include "gewebe_CGALBooleanOperations3.h"

using namespace std;

const bool M_DEBUG              = false;
const int NOT_FOUND             = -1;
const int INTERSECTION          = 0;
const int JOIN                  = 1;
const int DIFFERENCE            = 2;
const int SYMMETRIC_DIFFERENCE   = 3;

/**
 * @TODO
 *
 * - read CGAL doc : http://doc.cgal.org/latest/Polyhedron/classCGAL_1_1Polyhedron__3.html + http://doc.cgal.org/latest/Polyhedron/index.html
 * - check polyhedron + builder : http://doc.cgal.org/latest/Polyhedron/classCGAL_1_1Polyhedron__incremental__builder__3.html
 * - read on traits : http://doc.cgal.org/latest/Manual/devman_traits_classes.html
 * - look at example code in CGAL
 *
 */

// from http://cgal-discuss.949826.n4.nabble.com/attachment/4661367/0/union_and_intersection.cpp
// from http://jamesgregson.blogspot.de/2012/05/example-code-for-building.html
// from https://github.com/yusuketomoto/ofxCGAL/blob/master/src/ofxCGALBooleanOp.cpp

template <class HDS>
class PolyhedronBuilder : public CGAL::Modifier_base<HDS> {
public:

    vector<Point_3> & vertices;
    vector<int> & indices;
    const float kPointScale = 1.0f;

    PolyhedronBuilder(vector<Point_3> & _vertices, vector<int> & _indices) : vertices(_vertices), indices(_indices) {}

    void operator()(HDS & hds) {

        CGAL::Polyhedron_incremental_builder_3<HDS> mBuilder(hds, true);

        int numOfVertices = vertices.size();
        int numOfIndices = indices.size();

        mBuilder.begin_surface(numOfVertices, numOfIndices / 3);

        typedef typename HDS::Vertex Vertex;
        typedef typename Vertex::Point Point;

        for(int i=0; i<vertices.size(); i++) {
            mBuilder.add_vertex(Point(vertices[i].x(),
                                      vertices[i].y(),
                                      vertices[i].z()));
        }

        for(int i=0; i<numOfIndices; i+=3) {
            mBuilder.begin_facet();
            mBuilder.add_vertex_to_facet(indices[i + 0]);
            mBuilder.add_vertex_to_facet(indices[i + 1]);
            mBuilder.add_vertex_to_facet(indices[i + 2]);
            mBuilder.end_facet();
        }

        mBuilder.end_surface();
    }
};

bool near(Point_3 v0, Point_3 v1, double pMinDistance) {
    if (pMinDistance == 0) {
        return v0.x() == v1.x() && v0.y() == v1.y() && v0.z() == v1.z();
    } else {
        double x = CGAL::to_double(v0.x() - v1.x());
        double y = CGAL::to_double(v0.y() - v1.y());
        double z = CGAL::to_double(v0.z() - v1.z());
        double mDistance = sqrt(x*x+y*y+z*z);
        return mDistance < pMinDistance;
    }
}

int findVertexInIndexList(vector<Point_3> vertices_opt, Point_3 v, float pEpsilon) {
    for (int j = 0; j < vertices_opt.size(); j++) {
        Point_3 vo = vertices_opt[j];
        bool isNear = near(v, vo, pEpsilon);
        if (isNear) {
            return j;
        }
    }
    return NOT_FOUND;
}

void optimizeIndexList(vector<Point_3> & coords_raw, vector<Point_3> & coords_opt, vector<int> & indices_opt) {
    int mIndexCounter = 0;
    for (int i=0; i<coords_raw.size(); i++) {
        Point_3 v = coords_raw[i];
        int mExistingIndex = findVertexInIndexList(coords_opt, v, 0.0f);
        if (mExistingIndex == NOT_FOUND) {
            coords_opt.push_back(v);
            indices_opt.push_back(mIndexCounter);
            mIndexCounter++;
        } else {
            indices_opt.push_back(mExistingIndex);
        }
    }
}

void convertToPolyhedron(vector<Point_3> vertices, vector<int> indices, Polyhedron & polyhedron) {
    PolyhedronBuilder<HalfedgeDS> builder(vertices, indices);
    polyhedron.delegate(builder);
}

void extract_vertices(vector<Point_3> & vertices, JNIEnv * env, jfloatArray & j_coords) {
    jboolean iscopy;
    jfloat* jcoords_A_array = env->GetFloatArrayElements(j_coords, &iscopy);
    const int mNumberOfPoints_A = (env->GetArrayLength(j_coords)) / 3;

    if (M_DEBUG) cout << "--- A has points: " << mNumberOfPoints_A << endl;

    for(int i=0; i < mNumberOfPoints_A; i++) {
        double x = (double)*jcoords_A_array;
        jcoords_A_array++;
        double y = (double)*jcoords_A_array;
        jcoords_A_array++;
        double z = (double)*jcoords_A_array;
        jcoords_A_array++;
        vertices.push_back(Point_3(x, y, z));
    }
}

JNIEXPORT jfloatArray JNICALL Java_gewebe_CGALBooleanOperations3_boolean_1operation
(JNIEnv * env, jobject jobj, jint class_type, jfloatArray coords_A_array, jfloatArray coords_B_array )
{
    if (M_DEBUG) cout << "--- boolean_operation" << endl;

    /* evaluate command */

    int mBooleanOperation;
    if ( class_type == JOIN ) {
        if (M_DEBUG) cout << "--- type: JOIN" << endl;
        mBooleanOperation = JOIN;
    } else if ( class_type == INTERSECTION ) {
        if (M_DEBUG) cout << "--- type: INTERSECTION" << endl;
        mBooleanOperation = INTERSECTION;
    } else if ( class_type == DIFFERENCE ) {
        if (M_DEBUG) cout << "--- type: DIFFERENCE" << endl;
        mBooleanOperation = DIFFERENCE;
    } else if ( class_type == SYMMETRIC_DIFFERENCE ) {
        if (M_DEBUG) cout << "--- type: SYMMETRIC_DIFFERENCE" << endl;
        mBooleanOperation = SYMMETRIC_DIFFERENCE;
    } else {
        if (M_DEBUG) cout << "--- type: (DEFAULT) ?: " << class_type << endl;
        mBooleanOperation = INTERSECTION;
    }

    /* ------ */

    vector<Point_3> vertices_raw_A;
    extract_vertices(vertices_raw_A, env, coords_A_array);
    if (M_DEBUG) cout << "coords_raw_A: " << vertices_raw_A.size() << endl;

    vector<Point_3> vertices_raw_B;
    extract_vertices(vertices_raw_B, env, coords_B_array);

    /* build polyhedrons */

    Polyhedron polyhedronA;
    vector<Point_3> vertices_opt_A;
    vector<int> indices_opt_A;
    optimizeIndexList(vertices_raw_A, vertices_opt_A, indices_opt_A);
    convertToPolyhedron(vertices_opt_A, indices_opt_A, polyhedronA);

    Polyhedron polyhedronB;
    vector<Point_3> vertices_opt_B;
    vector<int> indices_opt_B;
    optimizeIndexList(vertices_raw_B, vertices_opt_B, indices_opt_B);
    convertToPolyhedron(vertices_opt_B, indices_opt_B, polyhedronB);

    /* check validity of polyhedra */

    if (!polyhedronA.is_pure_triangle() || !polyhedronB.is_pure_triangle()){
        cerr << "Inputs polyhedra must be triangulated." << std::endl;
        cerr << "A.is_pure_triangle() " << polyhedronA.is_pure_triangle() << endl;
        cerr << "B.is_pure_triangle() " << polyhedronB.is_pure_triangle() << endl;
    }

    if (!polyhedronA.size_of_vertices() || !polyhedronB.size_of_vertices()){
        cerr << "Inputs polyhedra must not be empty." << std::endl;
    }

    if (!polyhedronA.is_valid() || !polyhedronB.is_valid()){
        cerr << "Inputs polyhedra must be valid." << std::endl;
    }

    if(!polyhedronA.is_closed()) {
        cerr << "input mesh A is not closed." << endl;
    }

    if(!polyhedronB.is_closed()) {
        cerr << "input mesh B is not closed." << endl;
    }

    Nef_polyhedron Nef_A(polyhedronA);
    Nef_polyhedron Nef_B(polyhedronB);

    if (M_DEBUG) cout << "Nef_A (op) Nef_B" << endl;

    /*
     operator *= :: intersection
     operator += :: join
     operator -= :: difference
     operator ^= :: symmetric_difference
     */

    if (mBooleanOperation == INTERSECTION) {
        Nef_A *= Nef_B;
    } else if (mBooleanOperation == JOIN) {
        Nef_A += Nef_B;
    } else if (mBooleanOperation == DIFFERENCE) {
        Nef_A -= Nef_B;
    } else if (mBooleanOperation == SYMMETRIC_DIFFERENCE) {
        Nef_A ^= Nef_B;
    }

    if (M_DEBUG) cout << "Nef_A.is_simple(): " << Nef_A.is_simple() << endl;

    Polyhedron polyhedron_result;
    Nef_A.convert_to_polyhedron(polyhedron_result);
    if(Nef_A.is_simple()) {
        Nef_A.convert_to_Polyhedron(polyhedron_result);
    } else {
        cerr << "analyze/process n1 and do something..." << endl;
    }

    /* build jfloatarray */

    const int mNumberOfMeshData = distance(polyhedron_result.facets_begin(), polyhedron_result.facets_end()) * 3 * 3;
    jfloat mMeshData[mNumberOfMeshData];
    if (M_DEBUG) cout << "mNumberOfMeshData: " << mNumberOfMeshData << endl;

    int i = 0;
    for(Facet_iterator f = polyhedron_result.facets_begin();
        f != polyhedron_result.facets_end();
        ++f )
    {
        Halfedge_facet_circulator j = f->facet_begin();
        //        // Facets in polyhedral surfaces are at least triangles.
        //        CGAL_assertion( CGAL::circulator_size(j) >= 3);
        //        cout << CGAL::circulator_size(j) << ' ';
        do {
            if (M_DEBUG) cout << j->vertex()->point() << endl;
            mMeshData[i] = (float)CGAL::to_double(j->vertex()->point().x());
            i++;
            mMeshData[i] = (float)CGAL::to_double(j->vertex()->point().y());
            i++;
            mMeshData[i] = (float)CGAL::to_double(j->vertex()->point().z());
            i++;
        } while ( ++j != f->facet_begin());
    }

    jfloatArray mResultMeshData;
    mResultMeshData = env->NewFloatArray(mNumberOfMeshData);
    env->SetFloatArrayRegion(mResultMeshData, 0, mNumberOfMeshData, mMeshData);

    return mResultMeshData;
}

//    int size = std::distance(C.vertices_begin(), C.vertices_end());
//    cout << "number of points : " << size << endl;
//
//
//    //    if (/* DISABLES CODE */ (false)) {
//    cout << "coref ... ";
//
//    coref(new_A, new_B,
//          polyline_output,
//          std::back_inserter(result),
//          operation_type);
//
//    cout << "done." << endl;
//
//    //    }
//
//    //    int union_index = result[0].second == Corefinement::Join_tag ? 0:1;
//    //    int intersection_index = (union_index+1)%2;
//    //
//    //    cout << union_index << endl;
//    //    cout << intersection_index << endl;
//
//    //    cout << *( result[union_index].first );
//    //    cout << endl;
//    //    cout << *( result[intersection_index].first );
//
//    //    cout << *( result[0].first );
//
//    //    /* return results */
//
//    //    CGAL::Vertex_iterator begin = result[union_index].first->vertices_begin();
//    //    CGAL::Vertex_iterator end = result[union_index].first->vertices_end();
//
//    //    std::list<Vertex>::iterator itb = result[union_index]->vertices_begin();
//    //    for(; result[union_index].first->vertices_begin() != result[union_index].first->vertices_end(); itb++) {
//    //
//    //    }

//    /* populate vectors */
//    {
//        jfloat* jcoords_A_array = env->GetFloatArrayElements(coords_A_array, &iscopy);
//        const int mNumberOfPoints_A = (env->GetArrayLength(coords_A_array)) / 3;
//
//        if (M_DEBUG) cout << "--- A has points: " << mNumberOfPoints_A << endl;
//
//        for(int i=0; i < mNumberOfPoints_A; i++) {
//            double x = (double)*jcoords_A_array;
//            jcoords_A_array++;
//            double y = (double)*jcoords_A_array;
//            jcoords_A_array++;
//            double z = (double)*jcoords_A_array;
//            jcoords_A_array++;
//            coords_A.push_back(x);
//            coords_A.push_back(y);
//            coords_A.push_back(z);
//            //            cout << x << ", " << y << ", " << z << endl;
//        }
//    }
//    {
//        jfloat* jcoords_B_array = env->GetFloatArrayElements(coords_B_array, &iscopy);
//        const int mNumberOfPoints_B = (env->GetArrayLength(coords_B_array)) / 3;
//
//        if (M_DEBUG) cout << "--- B has points: " << mNumberOfPoints_B << endl;
//
//        for(int i=0; i < mNumberOfPoints_B; i++) {
//            double x = (double)*jcoords_B_array;
//            jcoords_B_array++;
//            double y = (double)*jcoords_B_array;
//            jcoords_B_array++;
//            double z = (double)*jcoords_B_array;
//            jcoords_B_array++;
//            coords_B.push_back(x);
//            coords_B.push_back(y);
//            coords_B.push_back(z);
//            //            cout << x << ", " << y << ", " << z << endl;
//        }
//    }

//    Vertex_iterator v;
//    for( v = polyhedron_result.vertices_begin();
//        v != polyhedron_result.vertices_end();
//        ++v )     {
//        cout << v->point() << endl;
//    }

//    vector<Alpha_shape_3::Facet> a_facets;
//    as->get_alpha_shape_facets(std::back_inserter(a_facets), facets_type);
//    const jsize nb_facets_indices = 3 * 3 * a_facets.size();
//
//    if (M_DEBUG) cout << "--- number of points: " << nb_facets_indices << endl;
//
//    Index index(as->vertices_begin(), as->vertices_end());
//    jfloat m_mesh_points [nb_facets_indices];
//
//    size_t nbf=a_facets.size();
//    for (size_t i=0;i<nbf;++i) {
//        if ( as->classify( a_facets[i].first )!=Alpha_shape_3::EXTERIOR ) {
//            a_facets[i]=as->mirror_facet( a_facets[i] );
//        }
//
//        int indices[3]={
//            (a_facets[i].second+1)%4,
//            (a_facets[i].second+2)%4,
//            (a_facets[i].second+3)%4,
//        };
//
//        /// according to the encoding of vertex indices, this is needed to get
//        /// a consistent orienation
//        if ( a_facets[i].second%2==0 ) {
//            swap(indices[0], indices[1]);
//        }
//
//        m_mesh_points[i * 9 + 0] = a_facets[i].first->vertex(indices[0])->point()[0];
//        m_mesh_points[i * 9 + 1] = a_facets[i].first->vertex(indices[0])->point()[1];
//        m_mesh_points[i * 9 + 2] = a_facets[i].first->vertex(indices[0])->point()[2];
//        m_mesh_points[i * 9 + 3] = a_facets[i].first->vertex(indices[1])->point()[0];
//        m_mesh_points[i * 9 + 4] = a_facets[i].first->vertex(indices[1])->point()[1];
//        m_mesh_points[i * 9 + 5] = a_facets[i].first->vertex(indices[1])->point()[2];
//        m_mesh_points[i * 9 + 6] = a_facets[i].first->vertex(indices[2])->point()[0];
//        m_mesh_points[i * 9 + 7] = a_facets[i].first->vertex(indices[2])->point()[1];
//        m_mesh_points[i * 9 + 8] = a_facets[i].first->vertex(indices[2])->point()[2];
//    }
//
//    jfloatArray result;
//    result = env->NewFloatArray( nb_facets_indices);
//    env->SetFloatArrayRegion(result, 0, nb_facets_indices, m_mesh_points);
//
//    return result;

//    /* build jfloatarray */
//
//    const int mNumberOfMeshData = distance(polyhedron_result.vertices_begin(), polyhedron_result.vertices_end()) * 3;
//    jfloat mMeshData[mNumberOfMeshData];
//
//    Vertex_iterator v;
//    int i = 0;
//    for( v = polyhedron_result.vertices_begin();
//        v != polyhedron_result.vertices_end();
//        ++v )
//    {
//        mMeshData[i] = (float)CGAL::to_double(v->point().x());
//        i++;
//        mMeshData[i] = (float)CGAL::to_double(v->point().y());
//        i++;
//        mMeshData[i] = (float)CGAL::to_double(v->point().z());
//        i++;
//    }
//
//    if (M_DEBUG) cout << "--- build array " << endl;
//
//    jfloatArray mResultMeshData;
//    mResultMeshData = env->NewFloatArray(mNumberOfMeshData);
//    env->SetFloatArrayRegion(mResultMeshData, 0, mNumberOfMeshData, mMeshData);
//
//    if (M_DEBUG) cout << "--- return array " << endl;
//
//    return mResultMeshData;

JNIEXPORT jint JNICALL Java_gewebe_CGALBooleanOperations3_version
(JNIEnv * env, jobject)
{
    int mVersion = 20210130;
    int mTime = 141015;
    return mVersion;
}


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#include <iostream>
#include <cmath>
#include <complex>
#include <vector>

#include <boost/numeric/mtl/mtl.hpp>

inline float f(float x) { return x; }
inline double f(double x) { return x; }

inline std::complex<double> f(std::complex<double> x)
{
    return std::complex<double>(real(x), real(x)+1.0);
}

int main(int, char**)
{

    typedef mtl::vec::fixed::dimension<5> fsize;
    mtl::dense_vector<float, mtl::vec::parameters<mtl::row_major, fsize, true> >     rf;
    mtl::dense_vector<float, mtl::vec::parameters<mtl::col_major, fsize, true> >     cf;
    rf= 1.0; cf= 1.0;

    std::cout << "rf is " << rf << '\n';
    std::cout << "cf is " << cf << '\n';

    return 0;
}


/*
 * Copyright 2013 Matthew Harvey
 *
 * 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.
 */

#include "date.hpp"
#include "date_parser.hpp"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/optional.hpp>
#include <jewel/assert.hpp>
#include <jewel/exception.hpp>
#include <wx/datetime.h>
#include <wx/string.h>
#include <limits>

using boost::optional;
using std::numeric_limits;

namespace gregorian = boost::gregorian;
namespace posix_time = boost::posix_time;

namespace dcm
{

namespace
{
    // Integral Julian representation of 1 Jan 1400 CE (12:01am)
    static int const min_valid_date_rep = 1721426;

    // boost::gregorian representation of 1 Jan 1400 CE (12:01am)
    gregorian::date const min_valid_gregorian(1400, 1, 1);

    // Integral Julian representation of 1 Jan 1970 CE (12:01am) (POSIX epoch)
    static int const epoch_date_rep = 2440588;

    // boost::gregorian representation of POSIX epoch
    static gregorian::date const epoch_grg(1970, 1, 1);

    wxDateTime boost_to_wx_date(gregorian::date const& p_date)
    {
        JEWEL_ASSERT (static_cast<int>(wxDateTime::Jan) == 0);
        JEWEL_ASSERT (static_cast<int>(wxDateTime::Dec) == 11);
        return wxDateTime
        (   p_date.day(),
            static_cast<wxDateTime::Month>(p_date.month() - 1),
            p_date.year(),
            0,
            0,
            0,
            0
        );
    }

}  // end anonymous namespace

bool
is_valid_date(DateRep date)
{
    return (date >= min_valid_date_rep);
}

DateRep
null_date_rep()
{
    return DateRep(0);
}

DateRep
earliest_date_rep()
{
    return numeric_limits<DateRep>::min();
}

DateRep
latest_date_rep()
{
    return numeric_limits<DateRep>::max();
}

DateRep
julian_int(gregorian::date p_date)
{
    if (p_date.is_not_a_date())
    {
        return null_date_rep();
    }

    // gregorian::date is never earlier than min_valid_gregorian.
    JEWEL_ASSERT (p_date >= min_valid_gregorian);

    gregorian::date_duration const interval = p_date - epoch_grg;
    DateRep const ret = epoch_date_rep + interval.days();
    JEWEL_ASSERT (is_valid_date(ret));
    return ret;
}

gregorian::date
boost_date_from_julian_int(DateRep julian_int)
{
    if (julian_int == null_date_rep())
    {
        // returns an invalid date
        return gregorian::date(boost::date_time::not_a_date_time);
    }
    if (!is_valid_date(julian_int))
    {
        JEWEL_THROW
        (   DateConversionException,
            "boost_date_from_julian_int is not designed to handle dates "
            "earlier than year 1 CE."
        );
    }
    gregorian::date_duration const
        interval(julian_int - epoch_date_rep);
    gregorian::date const ret = epoch_grg + interval;
    JEWEL_ASSERT (ret >= min_valid_gregorian);
    JEWEL_ASSERT (!ret.is_not_a_date());
    return ret;
}

gregorian::date
today()
{
    return gregorian::day_clock::local_day();
}

posix_time::ptime
now()
{
    return posix_time::second_clock::local_time();
}

wxString
date_format_wx(gregorian::date const& p_date)
{
    wxDateTime const wxdt = boost_to_wx_date(p_date);
    return wxdt.FormatDate();
}

gregorian::date
month_end_for_date(gregorian::date const& p_date)
{
    int temp_day = 1;
    int temp_month = p_date.month() + 1;
    int temp_year = p_date.year();
    if (temp_month == 13)
    {
        temp_month = 1;
        ++temp_year;
        JEWEL_ASSERT (temp_month == 1);
        JEWEL_ASSERT (temp_year == p_date.year() + 1);
    }
    gregorian::date ret(temp_year, temp_month, temp_day);
    ret -= gregorian::date_duration(1);
    JEWEL_ASSERT
    (   ( (ret.day() == 28) && (ret.month() == 2) ) ||
        ( (ret.day() == 29) && (ret.month() == 2) ) ||
          (ret.day() == 30) ||
          (ret.day() == 31)
    );
    JEWEL_ASSERT (ret.year() == p_date.year());
    return ret;
}

}  // namespace dcm


////////////////////////////////////////////////////////////////////////////////
// Copyright 2021 FZI Research Center for Information Technology
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. 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.
//
// 3. Neither the name of the copyright holder 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 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.
////////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
/*!\file    cartesian_trajectory_segment.cpp
 *
 * \author  Stefan Scherzinger <scherzin@fzi.de>
 * \date    2021/01/20
 *
 */
//-----------------------------------------------------------------------------

#include <cartesian_trajectory_interpolation/cartesian_trajectory_segment.h>
#include <algorithm>
#include <cmath>
#include "Eigen/src/Core/GlobalFunctions.h"
#include "Eigen/src/Core/Matrix.h"
#include "Eigen/src/Core/util/Constants.h"
#include "Eigen/src/Geometry/Quaternion.h"
#include <Eigen/Dense>

namespace cartesian_ros_control
{
  using Time = CartesianTrajectorySegment::Time;
  using SplineState = CartesianTrajectorySegment::SplineState;

  CartesianTrajectorySegment::CartesianTrajectorySegment(const Time&  start_time,
      const CartesianState& start_state,
      const Time&  end_time,
      const CartesianState& end_state)
    : QuinticSplineSegment(start_time, convert(start_state), end_time, convert(end_state))
  {
  };

  void CartesianTrajectorySegment::sample(const Time& time, CartesianState& state) const
  {
    // Sample from the underlying spline segment.
    SplineState s(7);
    QuinticSplineSegment::sample(time, s);

    if (time < this->startTime() || time > this->endTime())
    {
      state.p = Eigen::Vector3d(s.position[0], s.position[1], s.position[2]);
      state.q = Eigen::Quaterniond(s.position[3], s.position[4], s.position[5], s.position[6]).normalized();

      state.v = Eigen::Vector3d::Zero();
      state.w = Eigen::Vector3d::Zero();
      state.v_dot = Eigen::Vector3d::Zero();
      state.w_dot = Eigen::Vector3d::Zero();
    }
    else
    {
      state = convert(s);
    }
  }

  SplineState convert(const CartesianState& state)
  {
    SplineState spline_state;

    // Note: The pre-multiplication of velocity and acceleration terms with
    // `state.q.inverse()` transforms them into the body-local reference frame.
    // This is required for computing quaternion-based velocities and
    // accelerations below.

    // Convenience method
    auto fill = [](auto& vec, const auto& first, const auto& second)
    {
      vec.push_back(first.x());
      vec.push_back(first.y());
      vec.push_back(first.z());

      vec.push_back(second.w());
      vec.push_back(second.x());
      vec.push_back(second.y());
      vec.push_back(second.z());
    };

    // Spline positions
    fill(spline_state.position, state.p, state.q);

    // Spline velocities
    if(std::isnan(state.v.x()) || std::isnan(state.v.y()) || std::isnan(state.v.z())
          || std::isnan(state.w.x()) || std::isnan(state.w.y()) || std::isnan(state.w.z()))
    {
      return spline_state;  // with uninitialized velocity/acceleration data
    }
    Eigen::Quaterniond q_dot;
    Eigen::Vector3d tmp = state.q.inverse() * state.w;
    Eigen::Quaterniond omega(0, tmp.x(), tmp.y(), tmp.z());
    q_dot.coeffs() = 0.5 * (omega * state.q).coeffs();

    fill(spline_state.velocity, state.q.inverse() * state.v, q_dot);

    // Spline accelerations
    if(std::isnan(state.v_dot.x()) || std::isnan(state.v_dot.y()) || std::isnan(state.v_dot.z())
          || std::isnan(state.w_dot.x()) || std::isnan(state.w_dot.y()) || std::isnan(state.w_dot.z()))
    {
      return spline_state;  // with uninitialized acceleration data
    }
    Eigen::Quaterniond q_ddot;
    tmp = state.q.inverse() * state.w_dot;
    Eigen::Quaterniond omega_dot(0, tmp.x(), tmp.y(), tmp.z());
    q_ddot.coeffs() = 0.5 * (omega_dot * state.q).coeffs() + 0.5 * (omega * q_dot).coeffs();

    fill(spline_state.acceleration, state.q.inverse() * state.v_dot, q_ddot);

    return spline_state;
  };

  CartesianState convert(const SplineState& s)
  {
    CartesianState state;

    // Cartesian positions
    if (s.position.empty())
    {
      return state;  // with positions/velocities/accelerations zero initialized
    }
    state.p = Eigen::Vector3d(s.position[0], s.position[1], s.position[2]);
    state.q = Eigen::Quaterniond(s.position[3], s.position[4], s.position[5], s.position[6]).normalized();

    // Cartesian velocities
    if (s.velocity.empty())
    {
      return state;  // with velocities/accelerations zero initialized
    }
    Eigen::Quaterniond q_dot(s.velocity[3], s.velocity[4], s.velocity[5], s.velocity[6]);

    Eigen::Quaterniond omega;
    omega.coeffs() = 2.0 * (q_dot * state.q.inverse()).coeffs();

    state.v = Eigen::Vector3d(s.velocity[0], s.velocity[1], s.velocity[2]);
    state.w = Eigen::Vector3d(omega.x(), omega.y(), omega.z());

    // Cartesian accelerations
    if (s.acceleration.empty())
    {
      return state;  // with accelerations zero initialized
    }
    Eigen::Quaterniond q_ddot(s.acceleration[3], s.acceleration[4], s.acceleration[5], s.acceleration[6]);

    Eigen::Quaterniond omega_dot;
    omega_dot.coeffs() = 2.0 * (
        (q_ddot * state.q.inverse()).coeffs() -
        ((q_dot * state.q.inverse()) * (q_dot * state.q.inverse())).coeffs()
        );

    state.v_dot = Eigen::Vector3d(s.acceleration[0], s.acceleration[1], s.acceleration[2]);
    state.w_dot = Eigen::Vector3d(omega_dot.x(), omega_dot.y(), omega_dot.z());

    // Re-transform vel and acc to the correct reference frame.
    state.v = state.q * state.v;
    state.w = state.q * state.w;
    state.v_dot = state.q * state.v_dot;
    state.w_dot = state.q * state.w_dot;

    return state;
  }

  std::ostream& operator<<(std::ostream &out, const CartesianTrajectorySegment::SplineState& state)
  {
    out << "pos:\n";
    for (size_t i = 0; i < state.position.size(); ++i)
    {
      out << state.position[i] << '\n';
    }
    out << "vel:\n";
    for (size_t i = 0; i < state.velocity.size(); ++i)
    {
      out << state.velocity[i] << '\n';
    }
    out << "acc:\n";
    for (size_t i = 0; i < state.acceleration.size(); ++i)
    {
      out << state.acceleration[i] << '\n';
    }

    return out;
  }
}


// GearsDoc.cpp : implementation of the CGearsDoc class
//

#include "stdafx.h"
// SHARED_HANDLERS can be defined in an ATL project implementing preview, thumbnail
// and search filter handlers and allows sharing of document code with that project.

#include "Geometry.h"
#include "objects.h"
#include "shared.h"
#include "cnc.h"
#include "Drawing.h"
#include "dxf.h"
#include "sgv.h"
#include "gear_gen.h"
// ............................................................
ItemSet gear_gen_one( const gear_params_t& gp );
ItemSet gear_generate( const ItemSet& tooth, const gear_params_t& gp );

#ifndef SHARED_HANDLERS
#include "Gears.h"
#endif

#include "CADDoc.h"
#include "GearsDoc.h"

//#include <propkey.h>

#include <boost/math/constants/constants.hpp>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// ..........................................................................
IMPLEMENT_DYNCREATE( CGearsDoc, CADDoc )
BEGIN_MESSAGE_MAP( CGearsDoc, CADDoc )
	ON_COMMAND( ID_FILE_EXPORT, &CGearsDoc::OnFileExport )
	ON_COMMAND( ID_FILE_G_CODE, &CGearsDoc::OnFileGCode )
//	ON_NOTIFY( ID_GEAR_PARAM_CHANGED, 1, &CGearsDoc::OnCmdMsg )
END_MESSAGE_MAP( )

// ............................................................................
//void CGearsDoc::OnGearParamChanged( NMHDR*, LRESULT* )
//{
//}

// ..........................................................................
BOOL CGearsDoc::OnCmdMsg( UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo )
{
	if ( HIWORD(nCode) == WM_NOTIFY )
	{
		LPNMHDR pHdr= ((AFX_NOTIFY*)pExtra)->pNMHDR;
		switch( LOWORD( nCode ) )
		{
		case ID_GEAR_PARAM_CHANGED:
			auto pg= ( pgear_params_t )((PNMHDROBJECT)pHdr)->pObject;
			test( pg );
			test( );
			UpdateAllViews( nullptr, ID_UPDATE_VIEW_TOFIT );
			NMHDR* pnm= ((AFX_NOTIFY*)pExtra)->pNMHDR;
			//TRACE( "doc: %x - %x - %x low: %x\n", nID, nCode, pnm->code, LOWORD( nCode ) );
		}
	}
	return CDocument::OnCmdMsg( nID, nCode, pExtra, pHandlerInfo );
}

DrawingObects& CGearsDoc::GetExportDrawObject( )
{
	export_drawobj.get_set( ).clear( );
	export_drawobj.push_back( drawobj.get_set( ).front( ) );
	return export_drawobj;
}

// ..........................................................................
CGearsDoc::CGearsDoc( )
	:bAnimate( false )
	,export_drawobj( layers )
{
}

// ..........................................................................
CGearsDoc::~CGearsDoc( )
{
}

// ..........................................................................
void CGearsDoc::OnFileGCode( )
{
	//testing g-code............
	sp_gcitem_t test( boost::make_shared< gc0 >( ) );
	gc.push_back( test );
	gc.push_back( sp_gcitem_t( new gc0 ) );
}

template< typename Type >
inline SP_BaseItem  make_sp( Type& obj ) { return boost::make_shared< Type >( obj ); }
// ..........................................................................
void CGearsDoc::test( )
{
	bg_box box= get_rect_hull( drawobj.get_set( ) );
	RectItem ri( box );
	GEO_NUM bx= ri.width( ) / 4;
	ri.get_min_point( ).x-= bx;
	ri.get_max_point( ).x+= bx;
	GEO_NUM by= ri.height( ) / 4;
	ri.get_min_point( ).y-= by;
	ri.get_max_point( ).y+= by;
	draw_extents= ri.get_bg_box( );
//	drawobj.get_set( ).back( ).push_back( RectItem( ri ).get_SP( ) );
}

// ..........................................................................
void CGearsDoc::test( pgear_params_t pg )
{
	drawobj.get_set( ).clear( );
	////create a tooth
	auto tooth= gear_gen_one( *pg );

	//generate gear from tooth
	auto gear= gear_generate( tooth, *pg );
	if( pg->cb )
		gear.push_back( ArcItem( bg_point( 0, pg->cb ), bg_point( 0, pg->cb ), bg_point( 0, 0 ) ).get_SP( ) );
	gear.set_id( 1 );
	drawobj.push_back( gear );

	//copy gear with offset
	ItemSet gear2;//= boost::make_shared< ItemSet >( ItemSet( ) );
	gear2.set_offset( bg_point( pg->pd, 0 ) );
	double rotate= pg->tc % 2 ? pg->of + PI / pg->tc : pg->of;
	for( const auto& item : gear )
	{
		item->SetColor( RGB( 0, 222, 222 ) );
		auto hold= transform_object( item, bg_point( pg->pd, 0 ) );
		hold= rotate_object( rotate, hold, bg_point( pg->pd, 0 ) );
		hold->SetColor( RGB( 255, 0, 0 ) );
		gear2.push_back( hold );
	}
//	visuals.insert( gear2.begin( ), gear2.end( ) );
	gear2.set_id( 2 );
	drawobj.push_back( gear2 );

	// working ..........................................
	layers.insert( layer_t( 0, L"Zero Layer" ) );
	layers.insert( layer_t( 1, L"Milling" ) );

	test( );
}

// ..........................................................................
BOOL CGearsDoc::OnNewDocument( )
{
	if( ! CDocument::OnNewDocument( ) )
		return FALSE;

#if 1
	gear_params_t params(
		12,				//tooth count
		TO_RAD( 20 ),	//presure angle in radians
		8,				//diametrical pitch
		.25				//center bore......
	);
	test( &params );
	test( );
	UpdateAllViews( nullptr, ID_UPDATE_VIEW_TOFIT );

#endif
	return TRUE;
}

// ..........................................................................
BOOL CGearsDoc::OnSaveDocument( LPCTSTR lpszPathName )
{
	CADDoc::OnSaveDocument( lpszPathName );
	return TRUE;
}

// ..........................................................................
bool StartDXF_write( DL_Dxf* dxf, DL_WriterA* dw );
bool EndDXF_write( DL_Dxf* dxf, DL_WriterA* dw );

// ..........................................................................
LRESULT CGearsDoc::OnLayerEnable(WPARAM, LPARAM )
{
	return 0;
}

// ..........................................................................
void CGearsDoc::OnFileExport( )
{
	if( ! drawobj.size( ) )
		return;

	//DrawingObects a_gear( layers );
	auto& a_gear= GetExportDrawObject( );//.get_set( ).push_back( ItemSet( drawobj.get_set( ).front( ) ) );

	CFileDialog dlg( TRUE, NULL, NULL,
		OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
		_T("DXF Files (*.dxf)|*dxf|SVG Files (*.svg)|*svg||"), NULL, 0);
	dlg.m_ofn.nFilterIndex= get_user_profile( ).export_type;

	int ret= dlg.DoModal( );
	OPENFILENAME& ofn= dlg.GetOFN( );
	bfs::path path( ofn.lpstrFile );
	if( path.extension( ) == L".dxf" || path.extension( ) == L".svg" )
		path.replace_extension( L"" );
	get_user_profile( ).export_type= ofn.nFilterIndex;
	switch( ofn.nFilterIndex )
	{
	case 1: //dxf
		path+= L".dxf";
		DXF_WriteFile( path, a_gear );
		break;

	case 2: //svg
		path+= L".svg";
		SVG_WriteFile( path, a_gear );
		break;
	}
}

// ..........................................................................
void CGearsDoc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// TODO: add storing code here
	}
	else
	{
		// TODO: add loading code here
	}
}

// ..........................................................................
#ifdef _DEBUG
void CGearsDoc::AssertValid() const
{
	CDocument::AssertValid();
}

// ..........................................................................
void CGearsDoc::Dump(CDumpContext& dc) const
{
	CDocument::Dump(dc);
}
#endif //_DEBUG

#ifdef SHARED_HANDLERS

// ..........................................................................
// Support for thumbnails
void CGearsDoc::OnDrawThumbnail(CDC& dc, LPRECT lprcBounds)
{
	// Modify this code to draw the document's data
	dc.FillSolidRect(lprcBounds, RGB(255, 255, 255));

	CString strText = _T("TODO: implement thumbnail drawing here");
	LOGFONT lf;

	CFont* pDefaultGUIFont = CFont::FromHandle((HFONT) GetStockObject(DEFAULT_GUI_FONT));
	pDefaultGUIFont->GetLogFont(&lf);
	lf.lfHeight = 36;

	CFont fontDraw;
	fontDraw.CreateFontIndirect(&lf);

	CFont* pOldFont = dc.SelectObject(&fontDraw);
	dc.DrawText(strText, lprcBounds, DT_CENTER | DT_WORDBREAK);
	dc.SelectObject(pOldFont);
}

// ..........................................................................
// Support for Search Handlers
void CGearsDoc::InitializeSearchContent()
{
	CString strSearchContent;
	// Set search contents from document's data.
	// The content parts should be separated by ";"

	// For example:  strSearchContent = _T("point;rectangle;circle;ole object;");
	SetSearchContent(strSearchContent);
}

// ..........................................................................
void CGearsDoc::SetSearchContent(const CString& value)
{
	if (value.IsEmpty())
	{
		RemoveChunk(PKEY_Search_Contents.fmtid, PKEY_Search_Contents.pid);
	}
	else
	{
		CMFCFilterChunkValueImpl *pChunk = NULL;
		ATLTRY(pChunk = new CMFCFilterChunkValueImpl);
		if (pChunk != NULL)
		{
			pChunk->SetTextValue(PKEY_Search_Contents, value, CHUNK_TEXT);
			SetChunkValue(pChunk);
		}
	}
}

#endif // SHARED_HANDLERS


#include "repair.hpp"

#include <boost/format.hpp>

#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"

#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"

#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"

namespace MWMechanics
{

void Repair::repair(const MWWorld::Ptr &itemToRepair)
{
    MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
    MWWorld::LiveCellRef<ESM::Repair> *ref =
        mTool.get<ESM::Repair>();

    // unstack tool if required
    player.getClass().getContainerStore(player).unstack(mTool, player);

    // reduce number of uses left
    int uses = mTool.getClass().getItemHealth(mTool);
    mTool.getCellRef().setCharge(uses-1);

    MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
    MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);

    float fatigueTerm = stats.getFatigueTerm();
    int pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified();
    int pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
    int armorerSkill = npcStats.getSkill(ESM::Skill::Armorer).getModified();

    float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
            .find("fRepairAmountMult")->getFloat();

    float toolQuality = ref->mBase->mData.mQuality;

    float x = (0.1 * pcStrength + 0.1 * pcLuck + armorerSkill) * fatigueTerm;

    int roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
    if (roll <= x)
    {
        int y = fRepairAmountMult * toolQuality * roll;
        y = std::max(1, y);

        // repair by 'y' points
        int charge = itemToRepair.getClass().getItemHealth(itemToRepair);
        charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair));
        itemToRepair.getCellRef().setCharge(charge);

        // attempt to re-stack item, in case it was fully repaired
        MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair);

        // set the OnPCRepair variable on the item's script
        std::string script = stacked->getClass().getScript(itemToRepair);
        if(script != "")
            stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1);

        // increase skill
        player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0);

        MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1);
        MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}");
    }
    else
    {
        MWBase::Environment::get().getSoundManager()->playSound("Repair Fail",1,1);
        MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairFailed}");
    }

    // tool used up?
    if (mTool.getCellRef().getCharge() == 0)
    {
        MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
        MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);

        store.remove(mTool, 1, player);

        std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                .find("sNotifyMessage51")->getString();

        MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % mTool.getClass().getName(mTool)).str());

        // try to find a new tool of the same ID
        for (MWWorld::ContainerStoreIterator iter (store.begin());
             iter!=store.end(); ++iter)
        {
            if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), mTool.getCellRef().getRefId()))
            {
                mTool = *iter;
                break;
            }
        }
    }
}

}


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#include <boost/simd/function/scalar/hmsb.hpp>
#include <boost/simd/constant/allbits.hpp>
#include <boost/simd/constant/nan.hpp>
#include <scalar_test.hpp>

STF_CASE_TPL ("Check hmsb", STF_NUMERIC_TYPES)
{
  STF_EQUAL(boost::simd::hmsb(boost::simd::Allbits<T>()).size(), 1ULL);

  STF_EXPECT(boost::simd::hmsb(boost::simd::Signmask<T>()).all());
  STF_EXPECT(boost::simd::hmsb(boost::simd::Allbits<T>()).all());
  STF_EXPECT(boost::simd::hmsb(T(-1)).all());

  STF_EXPECT(boost::simd::hmsb(T(0)).none());
  STF_EXPECT(boost::simd::hmsb(T(1)).none());
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_MINUS_S_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_MINUS_S_HPP_INCLUDED

#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/function/min.hpp>
#include <boost/simd/function/saturate.hpp>
#include <boost/simd/function/saturated.hpp>
#include <boost/simd/detail/dispatch/meta/as_signed.hpp>
#include <boost/simd/detail/dispatch/meta/as_unsigned.hpp>
#include <boost/simd/detail/dispatch/meta/upgrade.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( minus_
                          , (typename A0)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_< bd::floating_<A0> >
                          , bd::scalar_< bd::floating_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const saturated_tag &,  A0 a0, A0 a1
                                    ) const BOOST_NOEXCEPT
    {
      return a0-a1;
    }
  };
  BOOST_DISPATCH_OVERLOAD ( minus_
                          , (typename A0)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_< bd::int_<A0> >
                          , bd::scalar_< bd::int_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const saturated_tag &,  A0 a0, A0 a1
                                    ) const BOOST_NOEXCEPT
    {
      using sz_t = typename nsm::bool_<sizeof(A0) == 4 || sizeof(A0) == 8>::type;
      return impl(a0, a1, sz_t());
    }

    static BOOST_FORCEINLINE A0 impl( A0 a0, A0 a1
                                    , const tt::false_type &) BOOST_NOEXCEPT
    {
      using u_t = bd::upgrade_t<A0>;
      using s_t = bd::as_signed_t<u_t>;
      return static_cast<A0>(saturate<A0>(s_t(a0)-s_t(a1)));
    }

    static BOOST_FORCEINLINE A0 impl( A0 a0, A0 a1
                                    , const tt::true_type &) BOOST_NOEXCEPT
    {
      using un_t = bd::as_unsigned_t<A0>;

      un_t ux = a0;
      un_t uy = a1;
      un_t res = ux - uy;

      ux = (ux >> (sizeof(A0)*CHAR_BIT-1)) + Valmax<A0>();

      if(A0((ux ^ uy) & (ux ^ res)) < Zero<A0>()) res = ux;

      return res;
    }
  };
  BOOST_DISPATCH_OVERLOAD ( minus_
                          , (typename A0)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_< bd::uint_<A0> >
                          , bd::scalar_< bd::uint_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const saturated_tag &,  A0 a0, A0 a1
                                    ) const BOOST_NOEXCEPT
    {
      A0 res = a0 - a1;
      res &= -(res <= a0);

      return res;
    }
  };
} } }
#endif


#include "dynet/nodes.h"
#include "dynet/dynet.h"
#include "dynet/training.h"
#include "dynet/timing.h"
#include "dynet/rnn.h"
#include "dynet/gru.h"
#include "dynet/lstm.h"
#include "dynet/fast-lstm.h"
#include "dynet/dict.h"
#include "dynet/expr.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <type_traits>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/program_options.hpp>

#include "s2s/define.hpp"
#include "s2s/options.hpp"
#include "s2s/corpora.hpp"

#ifndef INCLUDE_GUARD_Vinyals2014_HPP
#define INCLUDE_GUARD_Vinyals2014_HPP

namespace s2s {

class encoder_decoder {

public:

    std::vector<dynet::LookupParameter> p_feature_enc;
    dynet::LookupParameter p_word_dec;
    std::vector<dynet::Parameter> p_dec_init_bias;
    std::vector<dynet::Parameter> p_dec_init_w;
    dynet::Parameter p_Wa;
    dynet::Parameter p_Ua;
    dynet::Parameter p_va;
    dynet::Parameter p_Wch;
    dynet::Parameter p_out_R;
    dynet::Parameter p_out_bias;
    dynet::VanillaLSTMBuilder dec_builder;
    dynet::VanillaLSTMBuilder rev_enc_builder;
    dynet::VanillaLSTMBuilder fwd_enc_builder;

    bool rev_enc;
    bool bi_enc;
    bool dec_feed_hidden;
    bool additional_output_layer;
    bool additional_connect_layer;
    bool flag_drop_out;

    float dropout_rate_lstm_con;
    float dropout_rate_enc_in;
    float dropout_rate_enc_out;
    float dropout_rate_dec_in;
    float dropout_rate_dec_out;

    dynet::expr::Expression dropout_mask_enc_in;
    dynet::expr::Expression dropout_mask_dec_in;

    unsigned int slen;

    explicit encoder_decoder(dynet::Model& model, const s2s_options* opts) {

        rev_enc = opts->rev_enc;
        bi_enc = opts->bi_enc;
        dec_feed_hidden = opts->dec_feed_hidden;
        additional_output_layer = opts->additional_output_layer;
        additional_connect_layer = opts->additional_connect_layer;
        dropout_rate_lstm_con = opts->dropout_rate_lstm_con;
        dropout_rate_enc_in = opts->dropout_rate_enc_in;
        dropout_rate_dec_in = opts->dropout_rate_dec_in;

        unsigned int num_layers = opts->num_layers;
        unsigned int rnn_size = opts->rnn_size;
        unsigned int enc_input_size = 0;
        unsigned int dec_feeding_size = 0;

        flag_drop_out = true;

        assert(opts->enc_feature_vocab_size.size() == opts->enc_feature_vec_size.size());
        for(unsigned int i = 0; i < opts->enc_feature_vec_size.size(); i++){
            p_feature_enc.push_back(model.add_lookup_parameters(opts->enc_feature_vocab_size.at(i), {opts->enc_feature_vec_size.at(i)}));
            enc_input_size += opts->enc_feature_vec_size.at(i);
        }

        unsigned int cell_ratio = 2; // In GRU, cell_ration = 1;

        p_dec_init_w.resize(num_layers * cell_ratio);
        for(unsigned int i = 0; i < num_layers * cell_ratio; i++){
            p_dec_init_w[i] = model.add_parameters({opts->rnn_size, opts->rnn_size});
        }

        p_dec_init_bias.resize(num_layers * cell_ratio);
        for(unsigned int i = 0; i < num_layers * cell_ratio; i++){
            p_dec_init_bias[i] = model.add_parameters({opts->rnn_size});
        }

        p_word_dec = model.add_lookup_parameters(opts->dec_word_vocab_size, {opts->dec_word_vec_size});
        if(dec_feed_hidden){
            if(bi_enc){
                dec_feeding_size = opts->rnn_size * 3;
            }else{
                dec_feeding_size = opts->rnn_size * 2;
            }
        }else{
            if(bi_enc){
                dec_feeding_size = opts->rnn_size * 2;
            }else{
                dec_feeding_size = opts->rnn_size * 1;
            }
        }
        p_Wch = model.add_parameters({dec_feeding_size, dec_feeding_size});
        p_out_R = model.add_parameters({opts->dec_word_vocab_size, dec_feeding_size});
        p_out_bias = model.add_parameters({opts->dec_word_vocab_size});
        p_Wa = model.add_parameters({opts->att_size, opts->rnn_size});
        if(bi_enc){
            p_Ua = model.add_parameters({opts->att_size, unsigned(opts->rnn_size * 2)});
        }else{
            p_Ua = model.add_parameters({opts->att_size, unsigned(opts->rnn_size * 1)});
        }
        p_va = model.add_parameters({opts->att_size});
        rev_enc_builder = dynet::VanillaLSTMBuilder(
            num_layers,
            enc_input_size,
            rnn_size,
            model
        );
        fwd_enc_builder = dynet::VanillaLSTMBuilder(
            num_layers,
            enc_input_size,
            rnn_size,
            model
        );
        dec_builder = dynet::VanillaLSTMBuilder(
            num_layers,
            (dec_feeding_size + opts->dec_word_vec_size),
            rnn_size,
            model
        );
    }

    std::vector<dynet::expr::Expression> encoder(const batch &one_batch, dynet::ComputationGraph& cg) {

        slen = one_batch.src.size();
        const unsigned int batch_size = one_batch.src.at(0).at(0).size();

        std::vector<dynet::expr::Expression> h_fwd(slen);
        std::vector<dynet::expr::Expression> h_bwd(slen);
        std::vector<dynet::expr::Expression> h_bi(slen);

        set_dropout_mask_enc_in(cg, batch_size);
        set_dropout_mask_dec_in(cg, batch_size);
        // forward encoder
        if(rev_enc == false || bi_enc == true){
            fwd_enc_builder.new_graph(cg);
            fwd_enc_builder.start_new_sequence();
            if(flag_drop_out == true && dropout_rate_lstm_con > 0.f){
                fwd_enc_builder.set_dropout_masks(batch_size);
                // reset mask on 1-layer
                fwd_enc_builder.masks[0][0] = input(cg, {fwd_enc_builder.input_dim}, std::vector<float>(fwd_enc_builder.input_dim, 1.f));
            }
            for (unsigned int t_i = 0; t_i < slen; ++t_i) {
                assert(one_batch.src.at(t_i).size() == p_feature_enc.size());
                std::vector<dynet::expr::Expression> vec_phi(p_feature_enc.size());
                for(unsigned int f_i = 0; f_i < p_feature_enc.size(); f_i++){
                    for(unsigned int b_i = 0; b_i < one_batch.src.at(t_i).at(f_i).size(); b_i++){
                        if(!(0 <= one_batch.src.at(t_i).at(f_i).at(b_i) && one_batch.src.at(t_i).at(f_i).at(b_i) < p_feature_enc.at(f_i).dim().d[1])){
                            std::cerr << "0 < " << one_batch.src.at(t_i).at(f_i).at(b_i) << " < " << p_feature_enc.at(f_i).dim().d[1] << std::endl;
                            assert(false);
                        }
                    }
                    vec_phi[f_i] = lookup(cg, p_feature_enc[f_i], one_batch.src.at(t_i).at(f_i));
                }
                assert(one_batch.src.at(t_i).size() == vec_phi.size());
                dynet::expr::Expression i_x_t = concatenate(vec_phi);
                // dropout
                i_x_t = dynet::expr::cmult(i_x_t, dropout_mask_enc_in);
                fwd_enc_builder.add_input(i_x_t);
                h_fwd[t_i] = fwd_enc_builder.back();
            }
        }
        // backward encoder
        if(rev_enc == true || bi_enc == true){
            rev_enc_builder.new_graph(cg);
            rev_enc_builder.start_new_sequence();
            if(flag_drop_out == true && dropout_rate_lstm_con > 0.f){
                rev_enc_builder.set_dropout_masks(batch_size);
                rev_enc_builder.masks[0][0] = input(cg, {rev_enc_builder.input_dim}, std::vector<float>(rev_enc_builder.input_dim, 1.f));
            }
            for (unsigned int ind = 0; ind < slen; ++ind) {
                unsigned int t_i = (slen - 1) - ind;
                assert(t_i >= 0);
                assert(one_batch.src.at(t_i).size() == p_feature_enc.size());
                std::vector<dynet::expr::Expression> vec_phi(p_feature_enc.size());
                for(unsigned int f_i = 0; f_i < p_feature_enc.size(); f_i++){
                    for(unsigned int b_i = 0; b_i < one_batch.src.at(t_i).at(f_i).size(); b_i++){
                        if(!(0 <= one_batch.src.at(t_i).at(f_i).at(b_i) && one_batch.src.at(t_i).at(f_i).at(b_i) < p_feature_enc.at(f_i).dim().d[1])){
                            std::cerr << "0 < " << one_batch.src.at(t_i).at(f_i).at(b_i) << " < " << p_feature_enc.at(f_i).dim().d[1] << std::endl;
                            assert(false);
                        }
                    }
                    vec_phi[f_i] = lookup(cg, p_feature_enc[f_i], one_batch.src.at(t_i).at(f_i));
                }
                assert(one_batch.src.at(t_i).size() == vec_phi.size());
                dynet::expr::Expression i_x_t = concatenate(vec_phi);
                // dropout
                i_x_t = dynet::expr::cmult(i_x_t, dropout_mask_enc_in);
                rev_enc_builder.add_input(i_x_t);
                h_bwd[t_i] = rev_enc_builder.back();
            }
        }
        dynet::expr::Expression i_h_enc;
        if(bi_enc){
            // bidirectional encoding
            for (unsigned i = 0; i < slen; ++i) {
                h_bi[i] = concatenate(std::vector<Expression>({h_fwd[i], h_bwd[i]}));
            }
            i_h_enc = concatenate_cols(h_bi);
        }else{
            if(rev_enc){
                // backward encoding
                i_h_enc = concatenate_cols(h_bwd);
            }else{
                // forward encoding
                i_h_enc = concatenate_cols(h_fwd);
            }
        }
        dynet::expr::Expression i_Ua = parameter(cg, p_Ua);
        dynet::expr::Expression i_Uahj = i_Ua * i_h_enc;
        // Initialize decoder
        std::vector<dynet::expr::Expression> vec_enc_final_state;
        if(rev_enc){
            vec_enc_final_state = rev_enc_builder.final_s();
        }else{
            vec_enc_final_state = fwd_enc_builder.final_s();
        }
        dec_builder.new_graph(cg);
        if(additional_connect_layer){
            std::vector<dynet::expr::Expression> vec_dec_init_state;
            for (unsigned int i = 0; i < vec_enc_final_state.size(); i++){
                dynet::expr::Expression i_dec_init_w = parameter(cg, p_dec_init_w[i]);
                dynet::expr::Expression i_dec_init_bias = parameter(cg, p_dec_init_bias[i]);
                vec_dec_init_state.push_back(tanh(i_dec_init_w * vec_enc_final_state[i]) + i_dec_init_bias);
            }
            dec_builder.start_new_sequence(vec_dec_init_state);
        }else{
            dec_builder.start_new_sequence(vec_enc_final_state);
        }
        if(flag_drop_out == true){
            dec_builder.set_dropout_masks(batch_size);
            // reset mask on 1-layer
            dec_builder.masks[0][0] = input(cg, {dec_builder.input_dim}, std::vector<float>(dec_builder.input_dim, 1.f));
        }
        return std::vector<dynet::expr::Expression>({i_Uahj, i_h_enc});
    }

    std::vector<dynet::expr::Expression> init_feed(const batch &one_batch, dynet::ComputationGraph& cg){
        // std::vector<dynet::expr::Expression> i_feed{dynet::expr::zeroes(cg, dynet::Dim({dec_feeding_size}, one_batch.trg.at(0).size()))};
        std::vector<dynet::expr::Expression> i_feed{dynet::expr::zeroes(cg, dynet::Dim({p_out_R.dim().d[1]}, one_batch.src.at(0).at(0).size()))};
        return i_feed;
    }

    dynet::expr::Expression decoder_attention(dynet::ComputationGraph& cg, const std::vector<unsigned int> prev, const dynet::expr::Expression i_feed, const dynet::expr::Expression i_Uahj){
        return decoder_attention(cg, prev, i_feed, i_Uahj, dec_builder.state());
    }

    dynet::expr::Expression decoder_attention(dynet::ComputationGraph& cg, const std::vector<unsigned int> prev, const dynet::expr::Expression i_feed, const dynet::expr::Expression i_Uahj, const dynet::RNNPointer pointer_prev){

        dynet::expr::Expression i_x_t = lookup(cg, p_word_dec, prev);
        dynet::expr::Expression i_va = parameter(cg, p_va);
        dynet::expr::Expression i_Wa = parameter(cg, p_Wa);

        dynet::expr::Expression i_dec_input = concatenate(std::vector<dynet::expr::Expression>({i_x_t, i_feed}));
        // dropout
        i_dec_input = dynet::expr::cmult(i_dec_input, dropout_mask_dec_in);
        dec_builder.add_input(pointer_prev, i_dec_input);
        dynet::expr::Expression i_h_dec = dec_builder.h.back().back();
        dynet::expr::Expression i_wah = i_Wa * i_h_dec;
        dynet::expr::Expression i_Wah = concatenate_cols(std::vector<dynet::expr::Expression>(slen, i_wah));
        dynet::expr::Expression i_att_pred_t = transpose(tanh(i_Wah + i_Uahj)) * i_va;

        return i_att_pred_t;

    }

    std::vector<dynet::expr::Expression> decoder_output(dynet::ComputationGraph& cg, const dynet::expr::Expression i_att_pred_t, const dynet::expr::Expression i_h_enc){

        dynet::expr::Expression i_out_R = parameter(cg, p_out_R);
        dynet::expr::Expression i_out_bias = parameter(cg, p_out_bias);

        dynet::expr::Expression i_alpha_t = softmax(i_att_pred_t);
        dynet::expr::Expression i_c_t = i_h_enc * i_alpha_t;
        dynet::expr::Expression i_h_dec = dec_builder.h.back().back();
        dynet::expr::Expression i_feed_next;
        if(dec_feed_hidden){
            if(additional_output_layer){
                dynet::expr::Expression i_Wch = parameter(cg, p_Wch);
                i_feed_next = tanh(i_Wch * concatenate(std::vector<Expression>({i_h_dec, i_c_t})));
            }else{
                i_feed_next = concatenate(std::vector<Expression>({i_h_dec, i_c_t}));
            }
        }else{
            if(additional_output_layer){
                dynet::expr::Expression i_Wch = parameter(cg, p_Wch);
                i_feed_next = tanh(i_Wch * i_c_t);
            }else{
                i_feed_next = i_c_t;
            }
        }
        dynet::expr::Expression i_out_pred_t = i_out_bias + i_out_R * i_feed_next;
        return std::vector<dynet::expr::Expression>({i_out_pred_t, i_feed_next});

    }

    void disable_dropout(){
        if(rev_enc == false || bi_enc == true){
            fwd_enc_builder.disable_dropout();
        }
        if(rev_enc == true || bi_enc == true){
            rev_enc_builder.disable_dropout();
        }
        dec_builder.disable_dropout();
        flag_drop_out = false;
    }

    void enable_dropout(){
        if(rev_enc == false || bi_enc == true){
            fwd_enc_builder.set_dropout(dropout_rate_lstm_con, 0.f);
        }
        if(rev_enc == true || bi_enc == true){
            rev_enc_builder.set_dropout(dropout_rate_lstm_con, 0.f);
        }
        dec_builder.set_dropout(dropout_rate_lstm_con, 0.f);
        flag_drop_out = true;
    }

    void set_dropout_mask_enc_in(dynet::ComputationGraph& cg, const unsigned int batch_size){
        if(flag_drop_out == true && dropout_rate_enc_in > 0.f){
            std::vector<dynet::expr::Expression> dropout_masks(p_feature_enc.size());
            float retention_rate = 1.f - dropout_rate_enc_in;
            float scale = 1.f / retention_rate;
            for(unsigned int f_i = 0; f_i < p_feature_enc.size(); f_i++){
                dropout_masks[f_i] = dynet::expr::random_bernoulli(cg, dynet::Dim({p_feature_enc[f_i].dim().d[0]}, batch_size), retention_rate, scale);
            }
            dropout_mask_enc_in = concatenate(dropout_masks);
        }else{
            unsigned int input_dim = 0;
            if(bi_enc ==  true || rev_enc == true){
                input_dim = rev_enc_builder.input_dim;
            }
            if(bi_enc == true || rev_enc == false){
                input_dim = fwd_enc_builder.input_dim;
            }
            dropout_mask_enc_in = input(cg, {input_dim}, std::vector<float>(input_dim, 1.f));
        }
    }

    void set_dropout_mask_dec_in(dynet::ComputationGraph& cg, const unsigned int batch_size){
        unsigned int dim_w = p_word_dec.dim().d[0];
        if(flag_drop_out == true && dropout_rate_dec_in > 0.f){
            float retention_rate = 1.f - dropout_rate_dec_in;
            float scale = 1.f / retention_rate;
            dynet::expr::Expression i_w = dynet::expr::random_bernoulli(cg, dynet::Dim({dim_w}, batch_size), retention_rate, scale);
            if(dec_feed_hidden){
                if(additional_output_layer){
                    if(bi_enc == true){
                        dynet::expr::Expression i_dec_enc_fwd_bwd = dynet::expr::random_bernoulli(cg, dynet::Dim({dec_builder.hid + fwd_enc_builder.hid + rev_enc_builder.hid}, batch_size), retention_rate, scale);
                        dropout_mask_dec_in = concatenate(std::vector<dynet::expr::Expression>({i_w, i_dec_enc_fwd_bwd}));
                    }else{
                        dynet::expr::Expression i_dec_enc_fwd = dynet::expr::random_bernoulli(cg, dynet::Dim({dec_builder.hid + fwd_enc_builder.hid}, batch_size), retention_rate, scale);
                        dropout_mask_dec_in = concatenate(std::vector<dynet::expr::Expression>({i_w, i_dec_enc_fwd}));
                    }
                }else{
                    if(bi_enc == true){
                        dynet::expr::Expression i_dec = dynet::expr::random_bernoulli(cg, dynet::Dim({dec_builder.hid}, batch_size), retention_rate, scale);
                        dynet::expr::Expression i_enc_fwd = dynet::expr::random_bernoulli(cg, dynet::Dim({fwd_enc_builder.hid}, batch_size), retention_rate, scale);
                        dynet::expr::Expression i_enc_bwd = dynet::expr::random_bernoulli(cg, dynet::Dim({rev_enc_builder.hid}, batch_size), retention_rate, scale);
                        dropout_mask_dec_in = concatenate(std::vector<dynet::expr::Expression>({i_w, i_dec, i_enc_fwd, i_enc_bwd}));
                    }else{
                        dynet::expr::Expression i_dec = dynet::expr::random_bernoulli(cg, dynet::Dim({dec_builder.hid}, batch_size), retention_rate, scale);
                        dynet::expr::Expression i_enc_fwd = dynet::expr::random_bernoulli(cg, dynet::Dim({fwd_enc_builder.hid}, batch_size), retention_rate, scale);
                        dropout_mask_dec_in = concatenate(std::vector<dynet::expr::Expression>({i_w, i_dec, i_enc_fwd}));
                    }
                }
            }else{
                if(bi_enc == true){
                    dynet::expr::Expression i_enc_fwd = dynet::expr::random_bernoulli(cg, dynet::Dim({fwd_enc_builder.hid}, batch_size), retention_rate, scale);
                    dynet::expr::Expression i_enc_bwd = dynet::expr::random_bernoulli(cg, dynet::Dim({rev_enc_builder.hid}, batch_size), retention_rate, scale);
                    dropout_mask_dec_in = concatenate(std::vector<dynet::expr::Expression>({i_w, i_enc_fwd, i_enc_bwd}));
                }else{
                    dynet::expr::Expression i_enc = dynet::expr::random_bernoulli(cg, dynet::Dim({fwd_enc_builder.hid}, batch_size), retention_rate, scale);
                    dropout_mask_dec_in = concatenate(std::vector<dynet::expr::Expression>({i_w, i_enc}));
                }
            }
        }else{
            if(dec_feed_hidden){
                if(bi_enc == true){
                    dropout_mask_dec_in = input(cg, {dim_w + dec_builder.hid + fwd_enc_builder.hid + rev_enc_builder.hid}, std::vector<float>(dim_w + dec_builder.hid + fwd_enc_builder.hid + rev_enc_builder.hid, 1.f));
                }else{
                    dropout_mask_dec_in = input(cg, {dim_w + dec_builder.hid + fwd_enc_builder.hid}, std::vector<float>(dim_w + dec_builder.hid + fwd_enc_builder.hid, 1.f));
                }
            }else{
                if(bi_enc == true){
                    dropout_mask_dec_in = input(cg, {dim_w + fwd_enc_builder.hid + rev_enc_builder.hid}, std::vector<float>(dim_w + fwd_enc_builder.hid + rev_enc_builder.hid, 1.f));
                }else{
                    dropout_mask_dec_in = input(cg, {dim_w + fwd_enc_builder.hid}, std::vector<float>(dim_w + fwd_enc_builder.hid, 1.f));
                }
            }
        }
    }

private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) {
        ar & p_feature_enc;
        ar & p_word_dec;
        ar & p_dec_init_bias;
        ar & p_dec_init_w;
        ar & p_Wa;
        ar & p_Ua;
        ar & p_va;
        ar & p_out_R;
        ar & p_out_bias;
        ar & dec_builder;
        ar & rev_enc_builder;
        ar & fwd_enc_builder;
        ar & rev_enc;
        ar & bi_enc;
        ar & dec_feed_hidden;
        ar & additional_output_layer;
        ar & additional_connect_layer;
        ar & dropout_rate_lstm_con;
        ar & dropout_rate_enc_in;
        ar & dropout_rate_enc_out;
        ar & dropout_rate_dec_in;
        ar & dropout_rate_dec_out;
    }

};

}

#endif


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_PREDEFINED_MASKS_INCLUDE
#define MTL_PREDEFINED_MASKS_INCLUDE

#include <boost/numeric/mtl/recursion/bit_masking.hpp>

namespace mtl { namespace recursion {

    // Bitmasks:
    const unsigned long morton_mask= generate_mask<true, 0, row_major, 0>::value,
			morton_z_mask= generate_mask<false, 0, row_major, 0>::value,
			doppled_2_row_mask= generate_mask<true, 1, row_major, 0>::value,
			doppled_2_col_mask= generate_mask<true, 1, col_major, 0>::value,
			doppled_4_row_mask= generate_mask<true, 2, row_major, 0>::value,
			doppled_4_col_mask= generate_mask<true, 2, col_major, 0>::value,
			doppled_16_row_mask= generate_mask<true, 4, row_major, 0>::value,
			doppled_16_col_mask= generate_mask<true, 4, col_major, 0>::value,
			doppled_z_16_row_mask= generate_mask<false, 4, row_major, 0>::value,
			doppled_z_16_col_mask= generate_mask<false, 4, col_major, 0>::value,
			doppled_32_row_mask= generate_mask<true, 5, row_major, 0>::value,
			doppled_32_col_mask= generate_mask<true, 5, col_major, 0>::value,
			doppled_z_32_row_mask= generate_mask<false, 5, row_major, 0>::value,
			doppled_z_32_col_mask= generate_mask<false, 5, col_major, 0>::value,
			doppled_64_row_mask= generate_mask<true, 6, row_major, 0>::value,
			doppled_64_col_mask= generate_mask<true, 6, col_major, 0>::value,
			doppled_z_64_row_mask= generate_mask<false, 6, row_major, 0>::value,
			doppled_z_64_col_mask= generate_mask<false, 6, col_major, 0>::value,
			doppled_128_row_mask= generate_mask<true, 7, row_major, 0>::value,
			doppled_128_col_mask= generate_mask<true, 7, col_major, 0>::value,
			doppled_z_128_row_mask= generate_mask<false, 7, row_major, 0>::value,
			doppled_z_128_col_mask= generate_mask<false, 7, col_major, 0>::value,
			shark_32_row_mask= generate_mask<true, 5, row_major, 1>::value,
			shark_32_col_mask= generate_mask<true, 5, col_major, 1>::value,
			shark_z_32_row_mask= generate_mask<false, 5, row_major, 1>::value,
			shark_z_32_col_mask= generate_mask<false, 5, col_major, 1>::value,
			shark_64_row_mask= generate_mask<true, 6, row_major, 1>::value,
			shark_64_col_mask= generate_mask<true, 6, col_major, 1>::value,
			shark_z_64_row_mask= generate_mask<false, 6, row_major, 1>::value,
			shark_z_64_col_mask= generate_mask<false, 6, col_major, 1>::value;

} // namespace recursion

// Export masks into MTL namespace

using recursion::morton_mask;
using recursion::morton_z_mask;
using recursion::doppled_2_row_mask;
using recursion::doppled_2_col_mask;
using recursion::doppled_4_row_mask;
using recursion::doppled_4_col_mask;
using recursion::doppled_16_row_mask;
using recursion::doppled_16_col_mask;
using recursion::doppled_z_16_row_mask;
using recursion::doppled_z_16_col_mask;
using recursion::doppled_32_row_mask;
using recursion::doppled_32_col_mask;
using recursion::doppled_z_32_row_mask;
using recursion::doppled_z_32_col_mask;
using recursion::doppled_64_row_mask;
using recursion::doppled_64_col_mask;
using recursion::doppled_z_64_row_mask;
using recursion::doppled_z_64_col_mask;
using recursion::doppled_128_row_mask;
using recursion::doppled_128_col_mask;
using recursion::doppled_z_128_row_mask;
using recursion::doppled_z_128_col_mask;
using recursion::shark_32_row_mask;
using recursion::shark_32_col_mask;
using recursion::shark_z_32_row_mask;
using recursion::shark_z_32_col_mask;
using recursion::shark_64_row_mask;
using recursion::shark_64_col_mask;
using recursion::shark_z_64_row_mask;
using recursion::shark_z_64_col_mask;

} // namespace mtl

#endif // MTL_PREDEFINED_MASKS_INCLUDE


// This file is part of LatNet Builder.
//
// Copyright (C) 2012-2021  The LatNet Builder author's, supervised by Pierre L'Ecuyer, Universite de Montreal.
//
// 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.

#include "latbuilder/Norm/PAlphaDPW08.h"
#include "latbuilder/WeightsDispatcher.h"
#include "latbuilder/Util.h"

#include <boost/math/special_functions/zeta.hpp>

namespace LatBuilder { namespace Norm {

namespace SumHelperPAlphaDPW08{

   template <typename WEIGHTS>
   class SumHelper {
   public:
      Real operator()(
            const WEIGHTS& weights,
            Real normType,
            Real z,
            Real lambda,
            Dimension dimension
            ) const
      { throw std::logic_error("DPW08 norm not implemented for this type of weights"); }
   };

#define DECLARE_PALPHA_DPW08_SUM(weight_type) \
      template <> \
      class SumHelper<weight_type> { \
      public: \
         Real operator()( \
               const weight_type& weights, \
               Real normType, \
               Real z, \
               Real lambda, \
               Dimension dimension \
               ) const; \
      }

   DECLARE_PALPHA_DPW08_SUM(LatticeTester::ProductWeights);
   DECLARE_PALPHA_DPW08_SUM(LatBuilder::CombinedWeights);

#undef DECLARE_PALPHA_DPW08_SUM

   //===========================================================================
   // combined weights
   //===========================================================================

   // Separating sumCombined() from
   // SumHelper<LatBuilder::CombinedWeights>::operator() is a workaround for
   // LLVM/clang++.
   Real sumCombined(
         const CombinedWeights& weights,
         Real normType,
         Real z,
         Real lambda,
         Dimension dimension
         )
   {
      Real val = 0.0;
      for (const auto& w : weights.list())
         val += WeightsDispatcher::dispatch<SumHelper>(*w, normType, z, lambda, dimension);
      return val;
   }

   Real SumHelper<LatBuilder::CombinedWeights>::operator()(
         const CombinedWeights& weights,
         Real normType,
         Real z,
         Real lambda,
         Dimension dimension
         ) const
   {
      return sumCombined(weights, normType, z, lambda, dimension);
   }

   //===========================================================================
   // product weights
   //===========================================================================

   Real SumHelper<LatticeTester::ProductWeights>::operator()(
         const LatticeTester::ProductWeights& weights,
         Real normType,
         Real z,
         Real lambda,
         Dimension dimension
         ) const
   {
      Real val = 1.0;
      for (Dimension coord = 0; coord < dimension; coord++) {
         Real weight = weights.getWeightForCoordinate(coord);
         if (weight)
            // weights are assumed to already be to the power normType; map
            // them to power 2
            val *= 1.0 + z * pow(weight, lambda * 2 / normType);
      }
      val -= 1.0;
      return val;
   }
}

PAlphaDPW08::PAlphaDPW08(unsigned int alpha, const LatticeTester::Weights& weights, Real normType):
   NormAlphaBase<PAlphaDPW08>(alpha, normType),
   m_weights(weights)
{}

template <LatticeType LR, EmbeddingType L>
Real PAlphaDPW08::value(
      Real lambda,
      const SizeParam<LR, L>& sizeParam,
      Dimension dimension,
      Real norm
      ) const
{
   norm = 1.0 / (norm * sizeParam.numPoints());
   const auto kappa = primeFactors(sizeParam.numPoints()).size();
   // 2^(kappa+1), where kappa is the number of distinct prime factors of n
   const auto k = intPow(2, kappa + 1);
   Real z = static_cast<Real>(k * boost::math::zeta<Real>(this->alpha() * lambda));
   Real val = WeightsDispatcher::dispatch<SumHelperPAlphaDPW08::SumHelper>(
         m_weights,
         this->normType(),
         z,
         lambda,
         dimension
         );

   return pow(norm * val, 1.0 / lambda);
}

template Real PAlphaDPW08::value<LatticeType::ORDINARY, EmbeddingType::UNILEVEL>(Real, const SizeParam<LatticeType::ORDINARY, EmbeddingType::UNILEVEL>&, Dimension, Real) const;
template Real PAlphaDPW08::value<LatticeType::ORDINARY, EmbeddingType::MULTILEVEL>(Real, const SizeParam<LatticeType::ORDINARY, EmbeddingType::MULTILEVEL>&, Dimension, Real) const;

template Real PAlphaDPW08::value<LatticeType::POLYNOMIAL, EmbeddingType::UNILEVEL>(Real, const SizeParam<LatticeType::POLYNOMIAL, EmbeddingType::UNILEVEL>&, Dimension, Real) const;
template Real PAlphaDPW08::value<LatticeType::POLYNOMIAL, EmbeddingType::MULTILEVEL>(Real, const SizeParam<LatticeType::POLYNOMIAL, EmbeddingType::MULTILEVEL>&, Dimension, Real) const;

}}


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2015 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph.hh"
#include "graph_filtering.hh"
#include "graph_properties.hh"
#include "graph_selectors.hh"
#include "graph_util.hh"

#include "random.hh"

#include <boost/python.hpp>

using namespace std;
using namespace boost;
using namespace graph_tool;

struct do_maximal_vertex_set
{
    template <class Graph, class VertexIndex, class VertexSet,
              class RNG>
    void operator()(const Graph& g, VertexIndex vertex_index, VertexSet mvs,
                    bool high_deg, RNG& rng) const
    {
        typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;

        uniform_real_distribution<> sample(0, 1);

        VertexSet marked(vertex_index, num_vertices(g));
        vector<vertex_t> vlist;
        double max_deg = 0, tmp_max_deg = 0;
        typename graph_traits<Graph>::vertex_iterator v, v_end;
        for (tie(v, v_end) = vertices(g); v != v_end; ++v)
        {
            vlist.push_back(*v);
            mvs[*v] = marked[*v] = false;
            max_deg = max(out_degree(*v, g), max_deg);
        }

        vector<vertex_t> selected, tmp;
        tmp.reserve(vlist.size());
        selected.reserve(vlist.size());
        while (!vlist.empty())
        {
            selected.clear();
            tmp.clear();
            tmp_max_deg = 0;

            int i, N = vlist.size();
            #pragma omp parallel for default(shared) private(i)
            for (i = 0; i < N; ++i)
            {
                typename graph_traits<Graph>::vertex_descriptor v =
                    vlist[i];

                marked[v] = false;
                bool include = true;
                typename graph_traits<Graph>::adjacency_iterator a, a_end;
                for(tie(a, a_end) = adjacent_vertices(v, g); a != a_end; ++a)
                {
                    if (mvs[*a])
                    {
                        include = false;
                        break;
                    }
                }
                if (!include)
                    continue;

                include = false;
                if (out_degree(v, g) > 0)
                {
                    double p, r;
                    if (high_deg)
                        p = out_degree(v, g) / max_deg;
                    else
                        p = 1. / (2 * out_degree(v, g));

                    #pragma omp critical
                    {
                        r = sample(rng);
                    }
                    if (r < p)
                        include = true;
                }
                else
                {
                    include = true;
                }

                if (include)
                {
                    marked[v] = true;
                    #pragma omp critical
                    {
                        selected.push_back(v);
                    }
                }
                else
                {
                    #pragma omp critical
                    {
                        tmp.push_back(v);
                        tmp_max_deg = max(tmp_max_deg, out_degree(v, g));
                    }
                }
            }

            N = selected.size();
            #pragma omp parallel for default(shared) private(i)
            for (i = 0; i < N; ++i)
            {
                typename graph_traits<Graph>::vertex_descriptor v =
                    selected[i];
                bool include = true;
                typename graph_traits<Graph>::adjacency_iterator a, a_end;
                for(tie(a, a_end) = adjacent_vertices(v, g); a != a_end; ++a)
                {
                    if (*a == v)  //skip self-loops
                        continue;
                    if (mvs[*a])
                    {
                        include = false;
                        break;
                    }

                    if (marked[*a])
                    {
                        bool inc = ((high_deg && (out_degree(v, g) >
                                                  out_degree(*a, g))) ||
                                    (!high_deg && (out_degree(v, g) <
                                                   out_degree(*a, g))));
                        if (out_degree(v, g) == out_degree(*a, g))
                            inc = v < *a;
                        include = include && inc;
                    }
                }

                if (include)
                {
                    mvs[v] = true;
                }
                else
                {
                    #pragma omp critical
                    tmp.push_back(v);
                    tmp_max_deg = max(tmp_max_deg, out_degree(v, g));
                }
                marked[v] = false;
            }

            vlist = tmp;
            max_deg = tmp_max_deg;
        }
    }
};

void maximal_vertex_set(GraphInterface& gi, boost::any mvs, bool high_deg,
                        rng_t& rng)
{
    run_action<>()
        (gi, std::bind(do_maximal_vertex_set(), placeholders::_1, gi.GetVertexIndex(),
                       placeholders::_2, high_deg, std::ref(rng)),
         writable_vertex_scalar_properties())(mvs);
}

void export_maximal_vertex_set()
{
    python::def("maximal_vertex_set", &maximal_vertex_set);
}


#include <vector>

#include <armadillo>

#include <plink/plink_file.hpp>

class lars_variables
{
public:
    virtual arma::vec get_centered_phenotype( ) const = 0;
    virtual size_t get_num_samples( ) const = 0;
    virtual size_t get_num_variables( ) const = 0;
    virtual std::string get_name(size_t index) const = 0;
    virtual void calculate_cor(const arma::vec &residual, arma::vec &c) const = 0;
    virtual arma::vec eig_prod(const arma::vec &u) const = 0;
    virtual arma::mat get_active(const arma::uvec &active) const = 0;
};

class null_lars : public lars_variables
{
    public:
        null_lars(const arma::mat &X, const arma::vec &y)
            : m_X( X ),
              m_y( y )
        {
        }

        arma::vec get_centered_phenotype() const
        {
            return m_y - arma::mean( m_y );
        }

        size_t get_num_samples() const
        {
            return m_y.n_elem;
        }

        size_t get_num_variables() const
        {
            return m_X.n_cols;
        }

        std::string get_name(size_t index) const
        {
            return "";
        }

        void calculate_cor(const arma::vec &residual, arma::vec &c) const
        {
            for(int i = 0; i < m_X.n_cols; i++)
            {
                c[ i ] = arma::dot( m_X.col( i ), residual );
            }
        }

        arma::vec eig_prod(const arma::vec &u) const
        {
            return m_X.t( ) * u;
        }

        arma::mat get_active(const arma::uvec &active) const
        {
            return m_X.cols( active );
        }

    private:
        arma::mat m_X;
        arma::vec m_y;
};

/**
 * This class is responsible for both genetic and environmental data, along
 * with computing certain properties of this data that relates to the LARS
 * algorithm. Primarily, to avoid passing and operating on each data type
 * separately within the algorithm.
 */
class gene_environment : public lars_variables
{
public:
    /**
     * Constructor.
     *
     * @param genotypes A matrix of genotypes.
     * @param cov A matrix of covariates.
     * @param phenotype A vector of phenotypes.
     * @param cov_names Names of the covariates.
     * @param only_main Exclude gene-environment.
     */
    gene_environment(genotype_matrix_ptr genotypes, const arma::mat &cov, const arma::vec &phenotype, const std::vector<std::string> &cov_names, bool only_main);
    /**
     * Imputes the missing genotypes, covariates and phenotypes.
     */
    void impute_missing();

    /**
     * Centers a phenotype and returns it.
     *
     * @return Returns a centered phenotype.
     */
    arma::vec get_centered_phenotype() const;

    /**
     * Returns the number of samples.
     *
     * @return the number of samples.
     */
    size_t get_num_samples() const;

    /**
     * Returns the number of variables.
     *
     * @return the number of variables.
     */
    size_t get_num_variables() const;

    /**
     * Returns the name of the variable with the given index.
     *
     * @param index Index of the variable whoose name to return.
     *
     * @return The name of the variable with the given index.
     */
    std::string get_name(size_t index) const;

    /**
     * Returns the complete list of variable names.
     *
     * @return List of variables.
     */
    std::vector<std::string> get_names() const;

    /**
     * Returns the number of minor alleles for the given index, and
     * 0 if index is out of bounds.
     *
     * @param index Index to the genotype.
     *
     * @return The number of minor alleles for the given index, and
     * 0 if the index is out of bounds.
     */
    unsigned int compute_num_minor(size_t index) const;

    /**
     * Calculates the correlation between each variable and
     * the given vector of residuals. The results are stored
     * in the supplied vector c.
     *
     * @param residual A vector of residuals.
     * @param c A vector of at least the size of the number of variables,
     *          to store the computed correlations in.
     */
    void calculate_cor(const arma::vec &residual, arma::vec &c) const;

    /**
     * Computes the vector a from the LARS paper from the
     * vector u.
     *
     * @param u The u-vector from the LARS paper.
     *
     * @return The a-vector from the LARS paper.
     */
    arma::vec eig_prod(const arma::vec &u) const;

    /**
     * Returns a matrix of standardized variables according to the
     * given vector of indicides of active variables.
     *
     * @param active  Indicies of active variables.
     *
     * @return Matrix of standardized active variables.
     */
    arma::mat get_active(const arma::uvec &active) const;

    /**
     * Returns a matrix of raw variables according to the
     * given vector of indicides of active variables.
     *
     * @param active  Indicies of active variables.
     *
     * @return Matrix of standardized active variables.
     */
    arma::mat get_active_raw(const arma::uvec &active) const;

private:
    /**
     * Assigns missing genotypes with genotype mean.
     */
    void fill_missing_genotypes();

    /**
     * Assigns missing phenotypes with phenotype mean.
     */
    void fill_missing_phenotypes();

    /**
     * Assigns missing covariates with covariate mean.
     */
    void fill_missing_cov();

    /**
     * Computes the mean and standard deviation of each variable
     * and stores it in the m_mean and m_sd vectors. This is performed
     * in this way because it is too expensive to store the genotypes
     * as floats.
     */
    void compute_mean_sd();

private:
    /**
     * Matrix of genotypes.
     */
    genotype_matrix_ptr m_genotypes;

    /**
     * Matrix of covariates.
     */
    arma::mat m_cov;

    /**
     * Vector of phenotypes.
     */
    arma::vec m_phenotype;

    /**
     * Vector of mean values for each variable.
     */
    arma::vec m_mean;

    /**
     * Vector of standard deviation for each variable.
     */
    arma::vec m_sd;

    /**
     * Vector variable names.
     */
    std::vector<std::string> m_names;

    /**
     * Only consider main effects.
     */
    bool m_only_main;
};


////////////////////////////////////////////////////////////////////////////////
#include <polyfem/LinearSolver.hpp>
#include <polyfem/LinearSolverEigen.hpp>
// -----------------------------------------------------------------------------
#include <Eigen/Sparse>
#ifdef POLYFEM_WITH_CHOLMOD
#include <Eigen/CholmodSupport>
#endif
#ifdef POLYFEM_WITH_UMFPACK
#include <Eigen/UmfPackSupport>
#endif
#ifdef POLYFEM_WITH_SUPERLU
#include <Eigen/SuperLUSupport>
#endif
#ifdef POLYFEM_WITH_MKL
#include <Eigen/PardisoSupport>
#endif
#ifdef POLYFEM_WITH_PARDISO
#include "LinearSolverPardiso.h"
#endif
#ifdef POLYFEM_WITH_HYPRE
#include <polyfem/LinearSolverHypre.hpp>
#endif
#include <unsupported/Eigen/IterativeSolvers>
////////////////////////////////////////////////////////////////////////////////

namespace polyfem {

////////////////////////////////////////////////////////////////////////////////

#if EIGEN_VERSION_AT_LEAST(3,3,0)

// Magic macro because C++ has no introspection
#define ENUMERATE_PRECOND(HelperFunctor, SolverType, DefaultPrecond, precond) \
    do {                                                                      \
        using namespace Eigen;                                                \
        if (precond == "Eigen::IdentityPreconditioner") {                     \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                IdentityPreconditioner>::type>();                             \
        } else if (precond == "Eigen::DiagonalPreconditioner") {              \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                DiagonalPreconditioner<double>>::type>();                     \
        } else if (precond == "Eigen::IncompleteCholesky") {                  \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                IncompleteCholesky<double>>::type>();                         \
        } else if (precond == "Eigen::LeastSquareDiagonalPreconditioner") {   \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                LeastSquareDiagonalPreconditioner<double>>::type>();          \
        } else if (precond == "Eigen::IncompleteLUT") {                       \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                IncompleteLUT<double>>::type>();                              \
        } else {                                                              \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                DefaultPrecond>::type>();                                     \
        }                                                                     \
    } while (0)

#else

// Magic macro because C++ has no introspection
#define ENUMERATE_PRECOND(HelperFunctor, SolverType, DefaultPrecond, precond) \
    do {                                                                      \
        using namespace Eigen;                                                \
        if (precond == "Eigen::IdentityPreconditioner") {                     \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                IdentityPreconditioner>::type>();                             \
        } else if (precond == "Eigen::DiagonalPreconditioner") {              \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                DiagonalPreconditioner<double>>::type>();                     \
        } else if (precond == "Eigen::IncompleteCholesky") {                  \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                IncompleteCholesky<double>>::type>();                         \
        } else if (precond == "Eigen::IncompleteLUT") {                       \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                IncompleteLUT<double>>::type>();                              \
        } else {                                                              \
            return std::make_unique<typename HelperFunctor<SolverType,        \
                DefaultPrecond>::type>();                                     \
        }                                                                     \
    } while (0)

#endif

// -----------------------------------------------------------------------------

#define RETURN_DIRECT_SOLVER_PTR(EigenSolver)                        \
    do {                                                             \
        return std::make_unique<LinearSolverEigenDirect<EigenSolver< \
            polyfem::StiffnessMatrix > > >();                                  \
    } while (0)

////////////////////////////////////////////////////////////////////////////////

namespace {

template<template<class, class> class SparseSolver, typename Precond>
struct MakeSolver {
	typedef LinearSolverEigenIterative<SparseSolver<StiffnessMatrix, Precond>> type;
};

template<template<class, int, class> class SparseSolver, typename Precond>
struct MakeSolverSym {
	typedef LinearSolverEigenIterative<SparseSolver<StiffnessMatrix,
		Eigen::Lower|Eigen::Upper, Precond> > type;
};

// -----------------------------------------------------------------------------

template<
	template<class, class> class SolverType,
	typename DefaultPrecond = Eigen::DiagonalPreconditioner<double> >
struct PrecondHelper {
	static std::unique_ptr<LinearSolver> create(const std::string &arg) {
		ENUMERATE_PRECOND(MakeSolver, SolverType, DefaultPrecond, arg);
	}
};

template<
	template<class, int, class> class SolverType,
	typename DefaultPrecond = Eigen::DiagonalPreconditioner<double> >
struct PrecondHelperSym {
	static std::unique_ptr<LinearSolver> create(const std::string &arg) {
		ENUMERATE_PRECOND(MakeSolverSym, SolverType, DefaultPrecond, arg);
	}
};

} // anonymous namespace

////////////////////////////////////////////////////////////////////////////////

// Static constructor
std::unique_ptr<LinearSolver> LinearSolver::create(const std::string &solver, const std::string &precond) {
	using namespace Eigen;

	if (solver.empty() || solver == "Eigen::SimplicialLDLT") {
		RETURN_DIRECT_SOLVER_PTR(SimplicialLDLT);
	} else if (solver == "Eigen::SparseLU") {
		RETURN_DIRECT_SOLVER_PTR(SparseLU);
#ifdef POLYFEM_WITH_CHOLMOD
	} else if (solver == "Eigen::CholmodSupernodalLLT") {
		RETURN_DIRECT_SOLVER_PTR(CholmodSupernodalLLT);
#endif
#ifdef POLYFEM_WITH_UMFPACK
#ifndef POLYFEM_LARGE_INDEX
	} else if (solver == "Eigen::UmfPackLU") {
		RETURN_DIRECT_SOLVER_PTR(UmfPackLU);
#endif
#endif
#ifdef POLYFEM_WITH_SUPERLU
	} else if (solver == "Eigen::SuperLU") {
		RETURN_DIRECT_SOLVER_PTR(SuperLU);
#endif
#ifdef POLYFEM_WITH_MKL
	} else if (solver == "Eigen::PardisoLDLT") {
		RETURN_DIRECT_SOLVER_PTR(PardisoLDLT);
	} else if (solver == "Eigen::PardisoLU") {
		RETURN_DIRECT_SOLVER_PTR(PardisoLU);
#endif
#ifdef POLYFEM_WITH_PARDISO
	} else if (solver == "Pardiso") {
		return std::make_unique<LinearSolverPardiso>();
#endif
#ifdef POLYFEM_WITH_HYPRE
	} else if (solver == "Hypre") {
		return std::make_unique<LinearSolverHypre>();
#endif
#if EIGEN_VERSION_AT_LEAST(3,3,0)
	// Available only with Eigen 3.3.0 and newer
#ifndef POLYFEM_LARGE_INDEX
	} else if (solver == "Eigen::LeastSquaresConjugateGradient") {
		return PrecondHelper<BiCGSTAB, LeastSquareDiagonalPreconditioner<double>>::create(precond);
	} else if (solver == "Eigen::DGMRES") {
		return PrecondHelper<DGMRES>::create(precond);
#endif
#endif
#ifndef POLYFEM_LARGE_INDEX
	} else if (solver == "Eigen::ConjugateGradient") {
		return PrecondHelperSym<ConjugateGradient>::create(precond);
	} else if (solver == "Eigen::BiCGSTAB") {
		return PrecondHelper<BiCGSTAB>::create(precond);
	} else if (solver == "Eigen::GMRES") {
		return PrecondHelper<GMRES>::create(precond);
	} else if (solver == "Eigen::MINRES") {
		return PrecondHelperSym<MINRES>::create(precond);
#endif
	}
	throw std::runtime_error("Unrecognized solver type: " + solver);
}

////////////////////////////////////////////////////////////////////////////////

// List available solvers
std::vector<std::string> LinearSolver::availableSolvers() {
	return {{
		"Eigen::SimplicialLDLT",
		"Eigen::SparseLU",
#ifdef POLYFEM_WITH_CHOLMOD
		"Eigen::CholmodSupernodalLLT",
#endif
#ifdef POLYFEM_WITH_UMFPACK
		"Eigen::UmfPackLU",
#endif
#ifdef POLYFEM_WITH_SUPERLU
		"Eigen::SuperLU",
#endif
#ifdef POLYFEM_WITH_MKL
		"Eigen::PardisoLDLT",
		"Eigen::PardisoLU",
#endif
#ifdef POLYFEM_WITH_PARDISO
		"Pardiso",
#endif
#ifdef POLYFEM_WITH_HYPRE
		"Hypre",
#endif
#if EIGEN_VERSION_AT_LEAST(3,3,0)
#ifndef POLYFEM_LARGE_INDEX
		"Eigen::LeastSquaresConjugateGradient",
		"Eigen::DGMRES",
#endif
#endif
		"Eigen::ConjugateGradient",
		"Eigen::BiCGSTAB",
		"Eigen::GMRES",
		"Eigen::MINRES",
	}};
}

std::string LinearSolver::defaultSolver() {
	// return "Eigen::BiCGSTAB";
#ifdef POLYFEM_WITH_PARDISO
	return "Pardiso";
#else
#ifdef POLYFEM_WITH_HYPRE
	return "Hypre";
#else
	return "Eigen::BiCGSTAB";
#endif
#endif
}

// -----------------------------------------------------------------------------

// List available preconditioners
std::vector<std::string> LinearSolver::availablePrecond() {
	return {{
		"Eigen::IdentityPreconditioner",
		"Eigen::DiagonalPreconditioner",
		"Eigen::IncompleteCholesky",
#if EIGEN_VERSION_AT_LEAST(3,3,0)
		"Eigen::LeastSquareDiagonalPreconditioner",
#endif
#ifndef POLYFEM_LARGE_INDEX
		"Eigen::IncompleteLUT",
#endif
	}};
}

std::string LinearSolver::defaultPrecond() {
	return "Eigen::DiagonalPreconditioner";
}

////////////////////////////////////////////////////////////////////////////////

} // polyfem