/*===================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.

This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

See LICENSE.txt or http://www.mitk.org for details.

===================================================================*/

#include <mitkBaseData.h>
#include <mitkImageCast.h>
#include <mitkImageToItk.h>
#include <itkEvaluateDirectionImagesFilter.h>
#include <metaCommand.h>
#include "mitkCommandLineParser.h"
#include <itkTractsToVectorImageFilter.h>
#include <usAny.h>
#include <itkImageFileWriter.h>
#include <mitkIOUtil.h>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <fstream>
#include <itksys/SystemTools.hxx>
#include <mitkCoreObjectFactory.h>

#define _USE_MATH_DEFINES
#include <math.h>

/*!
\brief Calculates the Tractometer evaluation metrics for tractograms (http://www.tractometer.org/)
*/
int main(int argc, char* argv[])
{
    mitkCommandLineParser parser;

    parser.setTitle("Tractometer Metrics");
    parser.setCategory("Fiber Tracking and Processing Methods");
    parser.setDescription("Calculates the Tractometer evaluation metrics for tractograms (http://www.tractometer.org/)");
    parser.setContributor("MBI");

    parser.setArgumentPrefix("--", "-");
    parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input tractogram (.fib, vtk ascii file format)", us::Any(), false);
    parser.addArgument("out", "o", mitkCommandLineParser::OutputDirectory, "Output:", "output root", us::Any(), false);
    parser.addArgument("labels", "l", mitkCommandLineParser::StringList, "Label pairs:", "label pairs", false);
    parser.addArgument("labelimage", "li", mitkCommandLineParser::String, "Label image:", "label image", false);
    parser.addArgument("verbose", "v", mitkCommandLineParser::Bool, "Verbose:", "output valid, invalid and no connections as fiber bundles");
    parser.addArgument("fileID", "id", mitkCommandLineParser::String, "ID:", "optional ID field");

    map<string, us::Any> parsedArgs = parser.parseArguments(argc, argv);
    if (parsedArgs.size()==0)
        return EXIT_FAILURE;

    mitkCommandLineParser::StringContainerType labelpairs = us::any_cast<mitkCommandLineParser::StringContainerType>(parsedArgs["labels"]);

    string fibFile = us::any_cast<string>(parsedArgs["input"]);
    string labelImageFile = us::any_cast<string>(parsedArgs["labelimage"]);

    string outRoot = us::any_cast<string>(parsedArgs["out"]);

    string fileID = "";
    if (parsedArgs.count("fileID"))
        fileID = us::any_cast<string>(parsedArgs["fileID"]);

    bool verbose = false;
    if (parsedArgs.count("verbose"))
        verbose = us::any_cast<bool>(parsedArgs["verbose"]);

    try
    {
        typedef itk::Image<short, 3>    ItkShortImgType;
        typedef itk::Image<unsigned char, 3>    ItkUcharImgType;

        // load fiber bundle
        mitk::FiberBundle::Pointer inputTractogram = dynamic_cast<mitk::FiberBundle*>(mitk::IOUtil::LoadDataNode(fibFile)->GetData());

        mitk::Image::Pointer img = dynamic_cast<mitk::Image*>(mitk::IOUtil::LoadDataNode(labelImageFile)->GetData());
        typedef mitk::ImageToItk< ItkShortImgType > CasterType;
        CasterType::Pointer caster = CasterType::New();
        caster->SetInput(img);
        caster->Update();
        ItkShortImgType::Pointer labelImage = caster->GetOutput();

        string path = itksys::SystemTools::GetFilenamePath(labelImageFile);

        std::vector< bool > detected;
        std::vector< std::pair< int, int > > labelsvector;
        std::vector< ItkUcharImgType::Pointer > bundleMasks;
        std::vector< ItkUcharImgType::Pointer > bundleMasksCoverage;
        short max = 0;
        for (unsigned int i=0; i<labelpairs.size()-1; i+=2)
        {
            std::pair< short, short > l;
            l.first = boost::lexical_cast<short>(labelpairs.at(i));
            l.second = boost::lexical_cast<short>(labelpairs.at(i+1));
            std::cout << labelpairs.at(i);
            std::cout << labelpairs.at(i+1);
            if (l.first>max)
                max=l.first;
            if (l.second>max)
                max=l.second;

            labelsvector.push_back(l);
            detected.push_back(false);

            {
                mitk::Image::Pointer img = dynamic_cast<mitk::Image*>(mitk::IOUtil::LoadDataNode(path+"/Bundle"+boost::lexical_cast<string>(labelsvector.size())+"_MASK.nrrd")->GetData());
                typedef mitk::ImageToItk< ItkUcharImgType > CasterType;
                CasterType::Pointer caster = CasterType::New();
                caster->SetInput(img);
                caster->Update();
                ItkUcharImgType::Pointer bundle = caster->GetOutput();
                bundleMasks.push_back(bundle);
            }

            {
                mitk::Image::Pointer img = dynamic_cast<mitk::Image*>(mitk::IOUtil::LoadDataNode(path+"/Bundle"+boost::lexical_cast<string>(labelsvector.size())+"_MASK_COVERAGE.nrrd")->GetData());
                typedef mitk::ImageToItk< ItkUcharImgType > CasterType;
                CasterType::Pointer caster = CasterType::New();
                caster->SetInput(img);
                caster->Update();
                ItkUcharImgType::Pointer bundle = caster->GetOutput();
                bundleMasksCoverage.push_back(bundle);
            }
        }
        vnl_matrix< unsigned char > matrix; matrix.set_size(max, max); matrix.fill(0);

        vtkSmartPointer<vtkPolyData> polyData = inputTractogram->GetFiberPolyData();

        int validConnections = 0;
        int noConnection = 0;
        int validBundles = 0;
        int invalidBundles = 0;
        int invalidConnections = 0;

        ItkUcharImgType::Pointer coverage = ItkUcharImgType::New();
        coverage->SetSpacing(labelImage->GetSpacing());
        coverage->SetOrigin(labelImage->GetOrigin());
        coverage->SetDirection(labelImage->GetDirection());
        coverage->SetLargestPossibleRegion(labelImage->GetLargestPossibleRegion());
        coverage->SetBufferedRegion( labelImage->GetLargestPossibleRegion() );
        coverage->SetRequestedRegion( labelImage->GetLargestPossibleRegion() );
        coverage->Allocate();
        coverage->FillBuffer(0);

        vtkSmartPointer<vtkPoints> noConnPoints = vtkSmartPointer<vtkPoints>::New();
        vtkSmartPointer<vtkCellArray> noConnCells = vtkSmartPointer<vtkCellArray>::New();

        vtkSmartPointer<vtkPoints> invalidPoints = vtkSmartPointer<vtkPoints>::New();
        vtkSmartPointer<vtkCellArray> invalidCells = vtkSmartPointer<vtkCellArray>::New();

        vtkSmartPointer<vtkPoints> validPoints = vtkSmartPointer<vtkPoints>::New();
        vtkSmartPointer<vtkCellArray> validCells = vtkSmartPointer<vtkCellArray>::New();

        boost::progress_display disp(inputTractogram->GetNumFibers());
        for (int i=0; i<inputTractogram->GetNumFibers(); i++)
        {
            ++disp;

            vtkCell* cell = polyData->GetCell(i);
            int numPoints = cell->GetNumberOfPoints();
            vtkPoints* points = cell->GetPoints();

            if (numPoints>1)
            {
                double* start = points->GetPoint(0);
                itk::Point<float, 3> itkStart;
                itkStart[0] = start[0]; itkStart[1] = start[1]; itkStart[2] = start[2];
                itk::Index<3> idxStart;
                labelImage->TransformPhysicalPointToIndex(itkStart, idxStart);

                double* end = points->GetPoint(numPoints-1);
                itk::Point<float, 3> itkEnd;
                itkEnd[0] = end[0]; itkEnd[1] = end[1]; itkEnd[2] = end[2];
                itk::Index<3> idxEnd;
                labelImage->TransformPhysicalPointToIndex(itkEnd, idxEnd);

                if ( labelImage->GetPixel(idxStart)==0 || labelImage->GetPixel(idxEnd)==0 )
                {
                    noConnection++;

                    if (verbose)
                    {
                        vtkSmartPointer<vtkPolyLine> container = vtkSmartPointer<vtkPolyLine>::New();
                        for (int j=0; j<numPoints; j++)
                        {
                            double* p = points->GetPoint(j);
                            vtkIdType id = noConnPoints->InsertNextPoint(p);
                            container->GetPointIds()->InsertNextId(id);
                        }
                        noConnCells->InsertNextCell(container);
                    }
                }
                else
                {
                    bool invalid = true;
                    for (unsigned int i=0; i<labelsvector.size(); i++)
                    {
                        bool outside = false;
                        ItkUcharImgType::Pointer bundle = bundleMasks.at(i);
                        std::pair< short, short > l = labelsvector.at(i);
                        if ( (labelImage->GetPixel(idxStart)==l.first && labelImage->GetPixel(idxEnd)==l.second) ||
                             (labelImage->GetPixel(idxStart)==l.second && labelImage->GetPixel(idxEnd)==l.first) )
                        {
                            for (int j=0; j<numPoints; j++)
                            {
                                double* p = points->GetPoint(j);

                                itk::Point<float, 3> itkP;
                                itkP[0] = p[0]; itkP[1] = p[1]; itkP[2] = p[2];
                                itk::Index<3> idx;
                                bundle->TransformPhysicalPointToIndex(itkP, idx);

                                if ( !bundle->GetPixel(idx)>0 && bundle->GetLargestPossibleRegion().IsInside(idx) )
                                {
                                    outside=true;
                                }
                            }

                            if (!outside)
                            {
                                validConnections++;
                                if (detected.at(i)==false)
                                    validBundles++;
                                detected.at(i) = true;
                                invalid = false;

                                vtkSmartPointer<vtkPolyLine> container = vtkSmartPointer<vtkPolyLine>::New();
                                for (int j=0; j<numPoints; j++)
                                {
                                    double* p = points->GetPoint(j);
                                    vtkIdType id = validPoints->InsertNextPoint(p);
                                    container->GetPointIds()->InsertNextId(id);

                                    itk::Point<float, 3> itkP;
                                    itkP[0] = p[0]; itkP[1] = p[1]; itkP[2] = p[2];
                                    itk::Index<3> idx;
                                    coverage->TransformPhysicalPointToIndex(itkP, idx);
                                    if ( coverage->GetLargestPossibleRegion().IsInside(idx) )
                                        coverage->SetPixel(idx, 1);
                                }
                                validCells->InsertNextCell(container);
                            }
                            break;
                        }
                    }
                    if (invalid==true)
                    {
                        invalidConnections++;
                        int x = labelImage->GetPixel(idxStart)-1;
                        int y = labelImage->GetPixel(idxEnd)-1;
                        if (x>=0 && y>0 && x<matrix.cols() && y<matrix.rows() && (matrix[x][y]==0 || matrix[y][x]==0) )
                        {
                            invalidBundles++;
                            matrix[x][y]=1;
                            matrix[y][x]=1;
                        }

                        if (verbose)
                        {
                            vtkSmartPointer<vtkPolyLine> container = vtkSmartPointer<vtkPolyLine>::New();
                            for (int j=0; j<numPoints; j++)
                            {
                                double* p = points->GetPoint(j);
                                vtkIdType id = invalidPoints->InsertNextPoint(p);
                                container->GetPointIds()->InsertNextId(id);
                            }
                            invalidCells->InsertNextCell(container);
                        }
                    }
                }
            }
        }

        if (verbose)
        {
            mitk::CoreObjectFactory::FileWriterList fileWriters = mitk::CoreObjectFactory::GetInstance()->GetFileWriters();
            vtkSmartPointer<vtkPolyData> noConnPolyData = vtkSmartPointer<vtkPolyData>::New();
            noConnPolyData->SetPoints(noConnPoints);
            noConnPolyData->SetLines(noConnCells);
            mitk::FiberBundle::Pointer noConnFib = mitk::FiberBundle::New(noConnPolyData);

            string ncfilename = outRoot;
            ncfilename.append("_NC.fib");

            mitk::IOUtil::SaveBaseData(noConnFib.GetPointer(), ncfilename );

            vtkSmartPointer<vtkPolyData> invalidPolyData = vtkSmartPointer<vtkPolyData>::New();
            invalidPolyData->SetPoints(invalidPoints);
            invalidPolyData->SetLines(invalidCells);
            mitk::FiberBundle::Pointer invalidFib = mitk::FiberBundle::New(invalidPolyData);

            string icfilename = outRoot;
            icfilename.append("_IC.fib");

            mitk::IOUtil::SaveBaseData(invalidFib.GetPointer(), icfilename );

            vtkSmartPointer<vtkPolyData> validPolyData = vtkSmartPointer<vtkPolyData>::New();
            validPolyData->SetPoints(validPoints);
            validPolyData->SetLines(validCells);
            mitk::FiberBundle::Pointer validFib = mitk::FiberBundle::New(validPolyData);

            string vcfilename = outRoot;
            vcfilename.append("_VC.fib");

            mitk::IOUtil::SaveBaseData(validFib.GetPointer(), vcfilename );

            {
                typedef itk::ImageFileWriter< ItkUcharImgType > WriterType;
                WriterType::Pointer writer = WriterType::New();
                writer->SetFileName(outRoot+"_ABC.nrrd");
                writer->SetInput(coverage);
                writer->Update();
            }
        }

        // calculate coverage
        int wmVoxels = 0;
        int coveredVoxels = 0;
        itk::ImageRegionIterator<ItkUcharImgType> it (coverage, coverage->GetLargestPossibleRegion());
        while(!it.IsAtEnd())
        {
            bool wm = false;
            for (unsigned int i=0; i<bundleMasksCoverage.size(); i++)
            {
                ItkUcharImgType::Pointer bundle = bundleMasksCoverage.at(i);
                if (bundle->GetPixel(it.GetIndex())>0)
                {
                    wm = true;
                    wmVoxels++;
                    break;
                }
            }
            if (wm && it.Get()>0)
                coveredVoxels++;
            ++it;
        }

        int numFibers = inputTractogram->GetNumFibers();
        double nc = (double)noConnection/numFibers;
        double vc = (double)validConnections/numFibers;
        double ic = (double)invalidConnections/numFibers;
        if (numFibers==0)
        {
            nc = 0.0;
            vc = 0.0;
            ic = 0.0;
        }
        int vb = validBundles;
        int ib = invalidBundles;
        double abc = (double)coveredVoxels/wmVoxels;

        std::cout << "NC: " << nc;
        std::cout << "VC: " << vc;
        std::cout << "IC: " << ic;
        std::cout << "VB: " << vb;
        std::cout << "IB: " << ib;
        std::cout << "ABC: " << abc;

        string logFile = outRoot;
        logFile.append("_TRACTOMETER.csv");
        ofstream file;
        file.open (logFile.c_str());
        {
            string sens = itksys::SystemTools::GetFilenameWithoutLastExtension(fibFile);
            if (!fileID.empty())
                sens = fileID;
            sens.append(",");

            sens.append(boost::lexical_cast<string>(nc));
            sens.append(",");

            sens.append(boost::lexical_cast<string>(vc));
            sens.append(",");

            sens.append(boost::lexical_cast<string>(ic));
            sens.append(",");

            sens.append(boost::lexical_cast<string>(validBundles));
            sens.append(",");

            sens.append(boost::lexical_cast<string>(invalidBundles));
            sens.append(",");

            sens.append(boost::lexical_cast<string>(abc));
            sens.append(";\n");
            file << sens;
        }
        file.close();
    }
    catch (itk::ExceptionObject e)
    {
        std::cout << e;
        return EXIT_FAILURE;
    }
    catch (std::exception e)
    {
        std::cout << e.what();
        return EXIT_FAILURE;
    }
    catch (...)
    {
        std::cout << "ERROR!?!";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}


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

#include <covis/covis.h>
using namespace covis;
using namespace cv;

#include <cassert>
using namespace std;

#include <pcl/common/time.h>
#include <pcl/io/ply_io.h>
#include <pcl/visualization/pcl_visualizer.h>
using namespace pcl;
using namespace io;
using namespace visualization;

#include <eigen3/Eigen/Eigen>
using namespace Eigen;

#include <vtkCamera.h>
#include <vtkLinearSubdivisionFilter.h>
#include <vtkPlatonicSolidSource.h>
#include <vtkPolyDataNormals.h>
#include <vtkSphereSource.h>
#include <vtkWindowToImageFilter.h>

// Types
typedef PointXYZ PointT;
typedef PointCloud<PointT> CloudT;

// Function to auto crop image (this version only outputs the ROI) - https://github.com/EndPointCorp/opencv_autocrop
Rect autocrop(Mat& src);

// Main
int main(int argc, const char** argv) {
    core::ProgramOptions po;
    po.addPositional("cad-file", "CAD file (must be in mesh format, e.g. PLY)");
    po.addOption("scale", 1, "apply this scale to the point coordinates of the input file - if your file is in e.g. mm, input 0.001 here");

    // Camera position options
    po.addOption("subdiv", 's', 1, "subdivisions of an icosahedron to generate a view sphere - the number of points become 10*4^subdiv + 2");

    po.addOption("radius", 'r', 1, "radius of the tessellated sphere, i.e. distance of the camera from the object - you can use multiple values here to get multiple radii");

    po.addOption("lat-begin", 0, "latitude start angle [deg] in [0,180] - 0 is at the pole (0,0,1)");
    po.addOption("lat-end", 180, "latitude end angle [deg] in [0,180]");

    po.addOption("lon-begin", 0, "longitude start angle [deg] in [0,360] - 0 is at the x-axis (1,0,0) and 180 is at (-1,0,0)");
    po.addOption("lon-end", 360, "longitude end angle [deg] in [0,360]");

    po.addOption("rotations", 't', 1, "number of in-plane rotations of the optical camera axis at each position (> 0)");
    po.addOption("rot-begin", 0, "rotation start angle [deg]");
    po.addOption("rot-end", 360, "rotation end angle [deg]");

    po.addOption("north", 'n', "0,0,1", "set a \"natural\" vertical axis for the model, defaults to the z-axis - the virtual camera's y-axis is aligned with the NEGATIVE of this axis");

    // Camera parameter options
    po.addOption("width", 640, "horizontal resolution");
    po.addOption("height", 480, "vertical resolution");
    po.addOption("fov", 49, "vertical field of view [degree]");

    // Output image options
    po.addOption("output-dir", ".", "output directory");
    po.addFlag("no-rgb", "don't output rgb templates");
    po.addFlag("no-depth", "don't output depth templates");
    po.addFlag("no-cloud", "don't output point cloud templates");
    po.addFlag("no-pose", "don't output camera poses");
    po.addFlag('c', "crop", "crop the output rgb/depth/cloud template");
    po.addFlag('a', "mask", "produce a crop mask for the output rgb/depth/cloud template");
    po.addFlag('m', "metric", "if this flag is set, the output depth image will be saved in the original metric units of the CAD file (float), otherwise it is scaled by 1000 and saved as 16-bit unsigned");
    po.addFlag('f', "filter-cloud", "remove NaNs, infintes and pure zeros from the cloud template - note that this destroys the organized structure");
    po.addOption("bc", "0.25,0.25,0.25", "background color for the RGB template (grayscale value or RGB triplet in [0,1])");
    po.addOption("bd", -1, "background \"depth\" for the point cloud template, use < 0 to set it to NaN - the depth template will in such case just have zero");
    po.addOption("aaframes", 1, "set the number of frames to use for anti-aliasing");

    // General options
    po.addFlag('v', "visualize", "show some visualizations");

    if(!po.parse(argc, argv))
        return 0;

    // Open CAD source as the first thing
    const string source = po.getValue("cad-file");
    const string ext = boost::algorithm::to_lower_copy(extension(boost::filesystem::path(source)));

    PolygonMesh mesh;
    COVIS_ASSERT(util::load(source, mesh));
    const float scale = po.getValue<float>("scale");
    COVIS_ASSERT(scale > 0);
    if(scale != 1) {
        cout << "NOTE: Scaling the input mesh by a factor of " << scale << "..." << endl;
        mesh = *filter::scale(boost::make_shared<PolygonMesh>(mesh), scale);
    }

    // Get options
    const size_t subdiv = po.getValue<size_t>("subdiv");
    const vector<double> radius = po.getVector<double>("radius");
    COVIS_ASSERT(!radius.empty() && radius[0] > 0 && subdiv > 0);
    const double latBegin = po.getValue<double>("lat-begin");
    const double latEnd = po.getValue<double>("lat-end");
    COVIS_ASSERT(latBegin >= 0 && latBegin <= 180 && latBegin <= latEnd);
    const double lonBegin = po.getValue<double>("lon-begin");
    const double lonEnd = po.getValue<double>("lon-end");
    COVIS_ASSERT(lonBegin >= 0 && lonBegin <= 360 && lonBegin <= lonEnd);
    const size_t rotations = po.getValue<size_t>("rotations");
    COVIS_ASSERT_MSG(rotations > 0, "At least one rotation must be performed to generate any templates!");
    const double rotBegin = po.getValue<double>("rot-begin");
    const double rotEnd = po.getValue<double>("rot-end");
    COVIS_ASSERT(rotBegin <= rotEnd);
    const vector<double> north = po.getVector<double>("north");
    COVIS_ASSERT(north.size() == 3);

    const size_t width = po.getValue<size_t>("width");
    const size_t height = po.getValue<size_t>("height");
    const double fov = po.getValue<double>("fov");

    const boost::filesystem::path outputDir = po.getValue("output-dir");
    if(!is_directory(outputDir))
        create_directory(outputDir);
    const bool noRgb = po.getFlag("no-rgb");
    const bool noDepth = po.getFlag("no-depth");
    const bool noCloud = po.getFlag("no-cloud");
    const bool noPose = po.getFlag("no-pose");
    const bool crop = po.getFlag("crop");
    const bool mask = po.getFlag("mask");
    const bool metric = po.getFlag("metric");
    const bool filterCloud = po.getFlag("filter-cloud");
    vector<double> bc = po.getVector<double>("bc");
    COVIS_ASSERT(bc.size() == 1 || bc.size() == 3);
    const double bd = po.getValue<double>("bd");
    const int aaframes = po.getValue<int>("aaframes");
    const bool visualize = po.getFlag("visualize");

    // Tessellate the view sphere by subdividing an icosahedron
    cout << "Tessellating icosahedron (12 vertices)  with " << subdiv << " subdivisions..." << endl;
    vtkSmartPointer<vtkPlatonicSolidSource> ico = vtkPlatonicSolidSource::New();
    ico->SetSolidTypeToIcosahedron();
    ico->Update();

    vtkSmartPointer<vtkLinearSubdivisionFilter> sub = vtkLinearSubdivisionFilter::New();
    sub->SetNumberOfSubdivisions(subdiv);
#if VTK_MAJOR_VERSION < 6
    sub->SetInput(ico->GetOutput());
#else
    sub->SetInputData(ico->GetOutput());
#endif
    sub->Update();

    vtkSmartPointer<vtkPoints> vtkpoints = sub->GetOutput()->GetPoints();

    cout << "\tGot " << vtkpoints->GetNumberOfPoints() << " vertices on the view sphere" << endl;

    cout << "Selecting camera positions within specified radius and lat-lon intervals and applying rotations..." << endl;
    cout << "\tRadii: ";
    core::print(radius, cout, "[" ,"]", ",");
    cout << "\tLatitude interval: [" << latBegin << "," << latEnd << "]" << endl;
    cout << "\tLongitude interval: [" << lonBegin << "," << lonEnd << "]" << endl;
    cout << "\tRotation(s) at each camera: " << rotations << " over [" << rotBegin << "," << rotEnd << "]" << endl;

    // Object center
    Eigen::Vector4f centroid;
    CloudT tmp;
    pcl::fromPCLPointCloud2(mesh.cloud, tmp);
    pcl::compute3DCentroid(tmp, centroid);

    // Generated camera positions and template labels
    vector<Eigen::Affine3f> Tcam;
    vector<string> labels;

    // Loop over all radii
    for(vector<double>::const_iterator itrad = radius.begin(); itrad != radius.end(); ++itrad) {
        // Loop over all sphere points and generate camera poses
        for(vtkIdType i = 0; i < vtkpoints->GetNumberOfPoints(); ++i) {
            // Get sampled camera position
            double pi[3];
            vtkpoints->GetPoint(i, pi);

            // Convert to  Eigen, normalize to 1 to put it on the unit sphere
            Eigen::Affine3f T;
            T.translation() << pi[0], pi[1], pi[2];
            T.translation().normalize();

            // Compute (lat,long) coordinates
            const double lati = acos(T.translation()[2]); // Relative angle with north pole in [0,pi]
            double longi = atan2(T.translation()[1], T.translation()[0]); // In [-pi,pi]
            if(longi < 0)
            	longi += 2 * M_PI; // In [0,2pi]

            // Now discard if not inside the specified lat-long interval
            const float tol = 1e-5f;
            if(lati < (latBegin - tol) * M_PI / 180 ||
                    lati > (latEnd + tol) * M_PI / 180 ||
                    longi < (lonBegin - tol) * M_PI / 180 ||
                    longi > (lonEnd + tol) * M_PI / 180) {
                continue;
            }

            // Generate a rotation, with the aim of pointing the y-axis in the opposite direction as the input pole
            Matrix3f R;
            R.col(2) = -T.translation(); // Z-axis points towards focal point

            // North pole, unity
            Vector3f vnorth = Vector3f(north[0], north[1], north[2]).normalized();

            // If z-axis is (anti-)parallel with north pole, skip
            const float ndot = R.col(2).dot(vnorth);
            const bool isNorth = fabsf(ndot) < 1 + 1e-5f && fabsf(ndot) > 1 - 1e-5f;
            if(isNorth)
                cout << "Frame with index " << i << " (anti-) parallel with north pole, using arbitrary camera orientation..." << endl;

            // Project to a sphere with specified radius
            T.translation() *= *itrad;

            // Move the whole sphere so the focal point is at the object center
            T.translation() += centroid.head<3>();

            if(isNorth) { // Use an arbitrary up-vector
                R.col(1) = R.col(2).unitOrthogonal();
            } else { // Use a well-defined up-vector (pointing north)
                // North pole
                vnorth *= *itrad;
                // Direction vector from camera to north pole
                const Vector3f dirCamNorth = (vnorth - T.translation()).normalized();
                // Projection of that vector to image plane
                const Vector3f dirCamNorthProj = dirCamNorth - dirCamNorth.dot(R.col(2)) * R.col(2);
                // That projection negated makes the up vector (y-axis points down in image)
                R.col(1) = -dirCamNorthProj.normalized();
            }

            // Final image axis
            R.col(0) = R.col(1).cross(R.col(2));
            T.linear() = R;

            // Generate all rotations around the view vector and push back the resulting camera pose and a label
            for(size_t j = 0; j < rotations; ++j) {
                // Generate a z-axis rotation matrix of the current angle (converted to radians)
                Matrix3f Rz = AngleAxisf((rotBegin + j * (rotEnd - rotBegin) / rotations) * M_PI / 180.0f, Vector3f::UnitZ()).matrix();
                // Apply rotation to the original camera pose
                Eigen::Affine3f Tz;
                Tz.translation() = T.translation();
                Tz.linear() = T.linear() * Rz;

                Tcam.push_back(Tz);
                ostringstream oss;
                oss << "template" << setfill('0') << setw(4) << labels.size();
                labels.push_back(oss.str());
            }
        }
    } // End loop over radii

    cout << "Got " << Tcam.size() << " camera positions!" << endl;

    // Show camera poses if visualization flag is on
    if(visualize) {
        cout << "Showing the camera positions..." << endl;
        PCLVisualizer visu("Camera frames");
        visu.addCoordinateSystem(radius[0] / 2);
        if(bc.size() == 1)
            visu.setBackgroundColor(bc[0], bc[0], bc[0]);
        else
            visu.setBackgroundColor(bc[0], bc[1], bc[2]);
        visu.addPolygonMesh(mesh);
        for(size_t i = 0; i < Tcam.size(); ++i)
            visu.addCoordinateSystem(radius[0]/10, Tcam[i], labels[i]);
        visu.spin();
    }

    // Print info on templates
    cout << "Generating " << Tcam.size() << " templates..." << endl;

    if(crop)
        cout << "\tNOTE: Cropping is enabled!" << endl;
    if(mask)
        cout << "\tNOTE: Mask images are output, which can be used for cropping the outputs!" << endl;
    if(filterCloud)
        cout << "\tNOTE: Cloud templates are filtered for NaNs, infinities and pure zeros - organized structure is lost!" << endl;

    if(!noDepth) {
        if(metric)
            cout << "\tNOTE: Metric units enabled - depth maps will be in same units as inputs, and the images will be saved as floats!" << endl;
        else
            cout << "\tNOTE: Metric units disabled - depth maps will be scaled by 1000, and the images will be saved as 16-bit unsigned - make sure that your input CAD file is scaled to meters!" << endl;
    }

    // Create a window for rendering templates from the mesh
    PCLVisualizer visu;
    visu.setShowFPS(false);
    if(bc.size() == 1)
        visu.setBackgroundColor(bc[0], bc[0], bc[0]);
    else
        visu.setBackgroundColor(bc[0], bc[1], bc[2]);
    visu.setSize(width, height);
    visu.setCameraFieldOfView(fov * M_PI / 180.0);
    visu.addPolygonMesh(mesh, "mesh");

    /*
     * TODO: Set some relevant parameters for rendering
     */
    vtkSmartPointer<vtkLODActor> actor = (*visu.getCloudActorMap())["mesh"].actor;
    vtkSmartPointer<vtkMapper> mapper = actor->GetMapper();
    vtkSmartPointer<vtkPolyData> poly = dynamic_cast<vtkPolyData*>(mapper->GetInput());

    vtkSmartPointer<vtkProperty> prop = actor->GetProperty();
    prop->SetInterpolationToFlat();
    prop->SetSpecularPower(128); // Full specularity to increase edge contrasts

    /*
     * Now start generating and saving templates using the visu instance
     */
    if(aaframes > 1) {
    	cout << "Using " << aaframes << " frames for anti-aliasing!" << endl;
    	visu.getRenderWindow()->SetAAFrames(aaframes); // Avoid aliasing
    }
    for(size_t i = 0; i < Tcam.size(); ++i) {
        // Setup
        visu.setWindowName(labels[i]);
        visu.setCameraPosition(
                Tcam[i].translation()[0], Tcam[i].translation()[1], Tcam[i].translation()[2], // Camera position
                centroid[0], centroid[1], centroid[2], // Focal point
                -Tcam[i].linear()(0,1), -Tcam[i].linear()(1,1), -Tcam[i].linear()(2,1) // Up vector (y)
                );
        visu.spinOnce(10, true);

        // RGB image
        vtkSmartPointer<vtkWindowToImageFilter> win2rgb = vtkWindowToImageFilter::New();
        win2rgb->SetInput(visu.getRenderWindow());
        win2rgb->ReadFrontBufferOff();

        win2rgb->SetInputBufferTypeToRGB();
        win2rgb->Update();
        vtkSmartPointer<vtkImageData> rgb = win2rgb->GetOutput();

        Mat_<Vec3b> imgrgb(height, width, reinterpret_cast<Vec3b*>(rgb->GetScalarPointer()));
        cvtColor(imgrgb, imgrgb, CV_RGB2BGR);

        // Z buffer
        Mat_<float> imgz(height, width);
        visu.getRenderWindow()->GetZbufferData(0, 0, width-1, height-1, imgz[0]);

        // Create real depth from z buffer (taken from PCLVisualizer source code for renderView())
        vtkRenderer* ren = visu.getRenderWindow()->GetRenderers()->GetFirstRenderer();
        vtkCamera* cam = ren->GetActiveCamera();

        // TODO: What are these matrices?
        vtkSmartPointer<vtkMatrix4x4> proj =
                cam->GetCompositeProjectionTransformMatrix(ren->GetTiledAspectRatio(), 0, 1);
        vtkSmartPointer<vtkMatrix4x4> view = cam->GetViewTransformMatrix();

        Matrix4f mat1, mat2;
        for(int r = 0; r < 4; ++r) {
            for(int c = 0; c < 4; ++c) {
                mat1(r, c) = proj->Element[r][c];
                mat2(r, c) = view->Element[r][c];
            }
        }
        mat1 = mat1.inverse().eval();

        // TODO: What are these?
        const float dwidth = 2.0f / float (width);
        const float dheight = 2.0f / float (height);

        // Convert to depth image and point cloud, both as float in original input metric units
        Mat_<float> imgdepth(height, width);
        PointCloud<PointXYZRGBA>::Ptr cloud(new PointCloud<PointXYZRGBA>(width, height));
        for(size_t y = 0; y < height; ++y) {
            for(size_t x = 0; x < width; ++x) {
                // Output point
                PointXYZRGBA& p = (*cloud)(x,y);

                // Max z buffer value, background
                if(imgz(y, x) == 1.0) {
                    if(bd < 0) {
                        p.x = p.y = p.z = numeric_limits<float>::quiet_NaN();
                        imgdepth(y,x) = 0;
                    } else {
                        p.x = p.y = p.z = bd;
                        imgdepth(y,x) = bd;
                    }
                    continue;
                }

                Vector4f xyz(dwidth * x - 1.0f, dheight * y - 1.0f, imgz(y, x), 1.0f);
                xyz = mat2 * mat1 * xyz;

                const float w3 = 1.0f / xyz[3];
                xyz[0] *= w3;
                // Go from VTK frame to correct frame
                xyz[1] *= -w3;
                xyz[2] *= -w3;

                imgdepth(y,x) = xyz[2];

                p.x = xyz[0];
                p.y = xyz[1];
                p.z = xyz[2];
                p.r = imgrgb(y,x)[2];
                p.g = imgrgb(y,x)[1];
                p.b = imgrgb(y,x)[0];
            }
        }

        // Convert depth image to mm, 16-bit unsigned
        Mat_<ushort> imgdepthmm;
        if(!metric)
            imgdepth.convertTo(imgdepthmm, CV_16U, 1000, 0);

        // Cropping: if cropping enabled, crop all templates, otherwise output a mask that can be used to crop
        Mat_<uchar> cropMask;
        Rect win;
        if(crop || mask) {
            cropMask = Mat_<uchar>(height, width, uchar(0));
            win = autocrop(imgrgb);
            cropMask(win) = 255;
        }

        if(crop) {
            // Crop images
            imgrgb = imgrgb(win);
            if(metric)
            	imgdepth = imgdepth(win);
            else
                imgdepthmm = imgdepthmm(win);

            // Crop point cloud
            PointCloud<PointXYZRGBA> cloudc(win.width, win.height);
            for(int x = 0; x < win.width; ++x)
                for(int y = 0; y < win.height; ++y)
                    cloudc(x, y) = (*cloud)(win.x + x, win.y + y);
            *cloud = cloudc;
        }

        // Flip up/down because we want z pointing out and not in as in VTK
        flip(imgrgb, imgrgb, 0);
        if(metric)
        	flip(imgdepth, imgdepth, 0);
        else
            flip(imgdepthmm, imgdepthmm, 0);
        PointCloud<PointXYZRGBA> cloudf(cloud->width, cloud->height);
        for(size_t x = 0; x < cloud->width; ++x)
            for(size_t y = 0; y < cloud->height; ++y)
                cloudf(x, y) = (*cloud)(x, cloud->height - y - 1);
        *cloud = cloudf;

        // Filter NaNs, Infs and 0s
        if(filterCloud) {
            PointCloud<PointXYZRGBA> cloudf;
            for(size_t i = 0; i < cloud->size(); ++i)
                if(pcl::isFinite(cloud->points[i]) && !cloud->points[i].getVector3fMap().isZero())
                    cloudf.push_back(cloud->points[i]);
            *cloud = cloudf;
        }

        // Output to files
        if(!noRgb)
            COVIS_ASSERT(imwrite(outputDir.string() + "/" + labels[i] + ".png", imgrgb));
        if(!noDepth) {
            if(metric) {
                COVIS_ASSERT(imwrite(outputDir.string() + "/" + labels[i] + "_depth.png", imgdepth));
            } else {
                COVIS_ASSERT(imwrite(outputDir.string() + "/" + labels[i] + "_depth.png", imgdepthmm));
            }
        }
        if(!noCloud)
            COVIS_ASSERT(savePCDFile(outputDir.string() + "/" + labels[i] + "_cloud.pcd", *cloud, true) == 0);
        if(mask)
            COVIS_ASSERT(imwrite(outputDir.string() + "/" + labels[i] + "_mask.png", cropMask));
        if(!noPose)
            for(size_t i = 0; i < Tcam.size(); ++i)
                util::saveEigen(outputDir.string() + "/" + labels[i] + "_pose.txt",
                                Matrix4f(Tcam[i].inverse().matrix()),
                                true, // Row-major
                                false, // Binary
                                false); // Append
    }

    return 0;
}

// Internal function used by autocrop()
inline bool isBorder(Mat& edge, Vec3b color) {
    Mat im = edge.clone().reshape(0,1);

    bool res = true;
    for(int i = 0; i < im.cols; ++i)
        res &= (color == im.at<Vec3b>(0,i));

    return res;
}

Rect autocrop(Mat& src) {
    COVIS_ASSERT(src.type() == CV_8UC3);
    Rect win(0, 0, src.cols, src.rows);

    vector<Rect> edges;
    edges.push_back(Rect(0, 0, src.cols, 1));
    edges.push_back(Rect(src.cols-2, 0, 1, src.rows));
    edges.push_back(Rect(0, src.rows-2, src.cols, 1));
    edges.push_back(Rect(0, 0, 1, src.rows));

    Mat edge;
    int nborder = 0;
    Vec3b color = src.at<Vec3b>(0,0);

    for (size_t i = 0; i < edges.size(); ++i) {
        edge = src(edges[i]);
        nborder += isBorder(edge, color);
    }

    if (nborder < 4)
        return win;

    bool next;

    do {
        edge = src(Rect(win.x, win.height-2, win.width, 1));
        if( (next = isBorder(edge, color)) )
            win.height--;
    } while (next && win.height > 0);

    do {
        edge = src(Rect(win.width-2, win.y, 1, win.height));
        if( (next = isBorder(edge, color)) )
            win.width--;
    } while (next && win.width > 0);

    do {
        edge = src(Rect(win.x, win.y, win.width, 1));
        if( (next = isBorder(edge, color)) )
            win.y++, win.height--;
    } while (next && win.y <= src.rows);

    do {
        edge = src(Rect(win.x, win.y, 1, win.height));
        if( (next = isBorder(edge, color)) )
            win.x++, win.width--;
    } while (next && win.x <= src.cols);

    return win;
}


#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <random>
#include <chrono>
#include <functional>
#include <cctype>
#include <locale>
#include <algorithm>
#include <regex>
#include <iterator>
#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include "perceptron.h"
#include "segmentor.h"
#include "featurizer.h"
#include "util.h"

using namespace std;

int main() {
    AveragedPerceptron* perceptron = new AveragedPerceptron();
    Featurizer* feat = new Featurizer();
    map<string, int> index_table;
    feat->index_table = index_table;

    ifstream feature_is("feature_index.dat", ios::binary);
    boost::archive::binary_iarchive feature_ar(feature_is);
    feature_ar >> feat;

    ifstream perceptron_is("perceptron.dat", ios::binary);
    boost::archive::binary_iarchive perceptron_ar(perceptron_is);
    perceptron_ar >> perceptron;

    Segmentor* seg = new Segmentor(perceptron, feat);

    cout << "Total feature of : " << seg->feat->num << endl;

    ifstream test_query ("../data/dummytest.qry");
    string line;

    vector<string> test_texts;

    if (test_query.is_open()) {
        while (getline(test_query, line)) {
            transform(line.begin(), line.end(), line.begin(), ::tolower);
            line = trim(line);

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

            test_texts.push_back(line);
        }
    }

    ofstream prediction_file ("../prediction/ap_cpp.txt");

    for (auto x: test_texts) {
        vector<int> prediction = seg->predict(x);
        stringstream ss;
        for (int i = 0; i < prediction.size(); ++i) {
            ss << x[i];
            if (prediction[i] == 1) {
                ss << " ";
            }
        }
        string result = ss.str();
        prediction_file << trim(result) << endl;
        ss.str("");
        ss.clear();
    }

    return 0;
};


/*
@copyright Louis Dionne 2014
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/hana/sandbox/searchable_set.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/minimal/comparable.hpp>
using namespace boost::hana;

template <int i>
constexpr auto x = detail::minimal::comparable<>(i);

int main() {
    BOOST_HANA_CONSTEXPR_ASSERT( subset(singleton(x<0>), singleton(x<0>)));
    BOOST_HANA_CONSTEXPR_ASSERT(!subset(singleton(x<1>), singleton(x<0>)));

    BOOST_HANA_CONSTEXPR_ASSERT( subset(singleton(x<0>), doubleton(x<0>, x<1>)));
    BOOST_HANA_CONSTEXPR_ASSERT( subset(singleton(x<1>), doubleton(x<0>, x<1>)));
    BOOST_HANA_CONSTEXPR_ASSERT(!subset(singleton(x<2>), doubleton(x<0>, x<1>)));

    BOOST_HANA_CONSTEXPR_ASSERT( subset(doubleton(x<0>, x<1>), doubleton(x<0>, x<1>)));
    BOOST_HANA_CONSTEXPR_ASSERT(!subset(doubleton(x<0>, x<2>), doubleton(x<0>, x<1>)));
    BOOST_HANA_CONSTEXPR_ASSERT(!subset(doubleton(x<2>, x<3>), doubleton(x<0>, x<1>)));
}


#include "InputFileOperator.h"
#include "CommandLineHandlers/CommandLineHandler.h"
#include <boost/tokenizer.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/program_options.hpp>

USING_NAMESPACE(std);
USING_NAMESPACE(clu);
namespace po = boost::program_options;

class FilterOperator : public InputFileOperator
{
public:
	FilterOperator(size_t argument_number) : m_argumentNumber(argument_number - 1)
	{
		if (argument_number == 0)
			m_fullline = true;
		else
			m_fullline = false;
	}

	virtual bool OnLineRead(string& input)
	{
		string argument;
		if (m_fullline)
			argument = input;
		else {
			vector<string> tokens;
			ExtractTokens(input, tokens, SpaceDelimiters);

			if (tokens.size() <= m_argumentNumber)
				return true;

			argument = tokens[m_argumentNumber];
		}

		if (shouldPrint(argument))
			m_OutputHandler->OutputLineFeed(input);
		return true;
	}

	virtual bool shouldPrint(string& data) = 0;
protected:
	size_t m_argumentNumber;
	bool m_fullline;
};

class NumericFilter : public FilterOperator
{
public:
	NumericFilter(size_t arg_num, double arg_value) : FilterOperator(arg_num), m_argValue(arg_value)
	{
	}

	virtual bool shouldPrint(string& data)
	{
		double cur_value = atof(data.c_str());
		return shouldPrint(cur_value);
	}

	virtual bool shouldPrint(double value) = 0;
protected:
	double m_argValue;
};

class GreaterThanFilter : public NumericFilter
{
public:
	GreaterThanFilter(int arg_num, double threshold) : NumericFilter(arg_num, threshold)
	{
	}
	virtual bool shouldPrint(double value)
	{
		return m_argValue < value;
	}
};

class LessThanFilter : public NumericFilter
{
public:
	LessThanFilter(int arg_num, double threshold) : NumericFilter(arg_num, threshold) {}
	virtual bool shouldPrint(double value)
	{
		return m_argValue > value;
	};
};

class ContainStringFilter : public FilterOperator
{
public:
	ContainStringFilter(size_t arg_num, string value, bool match) : FilterOperator(arg_num), m_match(match)
	{
		m_search_regex = boost::regex(value.c_str());
	}
	virtual bool shouldPrint(string& data)
	{
		bool ret_value = false;
		if (m_match)
			ret_value = boost::regex_match(data, m_search_regex);
		else
			ret_value = boost::regex_search(data, m_search_regex);

		return ret_value;
	}
protected:
	boost::regex m_search_regex;
	bool m_match;
};

class NotContainStringFilter : public ContainStringFilter
{
public:
	NotContainStringFilter(int arg_num, string value, bool match) : ContainStringFilter(arg_num, value, match)
	{
	}

	virtual bool shouldPrint(string& data)
	{
		return ContainStringFilter::shouldPrint(data);
	}
};

class Filter_CommandLine : public InputFileOperator_CommandLine
{
public:
	virtual bool AddProgramOptions(po::options_description& desc)
	{
		desc.add_options()
			("filter,f", po::value<int>(), "Filter Operator - Filter argument")
			("larger,l", po::value<double>(), "Filter Operator - Larger")
			("smaller,s", po::value<double>(), "Filter Operator - Smaller")
			("contains", po::value<string>(), "Filter Operator - Contains")
			("ncontains", po::value<string>(), "Filter Operator - Not Contains")
			("reg", po::bool_switch(), "Filter Operator - Contains / Not Contains regular expression, also in start stop operations");
		return true;
	}

	virtual InputFileOperator* GetOperator(const po::variables_map &vm)
	{
		InputFileOperator* listener = NULL;
		if (vm.count("filter")) {
			int filter_value = vm["filter"].as<int>();
			if (vm.count("larger")) {
				double larger_value = vm["larger"].as<double>();
				listener = new GreaterThanFilter(filter_value, larger_value);
			}
			if (vm.count("smaller")) {
				double smaller_value = vm["smaller"].as<double>();
				listener = new LessThanFilter(filter_value, smaller_value);
			}

			bool match = false;
			if (vm.count("reg") && vm["reg"].as<bool>())
				match = true;
			if (vm.count("contains")) {
				const string& contains_value = vm["contains"].as<string>();
				listener = new ContainStringFilter(filter_value, contains_value, match);
			}
			if (vm.count("ncontains")) {
				const string& ncontains_value = vm["ncontains"].as<string>();
				listener = new NotContainStringFilter(filter_value, ncontains_value, match);
			}
		}
		return listener;
	}
};

static auto nameRegisterVar = CommandLineHandler::GetInstance().AddCommandLineHandler(new Filter_CommandLine());


// Copyright Abel Sinkovics (abel@sinkovics.hu) 2010.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <mpllibs/metamonad/do_.hpp>
#include <mpllibs/metamonad/tmp_tag.hpp>
#include <mpllibs/metamonad/tmp_value.hpp>
#include <mpllibs/metamonad/returns.hpp>
#include <mpllibs/metamonad/lazy_metafunction.hpp>
#include <mpllibs/metamonad/metafunction.hpp>
#include <mpllibs/metamonad/eval_let.hpp>
#include <mpllibs/metamonad/let.hpp>
#include <mpllibs/metamonad/lambda_c.hpp>
#include <mpllibs/metamonad/name.hpp>
#include <mpllibs/metamonad/either.hpp>
#include <mpllibs/metamonad/lazy.hpp>
#include <mpllibs/metamonad/apply.hpp>
#include <mpllibs/metamonad/syntax.hpp>
#include <mpllibs/boost.hpp>

#include <boost/test/unit_test.hpp>

#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/tag.hpp>
#include <boost/mpl/assert.hpp>

#include <boost/type_traits.hpp>

#include "common.hpp"

using boost::mpl::minus;

using mpllibs::metamonad::tmp_tag;
using mpllibs::metamonad::tmp_value;
using mpllibs::metamonad::returns;
using mpllibs::metamonad::lazy;
using mpllibs::metamonad::right;

/*
 * WrapperMonad
 */
namespace
{
  struct wrapper_tag : tmp_tag<wrapper_tag> {};

  typedef wrapper_tag wrapper_monad;

  MPLLIBS_METAFUNCTION(wrapped, (T)) ((tmp_value<wrapped<T>, wrapper_tag>));

  template <class T>
  struct unwrap;

  template <class T>
  struct unwrap<wrapped<T> > : returns<T> {};
}

namespace mpllibs
{
  namespace metamonad
  {
    namespace v1
    {
      template <>
      struct monad<wrapper_tag>
      {
        typedef lambda_c<t, wrapped<t> > return_;
        typedef lambda_c<a, f, apply<f, a> > bind;
      };
    }
  }
}

namespace MPLLIBS_BOOST_NAMESPACE
{
  namespace mpl
  {
    template <>
    struct equal_to_impl<wrapper_tag, wrapper_tag>
    {
      MPLLIBS_LAZY_METAFUNCTION(apply, (A)(B))
      ((lazy<equal_to<unwrap<A>, unwrap<B> > >));
    };
  }
}

namespace
{
  MPLLIBS_LAZY_METAFUNCTION(eval_to_right, (T)) ((right<T>));
}

BOOST_AUTO_TEST_CASE(test_do2)
{
  using mpllibs::metamonad::do_;
  using mpllibs::metamonad::let;
  using mpllibs::metamonad::eval_let;
  using mpllibs::metamonad::syntax;
  using mpllibs::metamonad::set;
  using mpllibs::metamonad::do_return;

  using boost::is_same;

  using boost::mpl::equal_to;

  typedef boost::mpl::tag<int13>::type int_tag;
  typedef mpllibs::metamonad::either_tag<int_tag, int_tag> either;

  // test_contents_of_return_is_substituted
  BOOST_MPL_ASSERT((
    equal_to<
      right<right<int13> >,
      do_<either,
        syntax<do_return<do_return<int13> > >
      >::type
    >
  ));

  // test_nested_do_with_different_monads
  BOOST_MPL_ASSERT((
    equal_to<
      right<wrapped<int13> >,
      do_<either,
        syntax<
          eval_to_right<
            do_<wrapper_monad,
              syntax<do_return<int13> >
            >
          >
        >
      >::type
    >
  ));

  // test_set_in_let
  BOOST_MPL_ASSERT((
    equal_to<
      right<int13>,
      eval_let<
        x, syntax<int11>,
        syntax<
          do_<either,
            syntax<set<x, do_return<int13> > >,
            syntax<do_return<x> >
          >
        >
      >::type
    >
  ));

  // test_argument_of_set_and_do_in_let
  BOOST_MPL_ASSERT((
    equal_to<
      right<int11>,
      eval_let<
        x, syntax<int11>,
        syntax<
          do_<either,
            syntax<set<x, do_return<int13> > >,
            syntax<do_return<int11> >
          >
        >
      >::type
    >
  ));

  // test_na_arguments_of_do
  BOOST_MPL_ASSERT((
    is_same<
      syntax<do_<either, syntax<do_return<int11> > > >,
      let<
        x, syntax<int11>,
        syntax<do_<either, syntax<do_return<int11> > > >
      >::type
    >
  ));
}


//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Vicente J. Botet Escriba 2011,2016.
// Distributed under the Boost
// Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or
// copy at http://www.boost.org/LICENSE_1_0.txt)
//
//////////////////////////////////////////////////////////////////////////////

/*!
 \file
 \brief
 The header \c <experimental/ordinal_range.hpp> defines a class template \c ordinal_range for viewing an ordinal as a range.
 */

#ifndef JASEL_FUNDAMENTAL_V3_ORDINAL_ORDINAL_RANGE_HPP
#define JASEL_FUNDAMENTAL_V3_ORDINAL_ORDINAL_RANGE_HPP

#include <experimental/ordinal.hpp>

#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/iterator_range.hpp>
#include <iosfwd>
#include <iostream>

namespace std
{
namespace experimental
{
inline namespace fundamental_v3
{

    #ifndef JASEL_DOXYGEN_INVOKED
    namespace ordinal_detail {
        // ordinal_iterator is an iterator over an ordinal  that
        // is bounded only by the limits of the ordinal.
        //
        // This is useful for implementing the ordinal_range<E>()
        // function.
        //
        // @par Note:
        // The use of this iterator and ordinal_range<E>() is appreciably less
        // performant than the corresponding hand-written integer
        // loop on many compilers.

        //! ordinal_iterator is a model of RandomAccessIterator
        template<typename T /* , typename Traits=ordinal_range_traits<T> */ >
        class ordinal_iterator
            : public boost::iterator_facade<
                        ordinal_iterator<T>,
                        T,
                        boost::random_access_traversal_tag,
                        T,
                        std::ptrdiff_t
                    >
        {
            typedef boost::iterator_facade<
                        ordinal_iterator<T>,
                        T,
                        boost::random_access_traversal_tag,
                        T,
                        std::ptrdiff_t
                    > base_t;
        public:
            typedef typename base_t::value_type value_type;
            typedef typename base_t::difference_type difference_type;
            typedef typename base_t::reference reference;

            ordinal_iterator() : index_(0u) {}
            explicit ordinal_iterator(ordinal::index_t x) : index_(x) {}

        private:
            void increment()
            {
                ++index_;
            }

            void decrement()
            {
                --index_;
            }

            void advance(difference_type offset)
            {
                index_ += offset;
            }

            difference_type distance_to(const ordinal_iterator& other) const
            {
                return other.index_ - index_;
            }

            bool equal(const ordinal_iterator& other) const
            {
                return index_ == other.index_;
            }

            reference dereference() const
            {
                return ordinal::val<T>(index_);
            }

            friend class ::boost::iterator_core_access;
            ordinal::index_t index_;
        };
    } // namespace ordinal_detail
    #endif

    /**
     @tparam T set's element ordinal type

     @par Requires: @c T must be a model of Ordinal.

     \c ordinal_range is a model of the RandomAccessRange Concept associated to the ordinal \c T.
     */

    template<typename T/* , typename Traits=ordinal_range_traits<T> */ >
    class ordinal_range
        : public boost::iterator_range< ordinal_detail::ordinal_iterator<T/*, Traits*/> >
    {
        typedef ordinal_detail::ordinal_iterator<T/*, Traits*/> iterator_t;
        typedef boost::iterator_range<iterator_t> base_t;
    public:
        //! builds a ordinal range
        ordinal_range()
            : base_t(iterator_t(0), iterator_t(ordinal::meta::size<T>::value))
        {
        }

        //! builds a ordinal sub-range
        ordinal_range(T first, T last)
            : base_t(iterator_t(ordinal::pos(first)),
                     iterator_t(ordinal::pos(last)+1))
        {
        }
    };

    //! function to generate an ordinal range.
    //! \c make_range allows treating ordinals as a model of the \e RandomAccessRange Concept.

    //! @par Requires:
 \c T is a model of the \e Ordinal Concept.
    //! @par Returns: an ordinal range from @c T'first to @c T'last inclusive.
    template<typename T /*, typename Traits*/ >
    ordinal_range<T>
    make_range()
    {
        return ordinal_range<T/*,Traits*/>();
    }

    //! function to generate an ordinal sub-range.
    //! \c make_range allows treating ordinal as a model of the \e RandomAccessRange Concept.
    //! It should be noted that the first and last parameters denoted a closed range.
    //! @par Requires: \c T is a model of the \e Ordinal Concept.
    //! @param first element of the range
    //! @param last element of the range
    //! @par Returns: an ordinal range from @c first to @c last inclusive.

    template<typename T /*, typename Traits */ >
    ordinal_range<T>
    make_range(T first, T last)
    {
        return ordinal_range<T /*,Traits*/ >(first,last);
    }

}} // namespace experimental::fundamental_v3
} // std

#endif // header


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<boost::uint8_t, Eigen::Dynamic, 4> test_uchar_D_4(const Eigen::Matrix<boost::uint8_t, Eigen::Dynamic, 4> & M)
{
	return M;
}
void export_uchar_D_4()
{
	boost::python::def("test_uchar_D_4",test_uchar_D_4);
}


/**
 *
 * Copyright (c) 2009-2020, Kitty Barnett
 *
 * The source code in this file is provided to you under the terms of the
 * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
 * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
 *
 * By copying, modifying or distributing this software, you acknowledge that
 * you have read and understood your obligations described above, and agree to
 * abide by those obligations.
 *
 */

#include "llviewerprecompiledheaders.h"

#include "llinventoryfunctions.h"
#include "llsettingsvo.h"
#include <boost/algorithm/string.hpp>

#include "rlvactions.h"
#include "rlvenvironment.h"
#include "rlvhelper.h"

// ================================================================================================
// Constants and helper functions
//

namespace
{
	const F32 SLIDER_SCALE_BLUE_HORIZON_DENSITY(2.0f);
	const F32 SLIDER_SCALE_DENSITY_MULTIPLIER(0.001f);
	const F32 SLIDER_SCALE_GLOW_R(20.0f);
	const F32 SLIDER_SCALE_GLOW_B(-5.0f);
	const F32 SLIDER_SCALE_SUN_AMBIENT(3.0f);

	const std::string RLV_GETENV_PREFIX = "getenv_";
	const std::string RLV_SETENV_PREFIX = "setenv_";

	U32 rlvGetColorComponentFromCharacter(char ch)
	{
		if ( ('r' == ch) || ('x' == ch) )      return VRED;
		else if ( ('g' == ch) || ('y' == ch )) return VGREEN;
		else if ( ('b' == ch) || ('d' == ch) ) return VBLUE;
		else if ('i' == ch)                    return VALPHA;
		return U32_MAX;
	}

	const LLUUID& rlvGetLibraryEnvironmentsFolder()
	{
		LLInventoryModel::cat_array_t cats;
		LLInventoryModel::item_array_t items;
		LLNameCategoryCollector f("Environments");
		gInventory.collectDescendentsIf(gInventory.getLibraryRootFolderID(), cats, items, LLInventoryModel::EXCLUDE_TRASH, f);
		return (!cats.empty()) ? cats.front()->getUUID() : LLUUID::null;
	}

	// Legacy WindLight values we need tend to be expressed as a fraction of the [0, 2PI[ domain
	F32 normalize_angle_domain(F32 angle)
	{
		while (angle < 0)
			angle += F_TWO_PI;
		while (angle > F_TWO_PI)
			angle -= F_TWO_PI;
		return angle;
	}
}

/*
 * Reasoning (Reference - https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Azimuth-Altitude_schematic.svg/1024px-Azimuth-Altitude_schematic.svg.png)
 *
 * Given a(zimuth angle) and e(levation angle) - in the SL axis - we know that it calculates the quaternion as follows:
 *
 * | cos a	sin a	0 |   | cos e |   | cos a x cos e | = | x |
 * | sin a	cos a	0 | x |     0 | = | sin a x cos e | = | y | (normalized direction vector identifying the sun position on a unit sphere)
 * |     0	    0	1 |   | sin e |   |         sin e | = | z |
 *
 * As a result we can reverse the above by: quaternion -> rotate it around X-axis
 *   x = cos a x cos e <=> cos a = x / cos e   \
 *                                              | (if we divide them we can get rid of cos e)
 *                                              | <=> sin a / cos a = y / x <=> tan a = y / x <=> a = atan2(y, x)
 *   y = sin a x cos e <=> sin a = y / cos e   /
 *   z = sin e         <=> e = asin z
 *
 * If we look at the resulting domain azimuth lies in ]-PI, PI] and elevation lies in [-PI/2, PI/2] which I actually prefer most. Going forward people should get the sun in a wind
 * direction by manipulating the azimuth and then deal with the elevation (which ends up mimicking how a camera or an observer behave in real life).
 *
 * Special cases:
 *   x = 0 => (1) cos e = 0 -> sin e = 1 so y = 0     and z = 1                                    => in (0, 0, 1) we loose all information about the azimuth since cos e = 0
 *         OR (2) cos a = 0 -> sin a = 1 so y = cos e and z = sin e => tan e = z/y (with y != 0)   => in (0, Y, Z) azimuth is PI/2 (or 3PI/2) and elevation can have an extended domain of ]-PI, PI]
 *         => When x = 0 (and y != 0) return PI/2 for azimuth and atan2(z, y) for elevation
 *   y = 0 => (1) sin a = 0 -> cos a = 1 so x = cos e and z = sin e => tan e = z/x (with x != 0)   => in (X, 0, Z) azimuth is    0 (or PI)    and elevation can have an extended domain of ]-PI, PI]
 *         OR (2) cos e = 0 -> see above
		   => When y = 0 (and x != 0) return 0 for azimuth and atan2(z, x) for elevation
 *   z = 0 =>     sin e = 0 -> cos e = 1 so x = cos a and y = sin a => tan a = y / x               => in (X, Y, 0) elevation is  0 (or PI)    and azimuth has its normal domain of ]-PI, PI]
 *         => When z = 0 return 0 for elevation and a = atan2(y, x) for azimuth
 *
 * We still need to convert all that back/forth between legacy WindLight's odd choices:
 *   east angle   = SL's azimuth rotates from E (1, 0, 0) to N (0, 1, 0) to W (-1, 0, 0) to S (0, -1, O) but the legacy east angle rotates the opposite way from E to S to W to N so invert the angle
 *                  (the resulting number then needs to be positive and reported as a fraction of 2PI)
 *   sunposition  = sun elevation reported as a fraction of 2PI
 *   moonposition = the moon always has sun's azimuth but its negative elevation
 *
 * Pre-EEP both azimuth and elevation have a 2PI range which means that two different a and e value combinations could yield the same sun direction which causes us problems now since we
 * can't differentiate between the two. Pre-EEP likely favoured elevation over azimuth since people might naturally get the time of day they're thinking of and then try to adjust the
 * azimuth to get the sun in the correct wind direction; however I've already decided that we'll favour azimuth going forward (see above).
 *
 * Comparison of pre-EEP and post-EEP legacy values:
 *   east angle = 0 (aka azimuth = 0)   -> y = 0 so e = atan2(z, x) -> elevation has a range of 2PI so we correctly report pre-EEP values
 *   sunmoonpos = 0 (aka elevation = 0) -> z = 0 so a = atan2(y, x) -> azimuth has a range of 2PI so we correctly report pre-EEP values
 *   -PI/2 < sunmoonpos < PI/2          -> general case             -> post-EEP ranges match pre-EEP ranges so we correctly report pre-EEP values
 *   sunmoonpos > PI/2                  -> elevation went beyond our new maxium so the post-EEP sunmoonpos will actually be off by PI/2 (or 0.25)
 *                                         (and the resulting east angle is off by PI or 0.5 - for example smp 0.375 and ea 0.875 are equivalent with smp 0.125 and ea 0.375)
 *
 * In reverse this means that when setting values through RLVa:
 *   sunmoonpos without eastangle (=0) => always correct
 *   eastangle without sunmoonpos (=0) => always correct
 *   eastangle before sunmoonpos       => always correct
 *   sunmoonpos before eastangle       => correct   for -0.25 <= sunmoonpos <= 0.25
 *                                        incorrect for  0.75  > sunmoonpos  > 0.25
 */
F32 rlvGetAzimuthFromDirectionVector(const LLVector3& vecDir)
{
	if (is_zero(vecDir.mV[VY]))
		return 0.f;
	else if (is_zero(vecDir.mV[VX]))
		return F_PI_BY_TWO;

	F32 radAzimuth = atan2f(vecDir.mV[VY], vecDir.mV[VX]);
	return (radAzimuth >= 0.f) ? radAzimuth : radAzimuth + F_TWO_PI;
}

F32 rlvGetElevationFromDirectionVector(const LLVector3& vecDir)
{
	if (is_zero(vecDir.mV[VZ]))
		return 0.f;

	F32 radElevation;
	if ( (is_zero(vecDir.mV[VX])) && (!is_zero(vecDir.mV[VY])) )
		radElevation = atan2f(vecDir.mV[VZ], vecDir.mV[VY]);
	else if ( (!is_zero(vecDir.mV[VX])) && (is_zero(vecDir.mV[VY])) )
		radElevation = atan2f(vecDir.mV[VZ], vecDir.mV[VX]);
	else
		radElevation = asinf(vecDir.mV[VZ]);
	return (radElevation >= 0.f) ? radElevation : radElevation + F_TWO_PI;
}

// Defined in llsettingssky.cpp
LLQuaternion convert_azimuth_and_altitude_to_quat(F32 azimuth, F32 altitude);

// ================================================================================================
// RlvIsOfSettingsType - Inventory collector for settings of a specific subtype
//

class RlvIsOfSettingsType : public LLInventoryCollectFunctor
{
public:
	RlvIsOfSettingsType(LLSettingsType::type_e eSettingsType, const std::string& strNameMatch = LLStringUtil::null)
		: m_eSettingsType(eSettingsType)
		, m_strNameMatch(strNameMatch)
	{
	}

	~RlvIsOfSettingsType() override
	{
	}

	bool operator()(LLInventoryCategory*, LLInventoryItem* pItem) override
	{
		if ( (pItem) && (LLAssetType::AT_SETTINGS == pItem->getActualType()) )
		{
			return
				(m_eSettingsType == LLSettingsType::fromInventoryFlags(pItem->getFlags())) &&
				( (m_strNameMatch.empty()) || (boost::iequals(pItem->getName(), m_strNameMatch)) );
		}
		return false;
	}

protected:
	LLSettingsType::type_e m_eSettingsType;
	std::string            m_strNameMatch;
};

// ================================================================================================
// RlvEnvironment
//

RlvEnvironment::RlvEnvironment()
{
	//
	// Presets
	//
	registerSetEnvFn<LLUUID>("asset", [](LLEnvironment::EnvSelection_t env, const LLUUID& idAsset)
		{
			if (idAsset.isNull())
				return RLV_RET_FAILED_OPTION;

			LLEnvironment::instance().setEnvironment(env, idAsset);
			return RLV_RET_SUCCESS;
		});
	// Deprecated
	auto fnApplyLibraryPreset = [](LLEnvironment::EnvSelection_t env, const std::string& strPreset, LLSettingsType::type_e eSettingsType)
		{
			LLUUID idAsset(strPreset);
			if (idAsset.isNull())
			{
				const LLUUID& idLibraryEnv = rlvGetLibraryEnvironmentsFolder();
				LLInventoryModel::cat_array_t cats;
				LLInventoryModel::item_array_t items;
				RlvIsOfSettingsType f(eSettingsType, strPreset);
				gInventory.collectDescendentsIf(idLibraryEnv, cats, items, LLInventoryModel::EXCLUDE_TRASH, f);
				if (!items.empty())
					idAsset = items.front()->getAssetUUID();
			}

			if (idAsset.isNull())
				return RLV_RET_FAILED_OPTION;

			LLEnvironment::instance().setEnvironment(env, idAsset);
			return RLV_RET_SUCCESS;
		};
	registerSetEnvFn<std::string>("preset",   [&fnApplyLibraryPreset](LLEnvironment::EnvSelection_t env, const std::string& strPreset) { return fnApplyLibraryPreset(env, strPreset, LLSettingsType::ST_SKY); });
	registerSetEnvFn<std::string>("daycycle", [&fnApplyLibraryPreset](LLEnvironment::EnvSelection_t env, const std::string& strPreset) { return fnApplyLibraryPreset(env, strPreset, LLSettingsType::ST_DAYCYCLE); });

	//
	// Atmosphere & Lighting tab
	//

	// SETTING_AMBIENT
	registerSkyFn<LLColor3>("ambient",		[](LLSettingsSky::ptr_t pSky) { return pSky->getAmbientColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setAmbientColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
	registerLegacySkyFn<LLColor3>("ambient",[](LLSettingsSky::ptr_t pSky) { return pSky->getAmbientColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setAmbientColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });

	// SETTING_BLUE_DENSITY
	registerSkyFn<LLColor3>("bluedensity",	[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueDensity() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueDensity(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
	registerLegacySkyFn<LLColor3>("bluedensity",[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueDensity() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueDensity(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });

	// SETTING_BLUE_HORIZON
	registerSkyFn<LLColor3>("bluehorizon",	[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueHorizon() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueHorizon(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
	registerLegacySkyFn<LLColor3>("bluehorizon",[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueHorizon() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueHorizon(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });

	// SETTING_DENSITY_MULTIPLIER
	registerSkyFn<F32>("densitymultiplier",	[](LLSettingsSky::ptr_t pSky) { return pSky->getDensityMultiplier() / SLIDER_SCALE_DENSITY_MULTIPLIER; },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setDensityMultiplier(nValue * SLIDER_SCALE_DENSITY_MULTIPLIER); });

	// SETTING_DISTANCE_MULTIPLIER
	registerSkyFn<F32>("distancemultiplier",[](LLSettingsSky::ptr_t pSky) { return pSky->getDistanceMultiplier(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setDistanceMultiplier(nValue); });

	// SETTING_SKY_DROPLET_RADIUS
	registerSkyFn<F32>("dropletradius",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyDropletRadius(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyDropletRadius(nValue); });

	// SETTING_HAZE_DENSITY
	registerSkyFn<F32>("hazedensity",		[](LLSettingsSky::ptr_t pSky) { return pSky->getHazeDensity(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setHazeDensity(nValue); });

	// SETTING_HAZE_HORIZON
	registerSkyFn<F32>("hazehorizon",		[](LLSettingsSky::ptr_t pSky) { return pSky->getHazeHorizon(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setHazeHorizon(nValue); });

	// SETTING_SKY_ICE_LEVEL
	registerSkyFn<F32>("icelevel",			[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyIceLevel(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyIceLevel(nValue); });

	// SETTING_MAX_Y
	registerSkyFn<F32>("maxaltitude",		[](LLSettingsSky::ptr_t pSky) { return pSky->getMaxY(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMaxY(nValue); });

	// SETTING_SKY_MOISTURE_LEVEL
	registerSkyFn<F32>("moisturelevel",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyMoistureLevel(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyMoistureLevel(nValue); });

	//	SETTING_GAMMA
	registerSkyFn<F32>("scenegamma",		[](LLSettingsSky::ptr_t pSky) { return pSky->getGamma(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGamma(nValue); });

	//
	// Clouds tab
	//

	// SETTING_CLOUD_COLOR
	registerSkyFn<LLColor3>("cloudcolor",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudColor(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudColor(clrValue); });
	registerLegacySkyFn<LLColor3>("cloudcolor",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudColor(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudColor(clrValue); });

	// SETTING_CLOUD_SHADOW
	registerSkyFn<F32>("cloudcoverage",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudShadow(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudShadow(nValue); });

	// SETTING_CLOUD_POS_DENSITY1
	registerSkyFn<LLColor3>("clouddensity",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity1(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity1(clrValue); });
	registerLegacySkyFn<LLColor3>("cloud",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity1(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity1(clrValue); });

	// SETTING_CLOUD_POS_DENSITY2
	registerSkyFn<LLColor3>("clouddetail",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity2(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity2(clrValue); });
	registerLegacySkyFn<LLColor3>("clouddetail",[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity2(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity2(clrValue); });

	// SETTING_CLOUD_SCALE
	registerSkyFn<F32>("cloudscale",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScale(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudScale(nValue); });

	// SETTING_CLOUD_SCROLL_RATE
	registerSkyFn<LLVector2>("cloudscroll",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScrollRate(); },
											[](LLSettingsSky::ptr_t pSky, const LLVector2& vecValue) { pSky->setCloudScrollRate(vecValue); });
	registerLegacySkyFn<LLVector2>("cloudscroll", [](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScrollRate(); },
											[](LLSettingsSky::ptr_t pSky, const LLVector2& vecValue) { pSky->setCloudScrollRate(vecValue); });

	// SETTING_CLOUD_TEXTUREID
	registerSkyFn<LLUUID>("cloudtexture",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudNoiseTextureId();  },
											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setCloudNoiseTextureId(idTexture);  });

	// SETTING_CLOUD_VARIANCE
	registerSkyFn<F32>("cloudvariance",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudVariance(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudVariance(nValue); });

	//
	// Sun & Moon
	//

	// SETTING_MOON_BRIGHTNESS
	registerSkyFn<F32>("moonbrightness",	[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonBrightness(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMoonBrightness(nValue); });

	// SETTING_MOON_SCALE
	registerSkyFn<F32>("moonscale",			[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonScale(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMoonScale(nValue); });

	// SETTING_MOON_TEXTUREID
	registerSkyFn<LLUUID>("moontexture",	[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonTextureId(); },
											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setMoonTextureId(idTexture); });

	// SETTING_GLOW
	registerSkyFn<float>("sunglowsize",		[](LLSettingsSky::ptr_t pSky) { return 2.0 - (pSky->getGlow().mV[VRED] / SLIDER_SCALE_GLOW_R); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGlow(LLColor3((2.0f - nValue) * SLIDER_SCALE_GLOW_R, .0f, pSky->getGlow().mV[VBLUE])); });
	registerSkyFn<float>("sunglowfocus",	[](LLSettingsSky::ptr_t pSky) { return pSky->getGlow().mV[VBLUE] / SLIDER_SCALE_GLOW_B; },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGlow(LLColor3(pSky->getGlow().mV[VRED], .0f, nValue * SLIDER_SCALE_GLOW_B)); });

	// SETTING_SUNLIGHT_COLOR
	registerSkyFn<LLColor3>("sunlightcolor",[](LLSettingsSky::ptr_t pSky) { return pSky->getSunlightColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setSunlightColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
	registerLegacySkyFn<LLColor3>("sunmooncolor", [](LLSettingsSky::ptr_t pSky) { return pSky->getSunlightColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setSunlightColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });

	// SETTING_SUN_SCALE
	registerSkyFn<float>("sunscale",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSunScale(); },
											[](LLSettingsSky::ptr_t pSky, F32 nValue) { pSky->setSunScale(nValue); });

	// SETTING_SUN_TEXTUREID
	registerSkyFn<LLUUID>("suntexture",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSunTextureId(); },
											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setSunTextureId(idTexture); });

	// SETTING_STAR_BRIGHTNESS
	registerSkyFn<F32>("starbrightness",	[](LLSettingsSky::ptr_t pSky) { return pSky->getStarBrightness(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setStarBrightness(nValue); });

	// SETTING_SUN_ROTATION
	registerSkyFn<F32>("sunazimuth",		[](LLSettingsSky::ptr_t pSky) { return rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()); },
											[](LLSettingsSky::ptr_t pSky, const F32& radAzimuth) {
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, rlvGetElevationFromDirectionVector(LLVector3::x_axis* pSky->getSunRotation())));
											});
	registerSkyFn<F32>("sunelevation",		[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()); },
											[](LLSettingsSky::ptr_t pSky, F32 radElevation) {
												radElevation = llclamp(radElevation, -F_PI_BY_TWO, F_PI_BY_TWO);
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(rlvGetAzimuthFromDirectionVector(LLVector3::x_axis* pSky->getSunRotation()), radElevation));
											});

	// SETTING_MOON_ROTATION
	registerSkyFn<F32>("moonazimuth",		[](LLSettingsSky::ptr_t pSky) { return rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getMoonRotation()); },
											[](LLSettingsSky::ptr_t pSky, const F32& radAzimuth) {
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, rlvGetElevationFromDirectionVector(LLVector3::x_axis* pSky->getMoonRotation())));
											});
	registerSkyFn<F32>("moonelevation",		[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getMoonRotation()); },
											[](LLSettingsSky::ptr_t pSky, F32 radElevation) {
												radElevation = llclamp(radElevation, -F_PI_BY_TWO, F_PI_BY_TWO);
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(rlvGetAzimuthFromDirectionVector(LLVector3::x_axis* pSky->getMoonRotation()), radElevation));
											});

	// Legacy WindLight support (see remarks at the top of this file)
	registerSkyFn<F32>("eastangle",			[](LLSettingsSky::ptr_t pSky) { return normalize_angle_domain(-rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation())) / F_TWO_PI; },
											[](LLSettingsSky::ptr_t pSky, const F32& radEastAngle)
											{
												const F32 radAzimuth = -radEastAngle * F_TWO_PI;
												const F32 radElevation = rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation());
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, radElevation));
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth + F_PI, -radElevation));
											});

	registerSkyFn<F32>("sunmoonposition",	[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()) / F_TWO_PI; },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue)
											{
												const F32 radAzimuth = rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation());
												const F32 radElevation = nValue * F_TWO_PI;
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, radElevation));
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth + F_PI, -radElevation));
											});

	// Create a fixed sky from the nearest daycycle (local > experience > parcel > region)
	registerSetEnvFn<F32>("daytime",		[](LLEnvironment::EnvSelection_t env, const F32& nValue)
											{
												if ((nValue >= 0.f) && (nValue <= 1.0f))
												{
													LLSettingsDay::ptr_t pDay;
													if (LLEnvironment::ENV_EDIT != env)
													{
														LLEnvironment::EnvSelection_t envs[] = { LLEnvironment::ENV_LOCAL, LLEnvironment::ENV_PUSH, LLEnvironment::ENV_PARCEL, LLEnvironment::ENV_REGION };
														for (size_t idxEnv = 0, cntEnv = sizeof(envs) / sizeof(LLEnvironment::EnvSelection_t); idxEnv < cntEnv && !pDay; idxEnv++)
															pDay = LLEnvironment::instance().getEnvironmentDay(envs[idxEnv]);
													}
													else
													{
														pDay = LLEnvironment::instance().getEnvironmentDay(LLEnvironment::ENV_EDIT);
													}

													if (pDay)
													{
														auto pNewSky = LLSettingsVOSky::buildDefaultSky();
														auto pSkyBlender = std::make_shared<LLTrackBlenderLoopingManual>(pNewSky, pDay, 1);
														pSkyBlender->setPosition(nValue);

														LLEnvironment::instance().setEnvironment(env, pNewSky);
														LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
													}
												}
												else if (nValue == -1)
												{
													LLEnvironment::instance().clearEnvironment(env);
													LLEnvironment::instance().setSelectedEnvironment(env);
													LLEnvironment::instance().updateEnvironment();
//													defocusEnvFloaters();
												}
												else
												{
													return RLV_RET_FAILED_OPTION;
												}

												return RLV_RET_SUCCESS;
											});
	registerGetEnvFn("daytime",				[](LLEnvironment::EnvSelection_t env)
											{
												// I forgot how much I hate this command... it literally makes no sense since time of day only has any meaning in an
												// actively animating day cycle (but in that case we have to return -1).
												if (!LLEnvironment::instance().getEnvironmentFixedSky(env)) {
													return std::to_string(-1.f);
												}

												// It's invalid input for @setenv_daytime (see above) so it can be fed in without changing the current environment
												return std::to_string(2.f);
											});
}

RlvEnvironment::~RlvEnvironment()
{
}

// static
LLEnvironment::EnvSelection_t RlvEnvironment::getTargetEnvironment()
{
	return RlvActions::canChangeEnvironment() ? LLEnvironment::ENV_LOCAL : LLEnvironment::ENV_EDIT;
}

// static
LLSettingsSky::ptr_t RlvEnvironment::getTargetSky(bool forSetCmd)
{
	LLEnvironment* pEnv = LLEnvironment::getInstance();

	if (forSetCmd)
	{
		LLEnvironment::EnvSelection_t targetEnv = getTargetEnvironment();
		bool isSharedEnv = !pEnv->getEnvironmentFixedSky(targetEnv),
			 hasLocalDayCycle = !isSharedEnv && pEnv->getEnvironmentDay(targetEnv),
			 isLocalTransition = !hasLocalDayCycle  && pEnv->getCurrentEnvironmentInstance()->isTransition();
		if ( (isSharedEnv) || (hasLocalDayCycle) || (isLocalTransition) )
		{
			LLSettingsSky::ptr_t pSky = (isSharedEnv) ? pEnv->getEnvironmentFixedSky(LLEnvironment::ENV_PARCEL, true)->buildClone()
				                                      : (hasLocalDayCycle) ? pEnv->getEnvironmentFixedSky(targetEnv)->buildClone()
			                                                               : pEnv->getEnvironmentFixedSky(targetEnv);
			pEnv->setEnvironment(targetEnv, pSky);
			pEnv->setSelectedEnvironment(targetEnv, LLEnvironment::TRANSITION_INSTANT);
			pEnv->updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
		}
	}

	return pEnv->getCurrentSky();
}

// static
bool RlvEnvironment::onHandleCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet, const std::string& strCmdPrefix, const handler_map_t& fnLookup, const legacy_handler_map_t& legacyFnLookup)
{
	if ( (rlvCmd.getBehaviour().length() > strCmdPrefix.length() + 2) && (boost::starts_with(rlvCmd.getBehaviour(), strCmdPrefix)) )
	{
		if ( (RLV_TYPE_FORCE == rlvCmd.getParamType()) && (!RlvActions::canChangeEnvironment(rlvCmd.getObjectID())) )
		{
			cmdRet = RLV_RET_FAILED_LOCK;
			return true;
		}

		std::string strEnvCommand = rlvCmd.getBehaviour().substr(strCmdPrefix.length());

		handler_map_t::const_iterator itFnEntry = fnLookup.find(strEnvCommand);
		if (fnLookup.end() != itFnEntry)
		{
			cmdRet = itFnEntry->second((RLV_TYPE_FORCE == rlvCmd.getParamType()) ? rlvCmd.getOption() : rlvCmd.getParam());
			return true;
		}

		// Legacy handling (blargh)
		U32 idxComponent = rlvGetColorComponentFromCharacter(strEnvCommand.back());
		if (idxComponent <= VALPHA)
		{
			strEnvCommand.pop_back();

			legacy_handler_map_t::const_iterator itLegacyFnEntry = legacyFnLookup.find(strEnvCommand);
			if (legacyFnLookup.end() != itLegacyFnEntry)
			{
				cmdRet = itLegacyFnEntry->second((RLV_TYPE_FORCE == rlvCmd.getParamType()) ? rlvCmd.getOption() : rlvCmd.getParam(), idxComponent);
				return true;
			}
		}
	}

	return false;
}

bool RlvEnvironment::onReplyCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
{
	return onHandleCommand(rlvCmd, cmdRet, RLV_GETENV_PREFIX, m_GetFnLookup, m_LegacyGetFnLookup);
}

bool RlvEnvironment::onForceCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
{
	return onHandleCommand(rlvCmd, cmdRet, RLV_SETENV_PREFIX, m_SetFnLookup, m_LegacySetFnLookup);
}

template<>
std::string RlvEnvironment::handleGetFn<float>(const std::function<float(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	return std::to_string(fn(pSky));
}

template<>
std::string RlvEnvironment::handleGetFn<LLUUID>(const std::function<LLUUID(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	return fn(pSky).asString();
}

template<>
std::string RlvEnvironment::handleGetFn<LLVector2>(const std::function<LLVector2(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	LLVector2 replyVec = fn(pSky);
	return llformat("%f/%f", replyVec.mV[VX], replyVec.mV[VY]);
}

template<>
std::string RlvEnvironment::handleGetFn<LLColor3>(const std::function<LLColor3(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	LLColor3 replyColor = fn(pSky);
	return llformat("%f/%f/%f", replyColor.mV[VX], replyColor.mV[VY], replyColor.mV[VZ]);
}

template<typename T>
ERlvCmdRet RlvEnvironment::handleSetFn(const std::string& strRlvOption, const std::function<void(LLSettingsSky::ptr_t, const T&)>& fn)
{
	T optionValue;
	if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
		return RLV_RET_FAILED_PARAM;

	LLSettingsSky::ptr_t pSky = getTargetSky(true);
	fn(pSky, optionValue);
	pSky->update();
	return RLV_RET_SUCCESS;
}

template<>
std::string RlvEnvironment::handleLegacyGetFn<LLVector2>(const std::function<const LLVector2 (LLSettingsSkyPtr_t)>& getFn, U32 idxComponent)
{
	if (idxComponent >= 2)
		return LLStringUtil::null;
	return std::to_string(getFn(getTargetSky()).mV[idxComponent]);
}

template<>
std::string RlvEnvironment::handleLegacyGetFn<LLColor3>(const std::function<const LLColor3 (LLSettingsSkyPtr_t)>& getFn, U32 idxComponent)
{
	if ( (idxComponent >= VRED) && (idxComponent <= VBLUE) )
	{
		return std::to_string(getFn(getTargetSky()).mV[idxComponent]);
	}
	else if (idxComponent == VALPHA)
	{
		const LLColor3& clr = getFn(getTargetSky());
		return std::to_string(llmax(clr.mV[VRED], clr.mV[VGREEN], clr.mV[VBLUE]));
	}
	return LLStringUtil::null;
}

template<>
ERlvCmdRet RlvEnvironment::handleLegacySetFn<LLVector2>(float optionValue, LLVector2 curValue, const std::function<void(LLSettingsSkyPtr_t, const LLVector2&)>& setFn, U32 idxComponent)
{
	if (idxComponent >= 2)
		return RLV_RET_FAILED_UNKNOWN;

	LLSettingsSky::ptr_t pSky = getTargetSky(true);
	curValue.mV[idxComponent] = optionValue;
	setFn(pSky, curValue);
	pSky->update();

	return RLV_RET_SUCCESS;
}

template<>
ERlvCmdRet RlvEnvironment::handleLegacySetFn<LLColor3>(float optionValue, LLColor3 curValue, const std::function<void(LLSettingsSkyPtr_t, const LLColor3&)>& setFn, U32 idxComponent)
{
	LLSettingsSky::ptr_t pSky = getTargetSky(true);
	if ( (idxComponent >= VRED) && (idxComponent <= VBLUE) )
	{
		curValue.mV[idxComponent] = optionValue;
	}
	else if (idxComponent == VALPHA)
	{
		const F32 curMax = llmax(curValue.mV[VRED], curValue.mV[VGREEN], curValue.mV[VBLUE]);
		if ( (0.0f == optionValue) || (0.0f == curMax) )
		{
			curValue.mV[VRED] = curValue.mV[VGREEN] = curValue.mV[VBLUE] = optionValue;
		}
		else
		{
			const F32 nDelta = (optionValue - curMax) / curMax;
			curValue.mV[VRED] *= (1.0f + nDelta);
			curValue.mV[VGREEN] *= (1.0f + nDelta);
			curValue.mV[VBLUE] *= (1.0f + nDelta);
		}
	}
	else
	{
		return RLV_RET_FAILED_UNKNOWN;
	}

	setFn(pSky, curValue);
	pSky->update();

	return RLV_RET_SUCCESS;
}

template<typename T>
void RlvEnvironment::registerSkyFn(const std::string& strFnName, const std::function<T(LLSettingsSkyPtr_t)>& getFn, const std::function<void(LLSettingsSkyPtr_t, const T&)>& setFn)
{
	RLV_ASSERT(m_GetFnLookup.end() == m_GetFnLookup.find(strFnName));
	m_GetFnLookup.insert(std::make_pair(strFnName, [this, getFn](const std::string& strRlvParam)
		{
			if (RlvUtil::sendChatReply(strRlvParam, handleGetFn<T>(getFn)))
				return RLV_RET_SUCCESS;
			return RLV_RET_FAILED_PARAM;
		}));

	RLV_ASSERT(m_SetFnLookup.end() == m_SetFnLookup.find(strFnName));
	m_SetFnLookup.insert(std::make_pair(strFnName, [this, setFn](const std::string& strRlvOption)
		{
			return handleSetFn<T>(strRlvOption, setFn);
		}));
}

void RlvEnvironment::registerGetEnvFn(const std::string& strFnName, const std::function<std::string(LLEnvironment::EnvSelection_t env)>& getFn)
{
	RLV_ASSERT(m_GetFnLookup.end() == m_GetFnLookup.find(strFnName));
	m_GetFnLookup.insert(std::make_pair(strFnName, [getFn](const std::string& strRlvParam)
		{
			if (RlvUtil::sendChatReply(strRlvParam, getFn(getTargetEnvironment())))
				return RLV_RET_SUCCESS;
			return RLV_RET_FAILED_PARAM;
		}));
}

template<typename T>
void RlvEnvironment::registerSetEnvFn(const std::string& strFnName, const std::function<ERlvCmdRet(LLEnvironment::EnvSelection_t env, const T& strRlvOption)>& setFn)
{
	RLV_ASSERT(m_SetFnLookup.end() == m_SetFnLookup.find(strFnName));
	m_SetFnLookup.insert(std::make_pair(strFnName, [setFn](const std::string& strRlvOption)
		{
			T optionValue;
			if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
				return RLV_RET_FAILED_PARAM;
			return setFn(getTargetEnvironment(), optionValue);
		}));
}

template<typename T>
void RlvEnvironment::registerLegacySkyFn(const std::string& strFnName, const std::function<const T (LLSettingsSkyPtr_t)>& getFn, const std::function<void(LLSettingsSkyPtr_t, const T&)>& setFn)
{
	RLV_ASSERT(m_LegacyGetFnLookup.end() == m_LegacyGetFnLookup.find(strFnName));
	m_LegacyGetFnLookup.insert(std::make_pair(strFnName, [this, getFn](const std::string& strRlvParam, U32 idxComponent)
		{
			const std::string strReply = handleLegacyGetFn<T>(getFn, idxComponent);
			if (strReply.empty())
				return RLV_RET_FAILED_UNKNOWN;
			else if (RlvUtil::sendChatReply(strRlvParam, strReply))
				return RLV_RET_SUCCESS;
			return RLV_RET_FAILED_PARAM;
		}));

	RLV_ASSERT(m_LegacySetFnLookup.end() == m_LegacySetFnLookup.find(strFnName));
	m_LegacySetFnLookup.insert(std::make_pair(strFnName, [this, getFn, setFn](const std::string& strRlvOption, U32 idxComponent)
		{
			float optionValue;
			if (!RlvCommandOptionHelper::parseOption(strRlvOption, optionValue))
				return RLV_RET_FAILED_PARAM;
			return handleLegacySetFn<T>(optionValue, getFn(getTargetSky(true)), setFn, idxComponent);;
		}));
}

// ================================================================================================