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

#include "DDImage/DeepOp.h"
#include "DDImage/Knobs.h"
#include "DDImage/Thread.h"
#include "DDImage/Iop.h"
#include "DDImage/Row.h"

static const char* CLASS = "DeepMerge";

using namespace DD::Image;

class DeepMerge : public DeepOnlyOp
{
  bool _dropHidden;

public:
  DeepMerge(Node* node) : DeepOnlyOp(node)
  {
    _dropHidden = false;
  }


  virtual int minimum_inputs() const
  {
    return 1;
  }

  virtual Op* op()
  {
    return this;
  }

  virtual int maximum_inputs() const {
    return 999;
  }

  virtual bool test_input(int idx, Op*op) const {
    return dynamic_cast<DeepOp*>(op);
  }

  DeepOp* dinput(int n)
  {
    return dynamic_cast<DeepOp*>(Op::input(n));
  }

  const char* node_help() const
  {
    return "Merges two or more deep data inputs to produce a composite deep data stream.";
  }

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

  void knobs(Knob_Callback f)
  {
    Bool_knob(f, &_dropHidden, "drop_hidden", "drop hidden samples");
    Tooltip(f, "Drop samples that are behind other samples with alpha 1 (ie those that are entirely obscured)");
  }

  //! get the extra channels needed apart from those being passed through.
  DD::Image::ChannelSet extraChans() const
  {
    //! If _dropHidden is on we need depth and alpha

    if (_dropHidden)
      return Mask_DeepFront | Mask_Alpha;

    return Mask_None;
  }

  void _validate(bool real)
  {
    bool any = false;

    for (int i = 0; i < inputs(); i++) {
      if (DeepOp* dop = dinput(i)) {
        dop->validate(real);
        
        if (!any) {
          _deepInfo = dop->deepInfo();
          any = true;
        } else {
          _deepInfo.merge(dop->deepInfo());
        }
      }
    }
  }

  void getDeepRequests(DD::Image::Box box, const DD::Image::ChannelSet& channels, int count, std::vector<RequestData>& requests)
  {
    DD::Image::ChannelSet reqChans = channels + extraChans();

    for (int i = 0; i < inputs(); i++) {
      if (dinput(i)) {
        requests.push_back(RequestData(dinput(i), box, reqChans, count));
      }
    }
  }

  bool doDeepEngine(DD::Image::Box box, const ChannelSet& channels, DeepOutputPlane& plane)
  {
    plane = DeepOutputPlane(channels, box);

    const int numChans = channels.size();
    
    bool done = true;

    std::vector<DeepPlane> inPlanes(inputs());
    
    DD::Image::ChannelSet getChans = channels + extraChans();

    for (int i = 0; i < inputs(); i++) {
      if (dinput(i)) {
        if (!dinput(i)->deepEngine(box, getChans, inPlanes[i]))
          return false;
      }
    }

    for (DD::Image::Box::iterator it = box.begin(); it != box.end(); it++) {
      
      if (aborted()) {
        return false;
      }
      
      DeepOutPixel pixelData;
      
      pixelData.clear();

      float firstSolid = FLT_MAX;

      if (_dropHidden) {
        for (int i = 0; i < inputs(); i++)
          if (dinput(i)) {
            DeepPixel deepPixel = inPlanes[i].getPixel(it);
            for (size_t j = 0; j < deepPixel.getSampleCount(); j++)
              if (deepPixel.getUnorderedSample(j, Chan_Alpha) >= 1)
                firstSolid = std::min(firstSolid, deepPixel.getUnorderedSample(j, Chan_DeepFront));
          }
      }
      
      for (int i = 0; i < inputs(); i++) {
        if (dinput(i)) {
          DeepPixel deepPixel = inPlanes[i].getPixel(it);
          
          pixelData.reserveMore(deepPixel.getSampleCount() * numChans);
          
          for (size_t j = 0; j < deepPixel.getSampleCount(); j++) {
            if (_dropHidden) {
              float front = deepPixel.getUnorderedSample(j, Chan_DeepFront);
              if (front > firstSolid)
                continue;
            }

            foreach (z, channels) {
              pixelData.push_back(deepPixel.getUnorderedSample(j, z));
            }
          }
        }
      }
      
      plane.addPixel(pixelData);
    }

    return done;
  }

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

static Op* build(Node* node) { return new DeepMerge(node); }

const Op::Description DeepMerge::d(::CLASS, "Image/DeepMerge", build);
const Op::Description DeepMerge::d2("ToDeep", "Image/DeepMerge", build);

