// Copyright (c) 2011 The Foundry Visionmongers Ltd.  All Rights Reserved.

#include "DDImage/DeepFilterOp.h"
#include "DDImage/Knobs.h"

static const char* CLASS = "DeepCrop";

using namespace DD::Image;

class DeepCrop : public DeepFilterOp
{

  float _zrange[2];
  bool _useZMin;
  bool _useZMax;
  bool _outsideZRange;

  float _bbox[4];
  bool _useBBox;
  bool _outsideBBox;
    
public:
  DeepCrop(Node* node) : DeepFilterOp(node) {
    _zrange[0] = 1;
    _zrange[1] = 2;

    _bbox[0] = _bbox[2] = input_format().width();
    _bbox[1] = _bbox[3] = input_format().height();

    _bbox[0] *= 0.2;
    _bbox[1] *= 0.2;

    _bbox[2] *= 0.8;
    _bbox[3] *= 0.8;

    _useZMin = true;
    _useZMax = true;
    _useBBox = true;

    _outsideZRange = false;
    _outsideBBox = false;
  }

  const char* node_help() const
  {
    return "Crop deep data in front of or behind certain planes, or inside or outside of a box.";
  }

  const char* Class() const {
    return CLASS;
  }

  virtual Op* op()
  {
    return this;
  }

  void knobs(Knob_Callback f)
  {
    Float_knob(f, &_zrange[0], "znear");
    SetRange(f, 0, 1);
    Tooltip(f, "The near deep value");

    Bool_knob(f, &_useZMin, "use_znear", "use");
    Tooltip(f, "Whether to use the near deep value");

    Float_knob(f, &_zrange[1], "zfar");
    SetRange(f, 0, 1);
    Tooltip(f, "The far deep value");

    Bool_knob(f, &_useZMax, "use_zfar", "use");
    Tooltip(f, "Whether to use the far deep value");

    Bool_knob(f, &_outsideZRange, "outside_zrange", "keep outside zrange");
    Tooltip(f, "Whether to keep the samples with deep between the range (false), or outside the range (true)");

    BBox_knob(f, &_bbox[0], "bbox");
    SetFlags(f, Knob::ALWAYS_SAVE);
    Tooltip(f, "2D bounding box for clipping");

    Bool_knob(f, &_useBBox, "use_bbox", "use");
    Tooltip(f, "Whether to use the 2D bounding box");

    Bool_knob(f, &_outsideBBox, "outside_bbox", "keep outside bbox");
    Tooltip(f, "Whether to keep samples within the 2D bounding box (false), or outside it (true)");
  }

  int knob_changed(DD::Image::Knob* k)
  {
    knob("znear")->enable(_useZMin);
    knob("zfar")->enable(_useZMax);
    knob("bbox")->enable(_useBBox);
    return 1;
  }

  void _validate(bool for_real)
  {
    DeepFilterOp::_validate(for_real);
    if (_useBBox && !_outsideBBox) {
      _deepInfo.box().set(floor(_bbox[0] - 1), floor(_bbox[1] - 1), ceil(_bbox[2] + 1), ceil(_bbox[3] + 1));
    }
  }

  bool doDeepEngine(DD::Image::Box box, const ChannelSet& channels, DeepOutputPlane& plane)
  {
    if (!input0())
      return true;

    DeepOp* in = input0();
    DeepPlane inPlane;
    
    ChannelSet needed = channels;
    needed += Mask_DeepFront;

    if (!in->deepEngine(box, needed, inPlane))
      return false;
    
    plane = DeepOutputPlane(channels, box);

    for (DD::Image::Box::iterator it = box.begin(); it != box.end(); it++) {

      const int x = it.x;
      const int y = it.y;
      
      bool inXY = (x >= _bbox[0] && x < _bbox[2] && y >= _bbox[1] && y < _bbox[3]);
    
      if (_useBBox && inXY == _outsideBBox) {
        plane.addHole();
        continue;
      }

      DeepPixel pixel = inPlane.getPixel(it);

      DeepOutPixel pels(pixel.getSampleCount() * channels.size());

      for (size_t sample = 0; sample < pixel.getSampleCount(); sample++) {

        float z = pixel.getUnorderedSample(sample, DD::Image::Chan_DeepFront);

        bool inZ = (!_useZMax || z <= _zrange[1]) && (!_useZMin || z >= _zrange[0]);

        if ((_useZMin || _useZMax) && inZ == _outsideZRange)
          continue;

        foreach (channel, channels) {
          pels.push_back(pixel.getUnorderedSample(sample, channel));
        }
      }
        
      plane.addPixel(pels);
    }

    return true;
  }

  static const Description d;
  static const Description d2;
  static const Description d3;
};

static Op* build(Node* node) { return new DeepCrop(node); }
const Op::Description DeepCrop::d(::CLASS, "Image/DeepCrop", build);
const Op::Description DeepCrop::d2("DeepMask", "Image/DeepCrop", build);
const Op::Description DeepCrop::d3("DeepClip", "Image/DeepCrop", build);
