#include <sstream> /* std::ostringstream */
/* Not use boost at toonz-6.1 */
// #include <boost/shared_array.hpp> /* boost::shared_array<> */
#include "tfxparam.h"
#include "stdfx.h"

#include "ino_common.h"
//------------------------------------------------------------
class ino_channel_selector final : public TStandardRasterFx {
  FX_PLUGIN_DECLARATION(ino_channel_selector)

  TRasterFxPort m_source1;
  TRasterFxPort m_source2;
  TRasterFxPort m_source3;
  TRasterFxPort m_source4;

  TIntParamP m_red_source;
  TIntParamP m_gre_source;
  TIntParamP m_blu_source;
  TIntParamP m_alp_source;

  TIntEnumParamP m_red_channel;
  TIntEnumParamP m_gre_channel;
  TIntEnumParamP m_blu_channel;
  TIntEnumParamP m_alp_channel;

public:
  ino_channel_selector()
      : m_red_source(1)
      , m_gre_source(1)
      , m_blu_source(1)
      , m_alp_source(1)

      , m_red_channel(new TIntEnumParam(0, "Red"))
      , m_gre_channel(new TIntEnumParam(1, "Green"))
      , m_blu_channel(new TIntEnumParam(2, "Blue"))
      , m_alp_channel(new TIntEnumParam(3, "Alpha")) {
    addInputPort("Source1", this->m_source1);
    addInputPort("Source2", this->m_source2);
    addInputPort("Source3", this->m_source3);
    addInputPort("Source4", this->m_source4);

    bindParam(this, "red_source", this->m_red_source);
    bindParam(this, "green_source", this->m_gre_source);
    bindParam(this, "blue_source", this->m_blu_source);
    bindParam(this, "alpha_source", this->m_alp_source);

    bindParam(this, "red_channel", this->m_red_channel);
    bindParam(this, "green_channel", this->m_gre_channel);
    bindParam(this, "blue_channel", this->m_blu_channel);
    bindParam(this, "alpha_channel", this->m_alp_channel);

    this->m_red_channel->addItem(1, "Green");
    this->m_red_channel->addItem(2, "Blue");
    this->m_red_channel->addItem(3, "Alpha");

    this->m_gre_channel->addItem(0, "Red");
    this->m_gre_channel->addItem(2, "Blue");
    this->m_gre_channel->addItem(3, "Alpha");

    this->m_blu_channel->addItem(0, "Red");
    this->m_blu_channel->addItem(1, "Green");
    this->m_blu_channel->addItem(3, "Alpha");

    this->m_alp_channel->addItem(0, "Red");
    this->m_alp_channel->addItem(1, "Green");
    this->m_alp_channel->addItem(2, "Blue");
  }
  bool doGetBBox(double frame, TRectD &bBox,
                 const TRenderSettings &info) override {
    for (int ii = 0; ii < this->getInputPortCount(); ++ii) {
      std::string nm          = this->getInputPortName(ii);
      TRasterFxPort *tmp_port = (TRasterFxPort *)this->getInputPort(nm);
      if (tmp_port->isConnected()) {
        return (*tmp_port)->doGetBBox(frame, bBox, info);
      }
    }
    bBox = TRectD();
    return false;
  }
  bool canHandle(const TRenderSettings &info, double frame) override {
    return true;
  }
  void doCompute(TTile &tile, double frame, const TRenderSettings &ri) override;
};
FX_PLUGIN_IDENTIFIER(ino_channel_selector, "inoChannelSelectorFx");
//--------------------------------------------------------------------
// #include "igs_channel_selector.h"
namespace {
template <typename IN_PIXEL, typename OUT_PIXEL>
void fx_template(const TRasterPT<IN_PIXEL> in_ras, const int in_sel,
                 const TRasterPT<OUT_PIXEL> out_ras, const int out_sel) {
  for (int yy = 0; yy < out_ras->getLy(); ++yy) {
    IN_PIXEL *sl_in   = in_ras->pixels(yy);
    OUT_PIXEL *sl_out = out_ras->pixels(yy);
    for (int xx = 0; xx < out_ras->getLx(); ++xx, ++sl_in, ++sl_out) {
      int val = 0;
      switch (in_sel) {
      case 0:
        val = sl_in->r;
        break;
      case 1:
        val = sl_in->g;
        break;
      case 2:
        val = sl_in->b;
        break;
      case 3:
        val = sl_in->m;
        break;
      }
      switch (out_sel) {
      case 0:
        sl_out->r = val;
        break;
      case 1:
        sl_out->g = val;
        break;
      case 2:
        sl_out->b = val;
        break;
      case 3:
        sl_out->m = val;
        break;
      }
    }
  }
}
void fx_(const TRasterP in_ras, const int in_sel, TRasterP out_ras,
         const int out_sel) {
  if ((TRaster32P)in_ras && (TRaster32P)out_ras) {
    fx_template<TPixel32, TPixel32>(in_ras, in_sel, out_ras, out_sel);
  } else if ((TRaster64P)in_ras && (TRaster64P)out_ras) {
    fx_template<TPixel64, TPixel64>(in_ras, in_sel, out_ras, out_sel);
  }
}
}
//------------------------------------------------------------
void ino_channel_selector::doCompute(TTile &tile, double frame,
                                     const TRenderSettings &ri) {
  /* ------ サポートしていないPixelタイプはエラーを投げる --- */
  if (!((TRaster32P)tile.getRaster()) && !((TRaster64P)tile.getRaster())) {
    throw TRopException("unsupported input pixel type");
  }

  /* ------ 動作パラメータを得る ---------------------------- */
  const int red_source  = this->m_red_source->getValue() - 1;
  const int gre_source  = this->m_gre_source->getValue() - 1;
  const int blu_source  = this->m_blu_source->getValue() - 1;
  const int alp_source  = this->m_alp_source->getValue() - 1;
  const int red_channel = this->m_red_channel->getValue();
  const int gre_channel = this->m_gre_channel->getValue();
  const int blu_channel = this->m_blu_channel->getValue();
  const int alp_channel = this->m_alp_channel->getValue();

  /* ------ 画像位置とサイズ -------------------------------- */
  /***const TRectD rect = TRectD( tile.m_pos, TDimensionD(
            tile.getRaster()->getLx()
          , tile.getRaster()->getLy()
  ));
  const TPointD poin = TPointD(rect.getP00());
  const TDimension dime = TDimension(
           (int)(rect.getLx()+0.5)
          ,(int)(rect.getLy()+0.5)
  );***/

  /* ------ 塗りつぶしクリア -------------------------------- */
  tile.getRaster()->clear();

  /* ------ 入力画像を接続していなければ処理しない ---------- */
  if (this->getInputPortCount() <= 0) {
    return;
  }

  /* ------ 入力画像の参照を確保 ---------------------------- */
  // TTile *source_tiles = new TTile[this->getInputPortCount()];
  // int   *source_sw    = new int[this->getInputPortCount()];
  // TRasterP *ras_a     = new TRasterP[this->getInputPortCount()];

  /* Not use boost at toonz-6.1 */
  /******
  boost::shared_array<TTile>
          source_tiles(new TTile[this->getInputPortCount()]);
  boost::shared_array<int>
          source_sw(new int[this->getInputPortCount()]);
  boost::shared_array<TRasterP>
          ras_a(new TRasterP[this->getInputPortCount()]);
******/
  /* Array item(TRasterFxPort) number is 4(fix) in this code */
  TTile source_tiles[4];
  int source_sw[4];
  TRasterP ras_a[4];

  int ras_s = 0;

  /* ------ 画像生成 ---------------------------------------- */
  for (int ii = 0; ii < this->getInputPortCount(); ++ii) {
    std::string nm          = this->getInputPortName(ii);
    TRasterFxPort *tmp_port = (TRasterFxPort *)this->getInputPort(nm);
    if (tmp_port->isConnected() && ((ii == red_source) || (ii == gre_source) ||
                                    (ii == blu_source) || (ii == alp_source))) {
      (*tmp_port)->allocateAndCompute(
          source_tiles[ii]
          //,poin,dime
          ,
          tile.m_pos /* 位置 */
          ,
          TDimension(/* サイズ */
                     tile.getRaster()->getLx(), tile.getRaster()->getLy()),
          tile.getRaster() /* sampling */
          ,
          frame, ri);
      source_sw[ii]  = 1;
      ras_a[ras_s++] = source_tiles[ii].getRaster();
    } else
      source_sw[ii] = 0;
  }

  TRasterP red_ras = 0;
  TRasterP gre_ras = 0;
  TRasterP blu_ras = 0;
  TRasterP alp_ras = 0;

  if ((0 <= red_source) && (red_source < this->getInputPortCount()) &&
      source_sw[red_source]) {
    red_ras = source_tiles[red_source].getRaster();
  }
  if ((0 <= gre_source) && (gre_source < this->getInputPortCount()) &&
      source_sw[gre_source]) {
    gre_ras = source_tiles[gre_source].getRaster();
  }
  if ((0 <= blu_source) && (blu_source < this->getInputPortCount()) &&
      source_sw[blu_source]) {
    blu_ras = source_tiles[blu_source].getRaster();
  }
  if ((0 <= alp_source) && (alp_source < this->getInputPortCount()) &&
      source_sw[alp_source]) {
    alp_ras = source_tiles[alp_source].getRaster();
  }

  /* ------ (app_begin)log記憶 ------------------------------ */
  const bool log_sw = ino::log_enable_sw();

  if (log_sw) {
    std::ostringstream os;
    os << "red"
       << "  s " << red_source << "  c " << red_channel << "   green"
       << "  s " << gre_source << "  c " << gre_channel << "   blue"
       << "  s " << blu_source << "  c " << blu_channel << "   alpha"
       << "  s " << alp_source << "  c " << alp_channel << "   tile w "
       << tile.getRaster()->getLx() << "  h " << tile.getRaster()->getLy()
       << "  b " << ino::pixel_bits(tile.getRaster());
    os << "   s_count " << this->getInputPortCount();
    for (int ii = 0; ii < this->getInputPortCount(); ++ii) {
      if (source_sw[ii]) {
        os << "   tile" << ii << "  w " << source_tiles[ii].getRaster()->getLx()
           << "  h " << source_tiles[ii].getRaster()->getLy() << "  b "
           << ino::pixel_bits(source_tiles[ii].getRaster());
      }
    }
    os << "   frame " << frame;
  }
  /* ------ 入力画像の参照開放 ------------------------------ */
  // delete [] source_sw;
  // delete [] source_tiles;
  /* ------ fx処理 ------------------------------------------ */
  try {
    tile.getRaster()->lock();
    for (int ii = 0; ii < ras_s; ++ii) {
      ras_a[ii]->lock();
    }
    if (red_ras) {
      fx_(red_ras, red_channel, tile.getRaster(), 0);
    }
    if (gre_ras) {
      fx_(gre_ras, gre_channel, tile.getRaster(), 1);
    }
    if (blu_ras) {
      fx_(blu_ras, blu_channel, tile.getRaster(), 2);
    }
    if (alp_ras) {
      fx_(alp_ras, alp_channel, tile.getRaster(), 3);
    }
    for (int ii = ras_s - 1; 0 <= ii; --ii) {
      ras_a[ii]->unlock();
    }
    tile.getRaster()->unlock();
  }
  /* ------ error処理 --------------------------------------- */
  catch (std::bad_alloc &e) {
    for (int ii = ras_s - 1; 0 <= ii; --ii) {
      ras_a[ii]->unlock();
    }
    tile.getRaster()->unlock();
    if (log_sw) {
      std::string str("std::bad_alloc <");
      str += e.what();
      str += '>';
    }
    // delete [] ras_a;
    throw;
  } catch (std::exception &e) {
    for (int ii = ras_s - 1; 0 <= ii; --ii) {
      ras_a[ii]->unlock();
    }
    tile.getRaster()->unlock();
    if (log_sw) {
      std::string str("exception <");
      str += e.what();
      str += '>';
    }
    // delete [] ras_a;
    throw;
  } catch (...) {
    for (int ii = ras_s - 1; 0 <= ii; --ii) {
      ras_a[ii]->unlock();
    }
    tile.getRaster()->unlock();
    if (log_sw) {
      std::string str("other exception");
    }
    // delete [] ras_a;
    throw;
  }
  // delete [] ras_a;
}
