// Constant.C
// Copyright (c) 2009 The Foundry Visionmongers Ltd.  All Rights Reserved.

static const char* const CLASS = "Constant";

static const char* const HELP =
  "Constant:\n"
  "Produces an image where every pixel is the same color. This includes "
  "pixels outside the image area as well as inside it."
  "\n\n"
  "The first and last frame set the length of the clip for operators that "
  "want that information.";

#include "DDImage/DDWindows.h"
#include "DDImage/Black.h"
#include "DDImage/Row.h"
#include "DDImage/Format.h"
#include "DDImage/Scene.h"
#include "DDImage/LUT.h"
#include "DDImage/gl.h"
#include "DDImage/GeoInfo.h"
#include "DDImage/Knobs.h"
#include "DDImage/VertexContext.h"

using namespace DD::Image;

class Constant : public Black
{
protected:
  void _validate(bool) override;
  Channel channel[4];
  float color[4];
  int first_frame, last_frame;
  FormatPair formats;
public:
  Constant(Node* node) : Black(node)
  {
    channel[0] = Chan_Red;
    channel[1] = Chan_Green;
    channel[2] = Chan_Blue;
    channel[3] = Chan_Alpha;
    color[0] = color[1] = color[2] = color[3] = 0;
    first_frame = 1;
    last_frame = 1;
    formats.format(nullptr);
  }
  void engine(int y, int x, int r, ChannelMask, Row & row) override;
  void fragment_shader(const VertexContext&, Pixel&) override;
  bool shade_GL(ViewerContext* ctx, GeoInfo& geo) override;
  int slowness() const { return 0; }
  void knobs(Knob_Callback) override;
  const char* Class() const override { return ::CLASS; }
  static const Description d;
  const char* node_help() const override { return HELP; }
  bool isBlackIop() const override { return false; }

  const char* gpuEngine_decl() const override;
  const char* gpuEngine_body() const override;
  void gpuEngine_GL_begin(DD::Image::GPUContext* context) override;
};

void Constant::_validate(bool)
{
  // Set the user-selected channels, see if any are non-zero:
  bool non_zero = false;
  info_.channels(Mask_None);
  for (int z = 0; z < 4; z++) {
    info_.turn_on(channel[z]);
    if (color[z])
      non_zero = true;
  }
  // When used internally by other plugins they don't set formats,
  // they just put the format into the info_. Detect this and don't
  // write over the format they chose:
  if (formats.format()) {
    info_.format(*formats.format());
    info_.full_size_format(*formats.fullSizeFormat());
  }
  info_.black_outside(!non_zero);
#if 1
  // Use a bounding box the size of the image. This is what most file
  // readers would do if they read an image that was a solid color:
  info_.set(format());
#else
  // Use a 1x1 bounding box. This should be correct as the color is
  // replicated from the one pixel everywhere. However plenty of
  // operators have dependencies on the bounding box.  Some pre-4.8
  // versions of Nuke did this if and only if the constant was black,
  // but even that caused problems.
  info_.set(format().x(), format().y(), format().x() + 1, format().y() + 1);
#endif
  info_.first_frame(first_frame);
  info_.last_frame(last_frame);
}

void Constant::engine(int y, int x, int r, ChannelMask channels, Row& row)
{
  for (int i = 4; i--; )
    if (intersect(channels, channel[i])) {
      float C = color[i];
      if (C) {
        float* TO = row.writable(channel[i]) + x;
        float* END = TO + (r - x);
        while (TO < END)
          *TO++ = C;
      }
      else {
        row.erase(channel[i]);
      }
    }

}

void Constant::fragment_shader(const VertexContext& vtx, Pixel& out)
{
  for (int i = 0; i < 4; i++)
    out[channel[i]] = color[i];

  // be sure to set as solid if no alpha is present
  if ( channel[3] != Chan_Alpha )
    out[Chan_Alpha] = 1.0f;
}

bool Constant::shade_GL(ViewerContext* ctx, GeoInfo& geo)
{
  glColor4fvLUT(color);
  return true;
}

void Constant::knobs(Knob_Callback f)
{
  Channel_knob(f, channel, 4, "channels");
  AColor_knob(f, color, "color");
  Format_knob(f, &formats, "format");
  Obsolete_knob(f, "full_format", "knob format $value");
  Obsolete_knob(f, "proxy_format", nullptr);
  Int_knob(f, &first_frame, "first", "frame range");
  SetFlags(f, Knob::NO_ANIMATION);
  Int_knob(f, &last_frame, "last", "");
  SetFlags(f, Knob::NO_ANIMATION);
}

const char* Constant::gpuEngine_decl() const
{
  if (nodeContext() != eTimeline) {
    return nullptr;
  }

  return "uniform vec4 $$color;\n";
}

const char* Constant::gpuEngine_body() const
{
  if (nodeContext() != eTimeline) {
    return nullptr;
  }

  return "OUT = $$color;\n";
}

void Constant::gpuEngine_GL_begin(DD::Image::GPUContext* context)
{
  if (nodeContext() != eTimeline) {
    return;
  }
  context->bind("$$color", 4, 1, color);
}


static Iop* build(Node* node) { return new Constant(node); }
const Iop::Description Constant::d(::CLASS, "Image/Constant", build);