// Mirror.C
// Copyright (c) 2009 The Foundry Visionmongers Ltd.  All Rights Reserved.

// Flips the image around the center of the Format image area.

// Notice that this implements the build_transform() call. This is used
// to flip the transformation over for any handles before this in the
// tree.

#include "DDImage/Iop.h"
#include "DDImage/Row.h"
#include "DDImage/Format.h"
#include "DDImage/ViewerContext.h"

using namespace DD::Image;

static const char* const CLASS = "Mirror";
static const char* const HELP = "Flips the image around the center of the Format image area.";

class Mirror : public Iop
{
  bool horizontal_;
  bool vertical_;
protected:
  void _validate(bool) override;
  void _request(int, int, int, int, ChannelMask, int) override;
  void engine(int y, int x, int r, ChannelMask, Row & row) override;
  void build_handles(ViewerContext*) override;
public:
  Mirror(Node* node) : Iop(node) { horizontal_ = vertical_ = false; }
  void knobs(Knob_Callback) override;
  const char* Class() const override { return CLASS; }
  const char* node_help() const override { return HELP; }

  //! Return hints to control when and how this op will be evaluated by the top-down system.
  OpHints opHints() const override;

  static const Iop::Description d;
};

void Mirror::_validate(bool)
{
  copy_info();

  // this swaps the corners of the bounding box if the image is mirrored,
  // taking care to resize should the bounding box is smaller than the image
  int height = format().height();
  int width  = format().width();
  if (horizontal_) {
    int t = info_.r();
    info_.r(width - info_.x());
    info_.x(width - t);
  }
  if (vertical_) {
    int t = info_.t();
    info_.t(height - info_.y());
    info_.y(height - t);
    info_.ydirection(info_.ydirection() * -1);
  }
}

void Mirror::build_handles(ViewerContext* ctx)
{
  // do the translation only in when the viewer is in 2D and not in 3D mode
  if (!node_disabled() && ctx->viewer_mode() == VIEWER_2D) {
    validate(false);

    // Apply the matrix so handles draw after are correct:
    Matrix4 saved_matrix = ctx->modelmatrix;
    Matrix4& m = ctx->modelmatrix;
    if (horizontal_) {
      m.scale(-1, 1);
      m.translate(-format().width(), 0);
    }
    if (vertical_) {
      m.scale(1, -1);
      m.translate(0, -format().height());
    }
    add_input_handle(0, ctx);
    ctx->modelmatrix = saved_matrix;
  }
  else
    add_input_handle(0, ctx);
}

void Mirror::_request(int x, int y, int r, int t, ChannelMask channels, int count)
{

  int height = format().height();
  int width  = format().width();

  if (horizontal_) {
    int T = r;
    r = width - x;
    x = width - T;
  }
  if (vertical_) {
    int T = t;
    t = height - y;
    y = height - T;
  }

  input0().request(x, y, r, t, channels, count);

}

// if the vertical_ flag is true, write each row of pixels in reverse order to
// mirror about a vertical axis.
// if the horizontal_ flag is true, ask for the height-y row and write it into
// the y row, to mirror the image about a horizontal axis.
void Mirror::engine(int y, int x, int r, ChannelMask channels, Row& row)
{
  int height = format().height();
  int width  = format().width();

  if (vertical_)
    y = height - 1 - y;

  if (horizontal_) {
    Row pixels_in(width - r, width - x);
    pixels_in.get(input0(), y, width - r, width - x, channels);
    foreach(z, channels) {
      if (pixels_in.is_zero(z)) {
        row.erase(z);
      }
      else {
        float* outptr = row.writable(z) + x;
        for (int i = x; i < r; i++) {
          *outptr++ = pixels_in[z][width - 1 - i];
        }
      }
    }
  }
  else {
    row.get(input0(), y, x, r, channels);
  }
}

#include "DDImage/Knobs.h"

void Mirror::knobs(Knob_Callback f)
{
  Bool_knob(f, &horizontal_, "Horizontal");
  Bool_knob(f, &vertical_, "Vertical");
}

OpHints Mirror::opHints() const
{
  return OpHints::eChainable;
}

static Iop* build(Node* node) { return new Mirror(node); }
const Iop::Description Mirror::d(CLASS, "Transform/Mirror", build);