//
// 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 STACK_HPP
#define STACK_HPP

#include <Config.hpp>
#include <GL.hpp>

#include <Geometry.hpp>
#include <MGXViewer/qglviewer.h>
#include <Store.hpp>

namespace mgx 
{
  class SetupProcess;

  /**
   * \class Stack Process.hpp <Process.hpp>
   * The Stack class represent the dimensions of the 3D data, and the frames transformations.
   *
   * The stack contains two stores (i.e. two 3D images): main and work. Typical algorithm will read the current store, and
   * write the result in the work store. If, at the end of the process, the main and work store are not of the size stored
   * in the stack, then both will be erased.
   */
  class mgx_EXPORT Stack {
    friend class SetupProcess;
  
  public:
    /**
     * Create an empty stack with a main and a work store
     */
    Stack(int id);
  
    /**
     * Deep copy of a stack (i.e. the content of main and work store will be copied)
     */
    Stack(const Stack& copy);
  
    /**
     * Delete the stores attached to the stack, but not the meshes
     */
    ~Stack();
  
    /**
     * Id of a stack.
     *
     * This is the same id used in the Process:stack(int) method.
     */
    int id() const {
      return _id;
    }
  
    /**
     * Change the id of the stack.
     *
     * Please do not use this method for stacks attached to a process.
     */
    void setId(int i) {
      _id = i;
    }
  
    /**
     * Id as seen by the user
     *
     * This is, typically id()+1
     */
    int userId() const {
      return _id + 1;
    }
  
    /**
     * Access the main store
     */
    const Store* main() const {
      return _main;
    }
    /**
     * Access the main store
     */
    Store* main() {
      return _main;
    }
    /**
     * Change the main store.
     *
     * \note A process shouldn't use this method. It is more sensible to clear, or resize the existing store.
     */
    void setMain(Store* m);
  
    /**
     * Access the work store
     */
    const Store* work() const {
      return _work;
    }
    /**
     * Access the work store
     */
    Store* work() {
      return _work;
    }
    /**
     * Change the work store.
     *
     * \note A process shouldn't use this method. It is more sensible to clear, or resize the existing store.
     */
    void setWork(Store* w);
  
    /**
     * Returns the current store
     */
    const Store* currentStore() const {
      return _current;
    }
    /**
     * Returns the current store
     */
    Store* currentStore() {
      return _current;
    }
  
    /**
     * Returns the size, in voxels, of the stores
     */
    Point3u size() const {
      return _size;
    }
    /**
     * Returns the bounding box, in voxels, of the stores
     */
    BoundingBox3i boundingBox() const {
      return BoundingBox3i(Point3i(), Point3i(size()));
    }
  
    /**
     * Returns the size, in micro-meters, of a voxel
     */
    Point3f step() const
    {
      return multiply(_step, scale());
    }
    // Point3f step() const { return _step * scale(); }// old
    /**
     * Returns the size, in number of elements, of the stores
     */
    size_t storeSize() const {
      return size_t(_size.x()) * _size.y() * _size.z();
    }
    /**
     * True if the stack is empty (i.e. of zero size)
     */
    bool empty() const {
      return _size == Point3u();
    }
  
    /**
     * Scaling factor to apply, if scaled
     */
    Point3f scale() const {
      return (_showScale ? _scale : Point3f(1.0, 1.0, 1.0));
    }
    // float scale() const { return (_showScale ? _scale : 1.0f); }
    /**
     * Position of the point (0,0,0) of the image, in world coordinate
     */
    Point3f origin() const {
      return multiply(scale(), _origin);
    }
    // Point3f origin() const { return scale()*_origin; }
    /**
     * Set the scaling factor of the stack
     */
    void setScale(Point3f s) {
      _scale = s;
    }
    // void setScale(float s) { _scale = s; }
    /**
     * Set the origin in coordinate of the front-bottom-left corner
     */
    void setOrigin(const Point3f& s);
  
    /**
     * Change the size (in voxel) of the stack.
     *
     * \note This method will resize the two stores, keeping the data when possible.
     */
    void setSize(const Point3u& s);
    /**
     * Change the dimensions of a voxel.
     */
    void setStep(const Point3f& s);
  
    /**
     * True if the stack is scaled
     */
    bool showScale() const {
      return _showScale;
    }
    /**
     * Set if the stack should be scaled
     */
    void setShowScale(bool s) {
      _showScale = s;
    }
  
    /**
     * True if the 3 scaling axis are tied together
     */
    bool tieScales() const {
      return _tieScales;
    }
    /**
     * Set if the scales should be tied
     */
    void setTieScales(bool s) {
      _tieScales = s;
    }
  
    /**
     * True if the stack is shown transformed
     */
    bool showTrans() const {
      return _showTrans;
    }
    /**
     * Set if the stack should be transformed
     */
    void setShowTrans(bool s) {
      _showTrans = s;
    }
    /**
     * True if the Bounding Box of the stack is shown
     */
    bool showBBox() const {
      return _showBBox;
    }
    /**
     * Set if the bounding box of the stack should be visible
     */
    void setShowBBox(bool s) {
      _showBBox = s;
    }
  
    /// Matrix transforming a world point to an image one
    Matrix4f worldToImage() const;
  
    /// Matrix transforming an image point to a world one
    Matrix4f imageToWorld() const;
  
    /// Matrix transforming a world vector to an image one
    Matrix4f worldToImageVector() const;
  
    /// Matrix transforming an image vector to a world one
    Matrix4f imageToWorldVector() const;
  
    /// Go from image coordinates to world coordinates (for a point)
    template <typename T> Point3f imageToWorld(const Vector<3, T>& img) const
    {
      return multiply(Point3f(img) + Point3f(.5f, .5f, .5f), step()) + origin();
    }
  
    /// Go from world coordinates to image coordinates (for a point)
    template <typename T> Vector<3, T> worldToImage(Point3f wrld) const
    {
      return Vector<3, T>((wrld - origin()) / step() - Point3f(.5f, .5f, .5f));
    }
  
    /// Go from image coordinates to world coordinates (for a vector)
    template <typename T> Point3f imageToWorldVector(const Vector<3, T>& img) const
    {
      return multiply(Point3f(img), step());
    }
  
    /// Go from world coordinates to image coordinates (for a vector)
    template <typename T> Vector<3, T> worldToImageVector(Point3f wrld) const
    {
      return Vector<3, T>(wrld / step());
    }
  
    /// Go from image coordinates to world coordinates (for a point)
    template <typename T> BoundingBox3f imageToWorld(const BoundingBox<3, T>& img) const
    {
      return BoundingBox3f(imageToWorld(img.pmin()), imageToWorld(img.pmax()));
    }
  
    /// Go from world coordinates to image coordinates (for a point)
    template <typename T> BoundingBox<3, T> worldToImage(const BoundingBox3f& wrld) const
    {
      return BoundingBox<3, T>(worldToImage<T>(wrld.pmin()), worldToImage<T>(wrld.pmax()));
    }
  
    Point3f worldToImagef(const Point3f& a) const {
      return worldToImage<float>(a);
    }
    Point3i worldToImagei(const Point3f& a) const {
      return worldToImage<int>(a);
    }
    Point3u worldToImageu(const Point3f& a) const {
      return worldToImage<uint>(a);
    }
  
    BoundingBox3f worldToImagef(const BoundingBox3f& a) const {
      return worldToImage<float>(a);
    }
    BoundingBox3i worldToImagei(const BoundingBox3f& a) const {
      return worldToImage<int>(a);
    }
    BoundingBox3u worldToImageu(const BoundingBox3f& a) const {
      return worldToImage<uint>(a);
    }
  
    Point3f worldToImageVectorf(const Point3f& a) const {
      return worldToImageVector<float>(a);
    }
    Point3i worldToImageVectori(const Point3f& a) const {
      return worldToImageVector<int>(a);
    }
    Point3u worldToImageVectoru(const Point3f& a) const {
      return worldToImageVector<uint>(a);
    }
  
    /**
     * Go from abstract unit to world unit.
     *
     * Abstract unit is a reference system in which the stack maximal dimensions are [-0.5,0.5]
     */
    Point3f abstractToWorld(const Point3f& p) const
    {
      return divide(p, scale()) * _texScale;
    }
  
    /**
     * Size of the image, in world coordinate
     */
    Point3f worldSize() const {
      return imageToWorldVector(_size);
    }
  
    /// Check if (x,y,z) is in the image
    bool boundsOK(int x, int y, int z) const
    {
      if(x < 0 or y < 0 or z < 0 or x >= int(_size.x()) or y >= int(_size.y()) or z >= int(_size.z()))
        return false;
      else
        return true;
    }
  
    /// Compute offset for image data
    size_t offset(uint x, uint y, uint z) const {
      return (size_t(z) * _size.y() + y) * _size.x() + x;
    }
  
    /**
     * Returns the position, in the image, of the point of image coordinate \c ipos.
     */
    size_t offset(Point3i ipos) const {
      return (size_t(ipos.z()) * _size.y() + ipos.y()) * _size.x() + ipos.x();
    }
  
    /// Compute the position from the offset
    Point3u position(size_t offset) const
    {
      uint x = offset % _size.x();
      offset = (offset - x) / _size.x();
      uint y = offset % _size.y();
      uint z = (offset - y) / _size.y();
      return Point3u(x, y, z);
    }
  
    /**
     * Shift the image so it is centered
     */
    void center();
  
    /**
     * Returns the manipulated frame
     */
    const qglviewer::ManipulatedFrame& frame() const {
      return _frame;
    }
  
    /**
     * Returns the manipulated frame
     */
    qglviewer::ManipulatedFrame& frame() {
      return _frame;
    }
  
    /**
     * Returns the transformation frame
     */
    const qglviewer::ManipulatedFrame& trans() const {
      return _trans;
    }
    /**
     * Returns the transformation frame
     */
    qglviewer::ManipulatedFrame& trans() {
      return _trans;
    }
  
    /**
     * Returns the active frame (i.e. frame() or trans()).
     *
     * It will be the manipulated frame or the transformation frame, depending on which is used (i.e. is showTrans()
     * true or false)
     */
    qglviewer::ManipulatedFrame& getFrame() {
      return (_showTrans ? _trans : _frame);
    }
  
    /**
     * Returns the active frame (i.e. frame() or trans()).
     *
     * It will be the manipulated frame or the transformation frame, depending on which is used (i.e. is showTrans()
     * true or false)
     */
    const qglviewer::ManipulatedFrame& getFrame() const {
      return (_showTrans ? _trans : _frame);
    }
  
    /**
     * Return the label to be used
     */
    int viewLabel() const {
      return _CurrLabel;
    }
    /**
     * Change the label to be used
     */
    void setLabel(int l)
    {
      _CurrLabel = l;
    }
    /**
     * Return the next label to be used and change it so successive calls return successive labels.
     */
    int nextLabel()
    {
      _CurrLabel++;
      if(_CurrLabel > ((1 << 16) - 1))
        _CurrLabel = 1;
      return _CurrLabel;
    }

    void setMainAsCurrent() {
      _current = _main;
    }
    void setWorkAsCurrent() {
      _current = _work;
    }
    void setNoneAsCurrent() {
      _current = 0;
    }
  
    void setCurrentStore(Store* store) {
      _current = (store == _main or store == _work) ? store : 0;
    }
  
    /**
     * Erase everything
     */
    void reset();
  
  private:
    void resetModified();
    void updateSizes();
    void resetCurrent();
    Store* _main, *_work, *_current;
    Point3f _scale;
    // float _scale;
    Point3f _origin;
    float _texScale;
    Point3u _size;
    Point3f _step;
    qglviewer::ManipulatedFrame _frame;
    qglviewer::ManipulatedFrame _trans;
    bool changed_frame, changed_trans;
    int _id;
    int _CurrLabel;
    bool _showScale, _showTrans, _showBBox, _tieScales;
  };
}

#endif
