/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Copyright 2011, Blender Foundation.
 */

class MemoryBuffer;

#ifndef __COM_MEMORYBUFFER_H__
#define __COM_MEMORYBUFFER_H__

#include "COM_ExecutionGroup.h"
#include "COM_MemoryProxy.h"
#include "COM_SocketReader.h"

extern "C" {
#include "BLI_math.h"
#include "BLI_rect.h"
}

/**
 * \brief state of a memory buffer
 * \ingroup Memory
 */
typedef enum MemoryBufferState {
  /** \brief memory has been allocated on creator device and CPU machine,
   * but kernel has not been executed */
  COM_MB_ALLOCATED = 1,
  /** \brief memory is available for use, content has been created */
  COM_MB_AVAILABLE = 2,
  /** \brief chunk is consolidated from other chunks. special state.*/
  COM_MB_TEMPORARILY = 6,
} MemoryBufferState;

typedef enum MemoryBufferExtend {
  COM_MB_CLIP,
  COM_MB_EXTEND,
  COM_MB_REPEAT,
} MemoryBufferExtend;

class MemoryProxy;

/**
 * \brief a MemoryBuffer contains access to the data of a chunk
 */
class MemoryBuffer {
 private:
  /**
   * \brief proxy of the memory (same for all chunks in the same buffer)
   */
  MemoryProxy *m_memoryProxy;

  /**
   * \brief the type of buffer COM_DT_VALUE, COM_DT_VECTOR, COM_DT_COLOR
   */
  DataType m_datatype;

  /**
   * \brief region of this buffer inside relative to the MemoryProxy
   */
  rcti m_rect;

  /**
   * brief refers to the chunk-number within the execution-group where related to the MemoryProxy
   * \see memoryProxy
   */
  unsigned int m_chunkNumber;

  /**
   * \brief state of the buffer
   */
  MemoryBufferState m_state;

  /**
   * \brief the actual float buffer/data
   */
  float *m_buffer;

  /**
   * \brief the number of channels of a single value in the buffer.
   * For value buffers this is 1, vector 3 and color 4
   */
  unsigned int m_num_channels;

  int m_width;
  int m_height;

 public:
  /**
   * \brief construct new MemoryBuffer for a chunk
   */
  MemoryBuffer(MemoryProxy *memoryProxy, unsigned int chunkNumber, rcti *rect);

  /**
   * \brief construct new temporarily MemoryBuffer for an area
   */
  MemoryBuffer(MemoryProxy *memoryProxy, rcti *rect);

  /**
   * \brief construct new temporarily MemoryBuffer for an area
   */
  MemoryBuffer(DataType datatype, rcti *rect);

  /**
   * \brief destructor
   */
  ~MemoryBuffer();

  /**
   * \brief read the ChunkNumber of this MemoryBuffer
   */
  unsigned int getChunkNumber()
  {
    return this->m_chunkNumber;
  }

  unsigned int get_num_channels()
  {
    return this->m_num_channels;
  }

  /**
   * \brief get the data of this MemoryBuffer
   * \note buffer should already be available in memory
   */
  float *getBuffer()
  {
    return this->m_buffer;
  }

  /**
   * \brief after execution the state will be set to available by calling this method
   */
  void setCreatedState()
  {
    this->m_state = COM_MB_AVAILABLE;
  }

  inline void wrap_pixel(int &x, int &y, MemoryBufferExtend extend_x, MemoryBufferExtend extend_y)
  {
    int w = this->m_width;
    int h = this->m_height;
    x = x - m_rect.xmin;
    y = y - m_rect.ymin;

    switch (extend_x) {
      case COM_MB_CLIP:
        break;
      case COM_MB_EXTEND:
        if (x < 0) {
          x = 0;
        }
        if (x >= w) {
          x = w;
        }
        break;
      case COM_MB_REPEAT:
        x = (x >= 0.0f ? (x % w) : (x % w) + w);
        break;
    }

    switch (extend_y) {
      case COM_MB_CLIP:
        break;
      case COM_MB_EXTEND:
        if (y < 0) {
          y = 0;
        }
        if (y >= h) {
          y = h;
        }
        break;
      case COM_MB_REPEAT:
        y = (y >= 0.0f ? (y % h) : (y % h) + h);
        break;
    }
  }

  inline void wrap_pixel(float &x,
                         float &y,
                         MemoryBufferExtend extend_x,
                         MemoryBufferExtend extend_y)
  {
    float w = (float)this->m_width;
    float h = (float)this->m_height;
    x = x - m_rect.xmin;
    y = y - m_rect.ymin;

    switch (extend_x) {
      case COM_MB_CLIP:
        break;
      case COM_MB_EXTEND:
        if (x < 0) {
          x = 0.0f;
        }
        if (x >= w) {
          x = w;
        }
        break;
      case COM_MB_REPEAT:
        x = fmodf(x, w);
        break;
    }

    switch (extend_y) {
      case COM_MB_CLIP:
        break;
      case COM_MB_EXTEND:
        if (y < 0) {
          y = 0.0f;
        }
        if (y >= h) {
          y = h;
        }
        break;
      case COM_MB_REPEAT:
        y = fmodf(y, h);
        break;
    }
  }

  inline void read(float *result,
                   int x,
                   int y,
                   MemoryBufferExtend extend_x = COM_MB_CLIP,
                   MemoryBufferExtend extend_y = COM_MB_CLIP)
  {
    bool clip_x = (extend_x == COM_MB_CLIP && (x < m_rect.xmin || x >= m_rect.xmax));
    bool clip_y = (extend_y == COM_MB_CLIP && (y < m_rect.ymin || y >= m_rect.ymax));
    if (clip_x || clip_y) {
      /* clip result outside rect is zero */
      memset(result, 0, this->m_num_channels * sizeof(float));
    }
    else {
      int u = x;
      int v = y;
      this->wrap_pixel(u, v, extend_x, extend_y);
      const int offset = (this->m_width * y + x) * this->m_num_channels;
      float *buffer = &this->m_buffer[offset];
      memcpy(result, buffer, sizeof(float) * this->m_num_channels);
    }
  }

  inline void readNoCheck(float *result,
                          int x,
                          int y,
                          MemoryBufferExtend extend_x = COM_MB_CLIP,
                          MemoryBufferExtend extend_y = COM_MB_CLIP)
  {
    int u = x;
    int v = y;

    this->wrap_pixel(u, v, extend_x, extend_y);
    const int offset = (this->m_width * v + u) * this->m_num_channels;

    BLI_assert(offset >= 0);
    BLI_assert(offset < this->determineBufferSize() * this->m_num_channels);
    BLI_assert(!(extend_x == COM_MB_CLIP && (u < m_rect.xmin || u >= m_rect.xmax)) &&
               !(extend_y == COM_MB_CLIP && (v < m_rect.ymin || v >= m_rect.ymax)));
#if 0
    /* always true */
    BLI_assert((int)(MEM_allocN_len(this->m_buffer) / sizeof(*this->m_buffer)) ==
               (int)(this->determineBufferSize() * COM_NUMBER_OF_CHANNELS));
#endif
    float *buffer = &this->m_buffer[offset];
    memcpy(result, buffer, sizeof(float) * this->m_num_channels);
  }

  void writePixel(int x, int y, const float color[4]);
  void addPixel(int x, int y, const float color[4]);
  inline void readBilinear(float *result,
                           float x,
                           float y,
                           MemoryBufferExtend extend_x = COM_MB_CLIP,
                           MemoryBufferExtend extend_y = COM_MB_CLIP)
  {
    float u = x;
    float v = y;
    this->wrap_pixel(u, v, extend_x, extend_y);
    if ((extend_x != COM_MB_REPEAT && (u < 0.0f || u >= this->m_width)) ||
        (extend_y != COM_MB_REPEAT && (v < 0.0f || v >= this->m_height))) {
      copy_vn_fl(result, this->m_num_channels, 0.0f);
      return;
    }
    BLI_bilinear_interpolation_wrap_fl(this->m_buffer,
                                       result,
                                       this->m_width,
                                       this->m_height,
                                       this->m_num_channels,
                                       u,
                                       v,
                                       extend_x == COM_MB_REPEAT,
                                       extend_y == COM_MB_REPEAT);
  }

  void readEWA(float *result, const float uv[2], const float derivatives[2][2]);

  /**
   * \brief is this MemoryBuffer a temporarily buffer (based on an area, not on a chunk)
   */
  inline bool isTemporarily() const
  {
    return this->m_state == COM_MB_TEMPORARILY;
  }

  /**
   * \brief add the content from otherBuffer to this MemoryBuffer
   * \param otherBuffer: source buffer
   *
   * \note take care when running this on a new buffer since it wont fill in
   *       uninitialized values in areas where the buffers don't overlap.
   */
  void copyContentFrom(MemoryBuffer *otherBuffer);

  /**
   * \brief get the rect of this MemoryBuffer
   */
  rcti *getRect()
  {
    return &this->m_rect;
  }

  /**
   * \brief get the width of this MemoryBuffer
   */
  int getWidth() const;

  /**
   * \brief get the height of this MemoryBuffer
   */
  int getHeight() const;

  /**
   * \brief clear the buffer. Make all pixels black transparent.
   */
  void clear();

  MemoryBuffer *duplicate();

  float getMaximumValue();
  float getMaximumValue(rcti *rect);

 private:
  unsigned int determineBufferSize();

#ifdef WITH_CXX_GUARDEDALLOC
  MEM_CXX_CLASS_ALLOC_FUNCS("COM:MemoryBuffer")
#endif
};

#endif
