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

#include "DDImage/DeepReader.h"
#include "DDImage/DeepPlane.h"

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

#include "dtex.h"
#include "rx.h"

using namespace DD::Image;

namespace Nuke {
  namespace Deep {

static inline bool IsRGB(Channel channel)
{
  return channel == Chan_Red || channel == Chan_Blue || channel == Chan_Green;
}

static inline bool IsRGBA(Channel channel)
{
  return channel == Chan_Red || channel == Chan_Blue || channel == Chan_Green || channel == Chan_Alpha;
}

  /**
   * Fetch the major version of renderman.  Returns -1 if indeterminable.
   */
  static int RenderManVersion()
  {
    int version[4];
    RxInfoType_t type;
    int count;
    int status = RxRendererInfo("version", &version, 4 * sizeof(int), &type, &count);
    if (type == RxInfoInteger && count == 4 && status == 0)
      return version[1];
    else
      return -1;
  }

class dtexDeepReaderFormat : public DeepReaderFormat
{

  friend class dtexDeepReader;

  bool _raw;
  bool _premult;
  bool _discrete;

public:

  dtexDeepReaderFormat()
  {
    _raw = false;
    _premult = false;
    _discrete = true; // NOTE this is for backwards compatibility, it is knob defaulted to false ( which is probably more correct and matches weta's implementation )
  }

  void knobs(Knob_Callback f)
  {
    Bool_knob(f, &_raw, "raw", "raw values");
    Tooltip(f, "Read the samples exactly 'as is' without processing.");
    Bool_knob(f, &_premult, "premult", "premultiply");
    Tooltip(f, "Premultiply values from file (if off, then it assumes they are already premultiplied)");
    Bool_knob(f, &_discrete, "discrete", "discrete");
    Tooltip(f, "Treat the file as discrete samples with the front and back being the same.  This is only relevant for deep shadow files, color deep composting files are always discrete.");
  }

  void append(Hash& hash)
  {
    hash.append(_raw);
    hash.append(_premult);
    hash.append(_discrete);
  }
};


/**
 * This is a plugin to read the Pixar Renderman 'dtex' files.
 */
class dtexDeepReader : public DeepReader
{
  static Lock sLibraryLock;
  
  DtexFile* _dtexFile;
  DtexCache* _dtexCache;
  Lock _engineLock;
  int _numChans;
  DD::Image::OutputContext _outputContext;
  int _dtexOpenError; // defined in thirdparty/sony/renderman/version/bin/platform/include/dtex.h
  dtexDeepReaderFormat *_dtexReaderFormat;

public:
  dtexDeepReader(DeepReaderOwner* op, const std::string& filename) : DeepReader(op)
  {
    _dtexOpenError = DTEX_NOERR;
    _dtexFile = NULL;
    _dtexCache = NULL;

    if (filename.length())
      open(filename);
    
    if (!_dtexFile) {
      if (filename.length() == 0)
        _op->error("no filename specified");
      else if (_dtexOpenError != DTEX_NOERR)
        _op->error("error (%d) opening file: %s", _dtexOpenError, filename.c_str());
      else
        _op->error("missing file: %s", filename.c_str());

      _deepInfo = DeepInfo();
      return;
    }

    _outputContext = _owner->readerOutputContext();

    DtexImage* image;
    DtexGetImageByIndex(_dtexFile, 0, &image);

    float NP[16];
    DtexNP(image, NP);

    float Nl[16];
    DtexNl(image, Nl);

    _numChans = DtexNumChan(image);

    setInfo(DtexWidth(image), DtexHeight(image), _outputContext, Mask_RGB | Mask_Alpha | Mask_Deep);

    _metaData.setData("dtex/np", NP, 16);
    _metaData.setData("dtex/nl", Nl, 16);

    _dtexReaderFormat = dynamic_cast<dtexDeepReaderFormat*>( op->handler() );
    mFnAssert(_dtexReaderFormat);
  }

  ~dtexDeepReader()
  {
    if (_dtexFile) {
      DtexClose(_dtexFile);
      _dtexFile = NULL;
    }

    if (_dtexCache) {
      DtexDestroyCache(_dtexCache);
      _dtexCache = NULL;
    }
  }

  void open(const std::string& filename)
  {
    _dtexCache = DtexCreateCache(10000, NULL);
    _dtexOpenError = DtexOpenFile(filename.c_str(), "rb", _dtexCache, &_dtexFile);
  }

  void doDeepEngine(DD::Image::Box box, const ChannelSet& channels, DeepOutputPlane& plane)
  {
    static int sRenderManVersion = RenderManVersion();

    // RenderMan 15 seems not to be thread-safe (bug 23831)
    Guard g(sRenderManVersion < 16 ? sLibraryLock : _engineLock);
    
    if (!_dtexFile) {
      if (_dtexOpenError != DTEX_NOERR)
        _op->error("error (%d) opening file", _dtexOpenError);
      else
        _op->error("missing file.");
      return;
    }

    DtexImage* image;
    DtexGetImageByIndex(_dtexFile, 0, &image);
    
    int height = DtexHeight(image);

    DtexPixel* pixel = DtexMakePixel(_numChans);

    plane = DeepOutputPlane(channels, box);

    bool raw = _dtexReaderFormat->_raw;
    bool discrete = _dtexReaderFormat->_premult;
    bool premult = _dtexReaderFormat->_discrete;

    std::map<Channel, int> chanMap;
    if (_numChans == 4) {
      chanMap[Chan_Red] = 0;
      chanMap[Chan_Green] = 1;
      chanMap[Chan_Blue] = 2;
      chanMap[Chan_Alpha] = 3;
    } else {
      chanMap[Chan_Alpha] = 0;
    }

    std::vector<float> pts(_numChans);
    std::vector<float> pts2(_numChans);

    for (Box::iterator it = box.begin(); it != box.end(); it++) {
      float x = it.x;
      float y = it.y;
      _outputContext.from_proxy_xy(x, y);

      DtexGetPixel(image, int(x), height - 1 - int(y), pixel);
      bool isMonochrome = DtexIsPixelMonochrome( pixel );

      int numpts = DtexPixelGetNumPoints(pixel);

      DeepOutPixel pels(numpts * channels.size());

      for (int j = 0; j < numpts; j ++ ) {
          
        float z;
        DtexPixelGetPoint(pixel, j, &z, &pts[0]);
        
        float z2;
        if ( raw || ! isMonochrome ) {
          z2 = z;
        } else {
          if ( DtexPixelGetPoint(pixel, j+1, &z2, &pts2[0]) != DTEX_NOERR )
            continue;
          if ( pts[chanMap[Chan_Alpha]] == pts2[chanMap[Chan_Alpha]] )
            continue;

          if ( discrete )
            z2 = z ;
        }

        float alpha = 1;

        if (chanMap.count(Chan_Alpha)) {
          const int alphaChan = chanMap[Chan_Alpha];

          alpha = ( raw  || ! isMonochrome ) ?
            pts[alphaChan] :
            ((pts[alphaChan] - pts2[alphaChan]) / pts[alphaChan]);
        }

        foreach(chan, channels) {
          if (chan == Chan_Z)
            pels.push_back(1/z);
          else if (chan == Chan_DeepFront )
            pels.push_back(z);
          else if ( chan == Chan_DeepBack ) {
            pels.push_back(z2);
          }
          else if (chan == Chan_Alpha)
            pels.push_back(alpha);
          else if (chanMap.count(chan)) {
            if (premult && IsRGB(chan) && chanMap.count(Chan_Alpha)) {
              pels.push_back(pts[chanMap[chan]] * alpha);
            }
            else {
              pels.push_back( pts[ chanMap[chan] ] );
            }
          }
          else if (IsRGBA(chan))
            pels.push_back(alpha);
          else
            pels.push_back(0);
        }
      }
      plane.addPixel(pels);
    }
    
    DtexDestroyPixel(pixel);
  }
};

static DeepReader* build(DeepReaderOwner* op, const std::string& fn)
{
  return new dtexDeepReader(op, fn);
}

static DeepReaderFormat* buildFormat(DeepReaderOwner* op)
{
  return new dtexDeepReaderFormat();
}

static const DeepReader::Description d("dtex\0deepshad\0dshd\0tex\0", "dtex", build, buildFormat);

    Lock dtexDeepReader::sLibraryLock;

  }
}


