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

#include <Config.hpp>

#include <Geometry.hpp>
#include <thrust/host_vector.h>
#include <TransferFunction.hpp>

namespace mgx 
{
  class SetupProcess;
  
  typedef unsigned short ushort;
  typedef thrust::host_vector<ushort> HVecUS;
  
  class Stack;
  /**
   * \class Store Store.hpp <Store.hpp>
   *
   * The Store class holds the actual 3D data and properties specific to it.
   */
  class mgx_EXPORT Store 
  {
    friend class SetupProcess;
  
  public:
    /**
     * Create an empty store
     */
    Store(Stack* stack);
  
    /**
     * Copy the store, and attach it to the same stack
     */
    Store(const Store& copy);
  
    /**
     * Delete the data attached to the store
     */
    ~Store();
  
    /**
     * Actual 3D data store linearly in a host vector.
     *
     * The data is always store as an unsigned 16 bits integer in a host vector (i.e. from the thrust library)
     */
    HVecUS& data() { return _data; }

    /**
     * Actual 3D data store linearly in a host vector.
     *
     * The data is always store as an unsigned 16 bits integer in a host vector (i.e. from the thrust library)
     */
    const HVecUS& data() const { return _data; }

    /**
     * Returns true if the data is to be interpreted as labels rather than intensities
     */
    bool labels() const { return _label; }

    /**
     * Change the interpretation of the volume as labels
     */
    void setLabels(bool val) { _label = val; }

    /**
     * Opacity used to render the volume
     */
    float opacity() const { return _opacity; }

    /**
     * Changed the opacity of the volume
     */
    void setOpacity(float f)
    {
      if(f < 0)
        _opacity = 0;
      else if(f > 1)
        _opacity = 1;
      else
        _opacity = f;
    }

    /**
     * Global brightness used to render the volume
     */
    float brightness() const { return _brightness; }

    /**
     * Change the brightness of the volume
     */
    void setBrightness(float f)
    {
      if(f < 0)
        _brightness = 0;
      else if(f > 1)
        _brightness = 1;
      else
        _brightness = f;
    }
  
    /**
     * Retrieve the transfer function used to render the volume
     */
    TransferFunction transferFct() const { return _fct; }

    /**
     * Change the transfer function used to render the volume
     */
    void setTransferFct(const TransferFunction& f)
    {
      if(_fct != f) {
        _fct = f;
        changed_function = true;
      }
    }
  
    /**
     * Returns true if the transfer function has been changed within this process
     */
    bool transferFunctionChanged() const { return changed_function; }
  
    /**
     * A process that changes the 3D data needs to call this method.
     *
     * This indicate the GUI that the data changed and need reloading in the graphics card.
     */
    void changed();
  
    /**
     * A process that changed a range in the 3D data needs to call this method.
     *
     * This indicate the GUI that the data changed in this range and need reloading. Note that many calls to this will
     * assume the union of the bounding boxes have changed.
     */
    void changed(const BoundingBox3i& bbox) { _changed |= bbox; }
  
    /**
     * Returns true if the 3D data has been changed during this process.
     */
    bool wasChanged() const { return _changed; }
  
    /**
     * Returns the current bounding box for the changes
     */
    const BoundingBox3i& changedBBox() const { return _changed; }
  
    /**
     * Ask the user interface to show this store
     */
    void show() { _isVisible = true; }

    /**
     * Ask the user interface to hide this store
     */
    void hide() { _isVisible = false; }

    /**
     * Is the store currently visible
     */
    bool isVisible() const { return _isVisible; }
  
    /**
     * Returns a constant pointer on the stack holding this store
     */
    const Stack* stack() const { return _stack; }
  
    /**
     * Change the stack this store is attached to
     */
    void setStack(Stack* s);
  
    /**
     * Makes sure the memory for the store is allocated.
     *
     * \note before the process starts, the memory is already allocated. It is useful only if you change the size of the
     * data by hand.
     */
    void allocate();

    /**
     * Reset the memory of the store.
     *
     * In the end, the store has the correct size and is filled with zeros.
     */
    void reset();
  
    /**
     * Returns the file corresponding to this store.
     */
    const QString& file() const { return _filename; }

    /**
     * Set which file corresponds to this store.
     */
    void setFile(const QString& f = QString());
  
    /**
     * Returns the size (in number of elements) of the store
     */
    uint size() const { return _data.size(); }

    /**
     * True if the store is of size 0
     */
    bool empty() const { return _data.empty(); }
  
    /**
     * Copy the metadata from another store
     *
     * In the end, the states will be the same (currently only labels and file name)
     */
    void copyMetaData(const Store* other);
  
  protected:
    void resetModified();
  
    HVecUS _data;
    bool _label;
    bool changed_function;
    float _opacity;
    float _brightness;
    BoundingBox3i _changed;
    TransferFunction _fct;
    bool _isVisible;
    QString _filename;
    const Stack* _stack;
  };
  
  void mgx_EXPORT swapMetaData(Store* s1, Store* s2);
}
#endif
