// exrReaderDeep.cpp

///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2011 The Foundry Visionmongers Ltd.  All Rights Reserved.
// Portions contributed and copyright held by others as indicated.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above
//      copyright notice, this list of conditions and the following
//      disclaimer.
//
//    * Redistributions in binary form must reproduce the above
//      copyright notice, this list of conditions and the following
//      disclaimer in the documentation and/or other materials provided with
//      the distribution.
//
//    * Neither the name of The Foundry Visionmongers nor any other contributors 
//      to this software may be used to endorse or promote products derived from 
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////
#include <OpenEXR/ImfChannelList.h>

#include <OpenEXR/ImfHeader.h>
#include <OpenEXR/ImfMultiPartOutputFile.h>
#include <ImfDeepScanLineOutputFile.h>
#include <OpenEXR/ImfDeepScanLineOutputPart.h>
#include <OpenEXR/ImfDeepScanLineOutputPart.h>
#include <OpenEXR/ImfPartType.h>
#include <OpenEXR/ImfDeepFrameBuffer.h>

#include <OpenEXR/ImfChannelListAttribute.h>
#include <OpenEXR/ImfIntAttribute.h>
#include <OpenEXR/ImfStringAttribute.h>
#include <OpenEXR/ImfTimeCodeAttribute.h>
#include <OpenEXR/ImfStandardAttributes.h>
#include <OpenEXR/ImfMatrixAttribute.h>
#include <OpenEXR/ImfBoxAttribute.h>

#include <OpenEXR/ImfFramesPerSecond.h>
#include <OpenEXR/ImfMisc.h>

#include <Imath/half.h>

#include <stdio.h>
#include <atomic>
#include <map>
#include <set>
#include <memory>
#include <algorithm>

#include "DDImage/DeepWriter.h"
#include "DDImage/DeepOp.h"
#include "DDImage/Executable.h"
#include "DDImage/Thread.h"
#include "DDImage/MetaData.h"

#include "exrGeneral.h"

//#define DEBUG_DEEP_EXR 1

using namespace DD::Image;


static Matrix4 getMatrix(const MetaData::Bundle& metadata,
                         const char* propname)
{
  MetaData::Bundle::PropertyPtr prop = metadata.getData(propname);
  if (!prop)
    return Matrix4::identity();

  return MetaData::getPropertyMatrix(prop);
}

static const int ctypesDeepLength = 3;
static const Imf::Compression ctypesDeep[ctypesDeepLength] = {
  Imf::NO_COMPRESSION,
  Imf::ZIPS_COMPRESSION,
  Imf::RLE_COMPRESSION
};
static const char* const cnamesDeep[ctypesDeepLength + 1] = {
  "none",
  "Zip (1 scanline)",
  "RLE",
  nullptr
};

/**
 * OpenEXR2 writer for deep data.
 *
 * Missing features, to be adapted from the existing EXR writer:
 *  stereo
 */
class exrWriterDeep : public DeepWriter
{
  typedef std::vector<std::vector<const float*> > SamplePtrs ;
  typedef std::vector<std::vector<std::vector <float> > > FloatSamples;
  typedef std::vector<std::vector<std::vector <half> > > HalfSamples ;
  typedef std::vector<unsigned> SampleCounts;

  int _datatype;
  int _compression;
  enum ExrMetaDataMode _metadataMode;
  bool _doNotWriteNukePrefix;
  
  bool writeLines(int y, int t, const Format*, const DD::Image::Box& box, const ChannelSet& channels, Imf::DeepScanLineOutputPart& part, int depth);
  bool fetchLine(int y, int yStart, const DD::Image::Box& box, const ChannelSet& channels, int floatdepth, SampleCounts& sampleCounts, SamplePtrs& samplePtrs, FloatSamples& samples, HalfSamples& halfSamples );

  class RangeLoader {

      exrWriterDeep* _op;
      std::atomic<std::int32_t> _nextY;
      int _t;
      const DD::Image::Box& _box;
      const ChannelSet& _channels;
      int _floatdepth;
      SampleCounts& _sampleCounts;
      SamplePtrs& _samplePtrs;
      FloatSamples& _samples;
      HalfSamples& _halfSamples;

      static void loadRangeThreadFunc(unsigned int threadNum, unsigned int, void * data);
      void loadRange();
  public:
      void run();
      void wait();
      RangeLoader( exrWriterDeep* op, int y, int t, const DD::Image::Box& box, const ChannelSet& channels, int floatdepth,
                     SampleCounts& sampleCounts, SamplePtrs& samplePtrs, FloatSamples& samples, HalfSamples& halfSamples):
        _op(op), _nextY(y), _t(t), _box(box), _channels(channels), _floatdepth(floatdepth),
        _sampleCounts(sampleCounts), _samplePtrs(samplePtrs), _samples(samples), _halfSamples(halfSamples) {}
  };

  bool isDeepChannel(Channel& z) { return z == Chan_DeepFront || z == Chan_DeepBack ; }

public:
  void knobs(Knob_Callback f) override;
  int knob_changed(Knob *k) override;
  exrWriterDeep(DeepWriterOwner* o);
  void execute() override;
 };


exrWriterDeep::exrWriterDeep(DeepWriterOwner* o) : DeepWriter(o), _datatype(0), _compression(1), _metadataMode(eDefaultMetaData), _doNotWriteNukePrefix(false)
{ 
}



void exrWriterDeep::knobs(Knob_Callback f)
{
  Enumeration_knob(f, &_datatype, dnames, "datatype");
  Enumeration_knob(f, &_compression, cnamesDeep, "compression");

  Enumeration_knob(f, (int*)&_metadataMode, metadata_modes, "metadata");
  Tooltip(f, "Which metadata to write out to the EXR file."
             "<p>'no metadata' means that no custom attributes will be created and only metadata that fills required header fields will be written.<p>'default metadata' means that the optional timecode, edgecode, frame rate and exposure header fields will also be filled using metadata values.");

  Bool_knob(f, &_doNotWriteNukePrefix, "noprefix", "do not attach prefix" );
  Tooltip(f, "By default unknown metadata keys have the prefix 'nuke' attached to them before writing them into the file.  Enable this option to write the metadata 'as is' without the nuke prefix.");
}

int exrWriterDeep::knob_changed(Knob *k)
{
  if ( k == &Knob::showPanel || k->is("metadata") ) {
    _owner->op()->knob( "noprefix")->enable( ExrMetaDataMode(_metadataMode) >=  eAllMetadataExceptInput );
    return 1;
  }
  return 0;
}


void exrWriterDeep::RangeLoader::loadRangeThreadFunc(unsigned int threadNum, unsigned int, void * data)
{
  exrWriterDeep::RangeLoader* rangeLoader = static_cast<exrWriterDeep::RangeLoader*> ( data );
  rangeLoader->loadRange();
}

void exrWriterDeep::RangeLoader::run()
{
  int n = Thread::numThreads - 1;
  int h = _nextY - _t;
  if ( h < n)
    n = h;

  // notice that one less parallel thread is launched, as the current thread
  // will also be running.
  if ( n > 0 )
    Thread::spawn(loadRangeThreadFunc, n, this);

  loadRange();
}

void exrWriterDeep::RangeLoader::wait()
{
  Thread::wait(this);
}

void exrWriterDeep::RangeLoader::loadRange()
{
  while (  _nextY >=  _t ) {
    // Atomic decrement and assign
    int thisLine =  _nextY--;
    // Have to retest the condition now in case someone took the line we tested
    // above. Bug 33927.
    if ( thisLine >= _t ) {
      if ( thisLine >=  _box.y() && thisLine <  _box.t()) {
        _op->fetchLine(thisLine,  _t, _box,  _channels,  _floatdepth,  _sampleCounts,  _samplePtrs,  _samples,  _halfSamples );
      }
    }
  }
}

bool exrWriterDeep::fetchLine(int y,
                              int t,
                              const DD::Image::Box& box,
                              const ChannelSet& channels,
                              int floatdepth,
                              SampleCounts& sampleCounts,
                              SamplePtrs& samplePtrs,
                              FloatSamples& samples,
                              HalfSamples& halfSamples )
{
  DD::Image::DeepPlane plane;

  if (!input()->deepEngine(y, box.x(), box.r(), channels, plane)) { // if false then it aborted
    return false;
  }

#ifdef DEBUG_DEEP_EXR
  const ChannelMap& channelMap = plane.channels();
  std::cout << "channel map size " << channelMap.size();
  foreach(z, channels) {
    std::cout << z << " maps to " << channelMap.chanNo(z) << std::endl;
  }

  std::cout << "actual channels " ;
  ChannelSet ac = channelMap;
  foreach(z, ac){
    std::cout << " " << z << std::endl;
  }
#endif

  int batchSize = sampleCounts.size() / box.w();
  int rowStart = ( batchSize - ((y - t)+1) );
  int rowOffset = (rowStart * box.w());

#ifdef DEBUG_DEEP_EXR
  std::cout << "Fetchline " << y << " rowStart " << rowStart << " lines = " << batchSize << std::endl;
#endif

  const size_t chanCount = plane.channels().size();
  // first we create a vector of sample counts, one per pixel
  for (int x = box.x(); x < box.r(); x++)
    sampleCounts[rowOffset + x - box.x()] = plane.getPixel(y, x).getSampleCount();

  // copy the data into the right structure
  int channelIndex = 0;
  if ( floatdepth == 32 ) {
    foreach(z, channels) {
      for (int x = box.x(); x < box.r(); x++) {
        int xOffset = rowOffset + x - box.x();
        int sampleCount = sampleCounts[xOffset];
        samples[channelIndex][xOffset].resize( sampleCount);
        float* writable = &samples[channelIndex][xOffset][0];
        const float* readable = &plane.getPixel(y, x).getUnorderedSample(0, z);

        for ( int s = 0; s < sampleCount; s++ ) {
          *writable++ = *readable;
          readable += chanCount;
        }
        samplePtrs[channelIndex][xOffset] = &samples[channelIndex][xOffset][0];
      }
      channelIndex++;
    }
  } else {
    // if 16 bit we need a copy in half-float format
    // where halfSamples[channel][x][pixel]
    foreach(z, channels) {
      for (int x = box.x(); x < box.r(); x++) {
        int xOffset = rowOffset + x - box.x();

        mFnAssert( channelIndex>=0 && (size_t)channelIndex < samples.size());
        mFnAssert( xOffset>=0 && (size_t)xOffset < samples[channelIndex].size());
        mFnAssert( channelIndex>=0 && (size_t)channelIndex < samplePtrs.size());
        mFnAssert( xOffset>=0 && (size_t)xOffset < samplePtrs[channelIndex].size());

        int sampleCount = sampleCounts[xOffset];
        if ( floatdepth == 32 || isDeepChannel(z) ) {
          if (sampleCount) {
            samples[channelIndex][xOffset].resize(sampleCount);
            float* writable = &samples[channelIndex][xOffset][0];
            const float* readable = &plane.getPixel(y, x).getUnorderedSample(0, z);
            for ( int s = 0; s < sampleCount; s++ ) {
              *writable++ = *readable;
              readable += chanCount;
            }
            samplePtrs[channelIndex][xOffset] = &samples[channelIndex][xOffset][0];
          }
          else {
            samplePtrs[channelIndex][xOffset] = nullptr;
          }
        } else {
          if (sampleCount) {
            halfSamples[channelIndex][xOffset].resize( sampleCount );
            half* writable = &halfSamples[channelIndex][xOffset][0];
            const float* readable = &plane.getPixel(y, x).getUnorderedSample(0, z);
            for ( int s = 0; s < sampleCount; s++ ) {
              *writable++ =  half(*readable);
              readable += chanCount;
            }
            samplePtrs[channelIndex][xOffset] = ( (float*)&halfSamples[channelIndex][xOffset][0] );
          }
          else {
            samplePtrs[channelIndex][xOffset] = nullptr;
          }
        }
      }
      channelIndex++;
    }
  }
  return true;
}

/**
 * Write out a particular line.
 *
 * Returns false if the input was aborted()
 */
bool exrWriterDeep::writeLines(int y, int t, const Format *format, const DD::Image::Box& box, const ChannelSet& channels, Imf::DeepScanLineOutputPart& part, int floatdepth)
{
  int numberOfRows = abs(t - y) + 1;
  assert( numberOfRows != 0 );

#ifdef DEBUG_DEEP_EXR
  std::cout << "Writing " << y << " to " << t << " lines " << numberOfRows << std::endl;
#endif

  SamplePtrs samplePtrs( channels.size() );
  FloatSamples samples( channels.size()  );
  HalfSamples halfSamples( channels.size() );

  SampleCounts sampleCounts( box.w() * numberOfRows );

  int channelIndex = 0;
  foreach(z, channels) {
    samplePtrs[channelIndex].resize( box.w() * numberOfRows );
    samples[channelIndex].resize( box.w() * numberOfRows );
    halfSamples[channelIndex].resize( box.w() * numberOfRows );
    channelIndex++;
  }


  RangeLoader loader (this, y, t, box, channels, floatdepth, sampleCounts, samplePtrs, samples, halfSamples );
  loader.run();
  loader.wait();

  Imf::DeepFrameBuffer frameBuffer;

  int startLine = format->t() - 1 - y;
  int yOffset = startLine * box.w();

  // create and insert the sample count slice
  frameBuffer.insertSampleCountSlice(Imf::Slice(Imf::UINT,
                                                        (char*)(&sampleCounts[0] - box.x()  -  yOffset ),
                                                        sizeof(unsigned int), // xstride
                                                        sizeof(unsigned int) * box.w() ));  // ystride

  channelIndex = 0;
  // create and insert the slices for the actual data
  foreach(z, channels) {
    if ( floatdepth == 32 || isDeepChannel(z) )
      frameBuffer.insert(getExrChannelName(z),
                         Imf::DeepSlice( Imf::FLOAT,
                                                 (char *)(&samplePtrs[channelIndex][0] - box.x() -  yOffset ),
                                                 sizeof(const float*), // xstride
                                                 sizeof(const float*) * box.w(), // ystride
                                                 Imf::pixelTypeSize(Imf::FLOAT) ) );  // samplestride
    else
      frameBuffer.insert(getExrChannelName(z),
                         Imf::DeepSlice( Imf::HALF,
                                                 (char *)(&samplePtrs[channelIndex][0] - box.x() - yOffset ) ,
                                                 sizeof(const half*), // xstride
                                                 sizeof(const half*) * box.w(), // ystride
                                                 Imf::pixelTypeSize(Imf::HALF) ) ); // samplestride

    channelIndex++;
  }

  part.setFrameBuffer(frameBuffer);
  part.writePixels(numberOfRows);

  return true;
}

void exrWriterDeep::execute()
{
  if ( ! input() ) {
    return;
  }

  // unfortunatly because of the way that deep writers work 'store' may not happen on the knobs before
  // execute so we grab the values from the knobs directly.

  _datatype = int(_owner->op()->knob("datatype")->get_value());
  _compression = int(_owner->op()->knob("compression")->get_value());
  _metadataMode = (enum ExrMetaDataMode)(int)_owner->op()->knob("metadata")->get_value();
  _doNotWriteNukePrefix = _owner->op()->knob("noprefix")->get_value() > 0.0;

  input()->validate(true);
  ChannelSet channels = input()->deepInfo().channels();
  const MetaData::Bundle& metadata = _owner->input()->op()->fetchMetaData(nullptr);

  int floatdepth = _datatype ? 32 : 16;

  Imf::Compression compression = ctypesDeep[this->_compression];

  channels &= ( _owner->channels() );
  if (!channels) {
    _owner->op()->critical("exrWriter: No channels selected (or available) for write\n");
    return;
  }

  channels += Mask_Deep; // must write deep channels
  channels += Mask_Alpha; // don't make sense without alpha

  std::vector<Imf::Header> headers(1); // when we do stereo this could be multi-part
  int viewCount = 1;
  
  for (int v = 0; v < viewCount ; v++) {

    // TODO stereo
    // DeepOp * op = dynamic_cast<DeepOp*>( _owner->op()->input(0,v) );
    DeepOp* op = input();
    assert( op );
    
    op->validate(true);
    const DeepInfo& di = op->deepInfo();
    op->deepRequest(di.box(), channels);
    
    DD::Image::Box box = di.box();

    const int formatLine = di.format()->t() - 1;
    
    Imath::Box2i dataBox(Imath::Vec2<int>(box.x(), formatLine - box.t() + 1 ),
                                 Imath::Vec2<int>(box.r() - 1, formatLine - box.y()));
    
    int partNumber = v;
    headers[partNumber].setType(Imf::DEEPSCANLINE);
    headers[partNumber].displayWindow().min.x = 0;
    headers[partNumber].displayWindow().min.y = 0;
    headers[partNumber].displayWindow().max.x = di.format()->width()-1;
    headers[partNumber].displayWindow().max.y = di.format()->height()-1;

    headers[partNumber].dataWindow().min.x = box.x();
    headers[partNumber].dataWindow().min.y = formatLine - box.t() + 1;
    headers[partNumber].dataWindow().max.x = box.r() - 1;
    headers[partNumber].dataWindow().max.y = formatLine - box.y();

    headers[partNumber].compression() = compression;
    headers[partNumber].pixelAspectRatio() = di.format()->pixel_aspect();

    const bool writeFullLayerNames = true;  // We always use full layer names
    metadataToExrHeader( (enum ExrMetaDataMode)_metadataMode, metadata, headers[partNumber], input()->op(), nullptr, _doNotWriteNukePrefix, writeFullLayerNames );

    if ( _metadataMode != eNoMetaData ) {
      // add some specific deep stuff
      Matrix4 Nl = getMatrix(metadata, DD::Image::MetaData::DTEX::DTEX_NL);
      Matrix4 NP = getMatrix(metadata, DD::Image::MetaData::DTEX::DTEX_NP);
      bool haveMetaDataMatrix = !Nl.isIdentity() || !NP.isIdentity();

      if ( ! haveMetaDataMatrix ) {
        Nl = getMatrix(metadata, DD::Image::MetaData::EXR::EXR_WORLD_TO_CAMERA );
        NP = getMatrix(metadata, DD::Image::MetaData::EXR::EXR_WORLD_TO_NDC);
      }

      haveMetaDataMatrix = !Nl.isIdentity() || !NP.isIdentity();

      if ( haveMetaDataMatrix ) {
        float nlVal[4][4];
        float npVal[4][4];
        for ( int row = 0; row < 4; row++ ) {
          for ( int column = 0; column < 4; column++ ) {
            nlVal[column][row] = Nl[column][row];
            npVal[column][row] = NP[column][row];
          }
        }

        headers[partNumber].insert( std::string( DD::Image::MetaData::EXR::EXR_WORLD_TO_CAMERA ).substr(4), Imf::M44fAttribute(Imath::M44f(nlVal)) );
        headers[partNumber].insert( std::string( DD::Image::MetaData::EXR::EXR_WORLD_TO_NDC ).substr(4), Imf::M44fAttribute(Imath::M44f(npVal)) );
      }
    }

    
    foreach(z, channels) {
#ifdef DEBUG_DEEP_EXR
      std::cout << " adding " << z << std::endl;
#endif
      headers[partNumber].channels().insert(getExrChannelName(z), Imf::Channel( floatdepth == 32 || isDeepChannel(z) ? Imf::FLOAT : Imf::HALF ));
    }
#ifdef DEBUG_DEEP_EXR
    std::cout << " done " << std::endl;
#endif
  }
  
  // now actually write out the data
  try { 
    // make file - use all available threads
    Imf::setGlobalThreadCount(Thread::numThreads);
    Imf::MultiPartOutputFile out(_owner->filename(), &headers[0], headers.size(), false, Thread::numThreads);
        
    for (int v = 0; v < viewCount ; v++) {
    
      // TODO stereo
      //DeepOp * op = dynamic_cast<DeepOp*>( _owner->op()->input(0,v) );
      DeepOp* op = input();
      assert( op );
      Imf::DeepScanLineOutputPart part(out, v);
      const DeepInfo& di = op->deepInfo();
      DD::Image::Box box = di.box();
      int batchSize = 64;

      for (int y = (box.t() - 1); y >= box.y(); y -= batchSize) {
#ifdef DEBUG_DEEP_EXR
        std::cout << "write line " << y << std::endl;
#endif
        int t = ( y + 1 ) - batchSize;
        if ( t < box.y() ) {
          t = box.y();
        }

        writeLines( y, t, di.format(), box, channels, part, floatdepth);

        _owner->op()->progressFraction(float(box.t()-y-box.y()) / (box.t()-box.y()));
        if ( _owner->op()->aborted() || _owner->op()->cancelled() ) {
          return;
        }
      }
    }
  }
  catch ( Iex::BaseExc& e ) {
#ifdef DEBUG_DEEP_EXR
    std::cout << e.what() << std::endl;
#endif
    _owner->op()->error( e.what() );
  }
}

static DeepWriter* build(DeepWriterOwner* iop)
{
  return new exrWriterDeep(iop);
}

static const DeepWriter::Description d("exr\0", build);