/* Copyright 2018 NVIDIA Corporation. 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 NVIDIA CORPORATION nor the names of its
*    contributors 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 ``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.
*/

// # RTC Kernel:
// ** Volume Edge Enhancement **

// # Summary:
// Compute the colormap alpha gradient in the current volume and darken samples with high gradient
// magnitude a a user-defined rate.

// Define the user-defined data structure
struct Edge_enhancement_params
{
  float sample_range; // [10, 10] 1.0 sample range
  float rh;           // 1.0f ray sampling difference
  int stp_num;        // 6 [GUI] additional samples along ray
  float min_alpha;    // 0.2f minimum alpha for sampling (improves performance)
};

NV_IDX_VOLUME_SAMPLE_PROGRAM_PREFIX
class NV_IDX_volume_sample_program
{
  NV_IDX_VOLUME_SAMPLE_PROGRAM

public:
  const Edge_enhancement_params*
    m_edge_enhancement_params; // define variables to bind user-defined buffer to

public:
  NV_IDX_DEVICE_INLINE_MEMBER
  void init_instance()
  {
    // Bind the contents of the buffer slot 0 to the variable
    m_edge_enhancement_params = NV_IDX_bind_parameter_buffer<Edge_enhancement_params>(0);
  }

  NV_IDX_DEVICE_INLINE_MEMBER
  int execute(const NV_IDX_sample_info_self& sample_info, float4& output_color)
  {
    const NV_IDX_volume& volume = state.self;
    const float3& ps = sample_info.sample_position;
    const NV_IDX_colormap& colormap = volume.get_colormap();

    // sample volume
    const float3 p0 = ps - (state.m_ray_direction * (m_edge_enhancement_params->rh *
                                                      m_edge_enhancement_params->sample_range));
    const float3 p2 = ps + (state.m_ray_direction * (m_edge_enhancement_params->rh *
                                                      m_edge_enhancement_params->sample_range));

    const float vs_p0 = volume.sample<float>(p0);
    const float vs_ps = volume.sample<float>(ps);
    const float vs_p2 = volume.sample<float>(p2);

    const float4 ps_color = colormap.lookup(vs_ps);
    const float ps_alpha = ps_color.w;
    const int min_hits = m_edge_enhancement_params->stp_num;
    const int num_steps = min_hits * 2;

    if (ps_alpha < m_edge_enhancement_params->min_alpha)
    {
      // sample below given alpha threshold
      // return NV_IDX_PROG_DISCARD_SAMPLE;
    }

    if ((num_steps > 0) && (ps_alpha > m_edge_enhancement_params->min_alpha))
    {
      // init result color
      int sum_over_b = 0;
      int sum_under_b = 0;
      int sum_over_a = 0;
      int sum_under_a = 0;

      // iterate steps
      for (int sc = 0; sc <= num_steps; sc++)
      {
        // get step size
        const float rt = float(sc) / float(num_steps);
        const float3 pi_b = (1.0f - rt) * p0 + rt * ps;
        const float3 pi_a = (1.0f - rt) * ps + rt * p2;

        const float vs_pi_b = volume.sample<float>(pi_b);
        const float vs_pi_a = volume.sample<float>(pi_a);

        const float4 pia_color = colormap.lookup(vs_pi_b);
        const float4 pib_color = colormap.lookup(vs_pi_a);

        // check by alpha
        if (pib_color.w > ps_alpha)
          sum_over_b += 1;
        if (pib_color.w < ps_alpha)
          sum_under_b += 1;
        if (pia_color.w > ps_alpha)
          sum_over_a += 1;
        if (pia_color.w < ps_alpha)
          sum_under_a += 1;
      }

      // check if all are larger
      // if (sum_over > 1 && sum_under == 0)
      //    output_color = make_float4(ps_color.x, ps_color.y, ps_color.z, ps_color.w);

      // check numer of samples below / above reference
      if (((sum_under_b > min_hits) && (sum_under_a > min_hits)) ||
        ((sum_over_b > min_hits) && (sum_over_a > min_hits)))
      {
        output_color = ps_color / float(sum_under_b + sum_under_a - 2 * min_hits + 1);
        output_color.w = ps_color.w;

        return NV_IDX_PROG_OK;
      }
      else
      {
        output_color = ps_color;

        // return NV_IDX_PROG_DISCARD_SAMPLE;
      }
    }
    else
    {
      output_color = ps_color;
    }

    return NV_IDX_PROG_OK;
  }
}; // class NV_IDX_volume_sample_program
