//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef TRANSFERFUNCTION_HPP
#define TRANSFERFUNCTION_HPP

/**
 * \file Transferfunction.hpp
 * This files contains the definition of a transfer function.
 */

#include <Config.hpp>

#include <Color.hpp>

#include <QMap>
#include <QObject>
#include <vector>

namespace mgx 
{
  /**
   * \class TransferFunction TransferFunction.hpp <TransferFunction.hpp>
   *
   * Class defining a transfer function as linear interpolation between set values.
   */
  class mgx_EXPORT TransferFunction 
	{
  public:
    /**
     * Type of interpolation
     */
    enum Interpolation {
      RGB = 0,           ///< Red-Green-Blue
      HSV = 1,           ///< Hue-Saturation-Value
      CYCLIC_HSV = 2     ///< Like HSV but the hue is cyclic (i.e. to go from 0.9 to 0.1 you go via 0)
    };
  
    /// epsilon used in floating point comparisons
    static const double epsilon;
  
    /// Type of a color
    typedef Color<float> Colorf;
  
    /**
     * Type of a list of value/colors
     */
    typedef std::vector<std::pair<double, Colorf> > value_list;
  
    /**
     * Type of the mapping from the position to the index in the value list.
     */
    typedef QMap<double, int> key_map;
  
    /**
     * Default constructor for a transfer function
     * \param interpolation Type of interpolation
     */
    TransferFunction(Interpolation interpolation = RGB);
  
    /**
     * Copy constructor
     */
    TransferFunction(const TransferFunction& copy);
  
    /**
     * Virtual destructor
     */
    virtual ~TransferFunction() {
    }
  
    //@{
    ///\name Predefined transfer functions
    /**
     * Predefined transfer-function, scaling from \c min to \max
     * \param min Color for 0
     * \param max Color for 1
     * \param interpolation Type of interpolation
     */
    static TransferFunction scale(Colorf min, Colorf max, Interpolation interpolation = RGB);
    /**
     * Scale in hue. Varies hue and transparency.
     */
    static TransferFunction hue_scale();
    /**
     * Scale in gray. Varies gray and transparency.
     */
    static TransferFunction scale_gray() {
      return scale(Colorf(0, 0, 0, 0), Colorf(1, 1, 1, 1));
    }
    /**
     * Scale in red. Varies red and transparency.
     */
    static TransferFunction scale_red() {
      return scale(Colorf(0, 0, 0, 0), Colorf(1, 0, 0, 1));
    }
    /**
     * Scale in green. Varies green and transparency.
     */
    static TransferFunction scale_green() {
      return scale(Colorf(0, 0, 0, 0), Colorf(0, 1, 0, 1));
    }
    /**
     * Scale in blue. Varies blue and transparency.
     */
    static TransferFunction scale_blue() {
      return scale(Colorf(0, 0, 0, 0), Colorf(0, 0, 1, 1));
    }
    /**
     * Scale in yellow. Varies yellow and transparency.
     */
    static TransferFunction scale_yellow() {
      return scale(Colorf(0, 0, 0, 0), Colorf(1, 1, 0, 1));
    }
    /**
     * Scale in cyan. Varies cyan and transparency.
     */
    static TransferFunction scale_cyan() {
      return scale(Colorf(0, 0, 0, 0), Colorf(0, 1, 1, 1));
    }
    /**
     * Scale in purple. Varies purple and transparency.
     */
    static TransferFunction scale_purple() {
      return scale(Colorf(0, 0, 0, 0), Colorf(1, 0, 1, 1));
    }
    /**
     * Scale in transparency. Main color is gray.
     */
    static TransferFunction constant_gray() {
      return scale(Colorf(1, 1, 1, 0), Colorf(1, 1, 1, 1));
    }
    /**
     * Scale in transparency. Main color is red.
     */
    static TransferFunction constant_red() {
      return scale(Colorf(1, 0, 0, 0), Colorf(1, 0, 0, 1));
    }
    /**
     * Scale in transparency. Main color is green.
     */
    static TransferFunction constant_green() {
      return scale(Colorf(0, 1, 0, 0), Colorf(0, 1, 0, 1));
    }
    /**
     * Scale in transparency. Main color is blue.
     */
    static TransferFunction constant_blue() {
      return scale(Colorf(0, 0, 1, 0), Colorf(0, 0, 1, 1));
    }
    /**
     * Scale in transparency. Main color is yellow.
     */
    static TransferFunction constant_yellow() {
      return scale(Colorf(1, 1, 0, 0), Colorf(1, 1, 0, 1));
    }
    /**
     * Scale in transparency. Main color is cyan.
     */
    static TransferFunction constant_cyan() {
      return scale(Colorf(0, 1, 1, 0), Colorf(0, 1, 1, 1));
    }
    /**
     * Scale in transparency. Main color is purple.
     */
    static TransferFunction constant_purple() {
      return scale(Colorf(1, 0, 1, 0), Colorf(1, 0, 1, 1));
    }
    /**
     * Jet color scale.
     */
    static TransferFunction jet();
    /**
     * French flag color scale.
     */
    static TransferFunction french_flag();

    /**
     * Blackbody colour scale: black - red - yellow - white
     */
    static TransferFunction blackbody();
    /**
     * A more uniform version of Jet, with smoothly changing luminance and chromaticity
     */
    static TransferFunction uniform_jet();
    /**
     * A helix through colour space, with warm midtones: black - purple - orange - yellow
     */
    static TransferFunction helix_warm();
    /**
     * A helix through colour space, with cool midtones: black - brown - purple - blue
     */
    static TransferFunction helix_cool();
    /**
     * A helix making a full turn through colour space: black - brown - purple - blue - green - yellow
     */
    static TransferFunction helix_full();
    //@}

    static TransferFunction batlow_10();
    static TransferFunction parula();
  
    /**
     * Load a transfer function from a string.
     */
    static TransferFunction load(QString spec);
    /**
     * Store the transfer function into a string
     */
    QString dump() const;
  
    /**
     * Add a point to the interpolation with the color are RGBA
     */
    void add_rgba_point(double pos, Colorf col);
  
    /**
     * Add a point to the interpolation with the color are HSVA
     */
    void add_hsva_point(double pos, Colorf col);
  
    /**
     * Remove the point at the given position (if any)
     */
    void remove_point(double pos);
  
    /**
     * Returns the number of points
     */
    size_t size() const {
      return values.size();
    }
  
    /**
     * Move a point (if found)
     */
    void move_point(double old_pos, double new_pos);
  
    /**
     * Position of the next point, or -1 if none
     */
    double next_pos(double old_pos) const;
  
    /**
     * Position of the previous point, or -1 if none
     */
    double prev_pos(double old_pos) const;
  
    /// Return the i_th point
    double operator[](int i) const;
  
    /**
     * Return the RGBA point at \c position.
     *
     * If the position is not a defined point, it returns Colorf(-1.0f)
     */
    Colorf rgba_point(double position) const;
    /**
     * Return the RGBA point at \c position.
     *
     * If the position is not a defined point, it returns Colorf(-1.0f)
     */
    Colorf hsva_point(double position) const;
  
    /**
     * Returns the color at \c position in RGBA, linearly interpolating between
     * specified points.
     */
    Colorf rgba(double position) const;
    /**
     * Returns the color at \c position in HSVA, linearly interpolating between
     * specified points.
     */
    Colorf hsva(double position) const;
    /**
     * Returns the transparency of the color at \c position, linearly
     * interpolating between specified points.
     */
    double alpha(double position) const;
  
    /**
     * Returns true if no points are defined
     */
    bool empty() const {
      return values.empty();
    }
  
    /**
     * Assignement operator for transfer function
     */
    TransferFunction& operator=(const TransferFunction& other);
  
    /**
     * Test if two transfer functions are equal.
     *
     * They are equal only if they are defined using the same control points, and
     * use the same interpolation and border conditions.
     */
    bool operator==(const TransferFunction& other) const;
    /**
     * Test if two transfer functions are different.
     *
     * They are equal only if they are defined using the same control points, and
     * use the same interpolation and border conditions.
     */
    bool operator!=(const TransferFunction& other) const;
  
    /**
     * Reverse the control point.
     *
     * Each control point is moved to 1-position.
     */
    void reverse();
  
    /**
     * Erase all control points
     */
    void clear();
  
    /**
     * Adjust the control point to span exactly from \c minValue to \c maxValue.
     */
    void adjust(double minValue, double maxValue);
  
    /**
     * Returns the current interpolation mode
     */
    Interpolation interpolation() const {
      return _interpolation;
    }
    /**
     * Change the interpolation mode
     */
    void setInterpolation(Interpolation i);
    /**
     * Change all the control points at once
     */
    void setPointList(const value_list& lst);
  
  protected:
    /**
     * Internal function to interpolate color
     * \param position Target position
     * \param p1 Position of the color before \c position
     * \param col1 Color at position \c p1
     * \param p2 Position of the color after \c position
     * \param col2 Color at position \c p2
     */
    Colorf interpolate(double position, double p1, Colorf col1, double p2, Colorf col2) const;
    /**
     * Update the key map based on the value list
     */
    void update_keys();
    /**
     * Current interpolation mode
     */
    Interpolation _interpolation;
  
    /**
     * Returns the color at position \c position, using a color mode in
     * accordance with the interpolation mode
     */
    Colorf color(double position) const;
  
    /**
     * Add a control point with the color using the current color mode
     * \param pos Position to add the point to
     * \param col Color added
     */
    void add_point(double pos, Colorf col);
  
    /**
     * List of values.
     *
     * If the mode is HSV or CYCLIC_HSV, the colors are stored in HSVA.
     * Otherwise, the colors are stored in RGBA.
     */
    value_list values;
    /**
     * Mapping of the position to the index in the value list.
     */
    key_map keys;
  
    /**
     * If true, the colors left and right of the defined points are clamped.
     * Otherwise, they are set to the exterior color.
     */
    bool clamp;
  
    /**
     * Color left and right of the control points if not clamped.
     */
    Colorf exteriorColor;
  };
}
#endif
