//
//  KRViewport.cpp
//  Kraken Engine
//
//  Copyright 2026 Kearwood Gilbert. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
//  
//  1. Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//  
//  2. 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.
//  
//  THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''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 KEARWOOD GILBERT 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.
//  
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of Kearwood Gilbert.
//
#define KRENGINE_SWAP_INT(x,y) {int t;t=x;x=y;y=t;}

#include "KREngine-common.h"

#include "KRViewport.h"

const long KRENGINE_OCCLUSION_TEST_EXPIRY = 10;

using namespace hydra;

KRViewport::KRViewport()
{
  m_size = Vector2::One();
  m_matProjection = Matrix4();
  m_matView = Matrix4();
  m_lodBias = 0.0f;
  calculateDerivedValues();
}

KRViewport::KRViewport(const Vector2& size, const Matrix4& matView, const Matrix4& matProjection)
{
  m_size = size;
  m_matView = matView;
  m_matProjection = matProjection;
  calculateDerivedValues();
}

bool KRViewport::getShaderValue(const KRCamera* camera, ShaderValue value, Matrix4* output) const
{
  switch (value) {
    case ShaderValue::projection_matrix:
      *output = m_matProjection;
      return true;
    case ShaderValue::invp:
      *output = m_matInverseProjection;
      return true;
    default:
      return false;
  }
}

bool KRViewport::getShaderValue(const KRCamera* camera, ShaderValue value, Vector4* output) const
{
  switch (value) {
    case ShaderValue::viewport:
      *output = Vector4::Create(
        0.0f,
        0.0f,
        getSize().x,
        getSize().y
      );
      return true;
    default:
      return false;
  }
}



KRViewport& KRViewport::operator=(const KRViewport& v)
{
  if (this != &v) { // Prevent self-assignment.
    m_size = v.m_size;
    m_matProjection = v.m_matProjection;
    m_matView = v.m_matView;
    m_lodBias = v.m_lodBias;

    calculateDerivedValues();
  }
  return *this;
}


KRViewport::~KRViewport()
{

}

const Vector2& KRViewport::getSize() const
{
  return m_size;
}

const Matrix4& KRViewport::getViewMatrix() const
{
  return m_matView;
}

const Matrix4& KRViewport::getProjectionMatrix() const
{
  return m_matProjection;
}

void KRViewport::setSize(const Vector2& size)
{
  m_size = size;
}

void KRViewport::setViewMatrix(const Matrix4& matView)
{
  m_matView = matView;
  calculateDerivedValues();
}

void KRViewport::setProjectionMatrix(const Matrix4& matProjection)
{
  m_matProjection = matProjection;
  calculateDerivedValues();
}

const Matrix4& KRViewport::KRViewport::getViewProjectionMatrix() const
{
  return m_matViewProjection;
}

const Matrix4& KRViewport::getInverseViewMatrix() const
{
  return m_matInverseView;
}

const Matrix4& KRViewport::getInverseProjectionMatrix() const
{
  return m_matInverseProjection;
}

const Vector3& KRViewport::getCameraDirection() const
{
  return m_cameraDirection;
}

const Vector3& KRViewport::getCameraPosition() const
{
  return m_cameraPosition;
}

const int* KRViewport::getFrontToBackOrder() const
{
  return &m_frontToBackOrder[0];
}

const int* KRViewport::getBackToFrontOrder() const
{
  return &m_backToFrontOrder[0];
}

void KRViewport::calculateDerivedValues()
{
  m_matViewProjection = m_matView * m_matProjection;
  m_matInverseView = Matrix4::Invert(m_matView);
  m_matInverseProjection = Matrix4::Invert(m_matProjection);
  m_cameraPosition = Matrix4::Dot(m_matInverseView, Vector3::Zero());
  m_cameraDirection = Matrix4::Dot(m_matInverseView, Vector3::Create(0.0, 0.0, 1.0)) - Matrix4::Dot(m_matInverseView, Vector3::Create(0.0, 0.0, 0.0));

  for (int i = 0; i < 8; i++) {
    m_frontToBackOrder[i] = i;
  }

  if (m_cameraDirection.x > 0.0) {
    KRENGINE_SWAP_INT(m_frontToBackOrder[0], m_frontToBackOrder[1]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[2], m_frontToBackOrder[3]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[4], m_frontToBackOrder[5]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[6], m_frontToBackOrder[7]);
  }

  if (m_cameraDirection.y > 0.0) {
    KRENGINE_SWAP_INT(m_frontToBackOrder[0], m_frontToBackOrder[2]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[1], m_frontToBackOrder[3]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[4], m_frontToBackOrder[6]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[5], m_frontToBackOrder[7]);
  }

  if (m_cameraDirection.z > 0.0) {
    KRENGINE_SWAP_INT(m_frontToBackOrder[0], m_frontToBackOrder[4]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[1], m_frontToBackOrder[5]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[2], m_frontToBackOrder[6]);
    KRENGINE_SWAP_INT(m_frontToBackOrder[3], m_frontToBackOrder[7]);
  }

  for (int i = 0; i < 8; i++) {
    m_backToFrontOrder[i] = m_frontToBackOrder[7 - i];
  }
}

float KRViewport::getLODBias() const
{
  return m_lodBias;
}

void KRViewport::setLODBias(float lod_bias)
{
  m_lodBias = lod_bias;
}

float KRViewport::coverage(const AABB& b) const
{
  if (!visible(b)) {
    return 0.0f; // Culled out by view frustrum
  } else {
    Vector3 nearest_point = b.nearestPoint(getCameraPosition());
    float distance = (nearest_point - getCameraPosition()).magnitude();

    Vector3 v = Matrix4::DotWDiv(m_matProjection, getCameraPosition() + getCameraDirection() * distance);

    float screen_depth = distance / 1000.0f;

    return std::clamp(1.0f - screen_depth, 0.01f, 1.0f);

    /*

    Vector2 screen_min;
    Vector2 screen_max;
    // Loop through all corners and transform them to screen space
    for(int i=0; i<8; i++) {
        Vector3 screen_pos = Matrix4::DotWDiv(m_matViewProjection, Vector3(i & 1 ? b.min.x : b.max.x, i & 2 ? b.min.y : b.max.y, i & 4 ? b.min.z : b.max.z));
        if(i==0) {
            screen_min = screen_pos.xy();
            screen_max = screen_pos.xy();
        } else {
            if(screen_pos.x < screen_min.x) screen_min.x = screen_pos.x;
            if(screen_pos.y < screen_min.y) screen_min.y = screen_pos.y;
            if(screen_pos.x > screen_max.x) screen_max.x = screen_pos.x;
            if(screen_pos.y > screen_max.y) screen_max.y = screen_pos.y;
        }
    }

    screen_min.x = std::clamp(screen_min.x, 0.0f, 1.0f);
    screen_min.y = std::clamp(screen_min.y, 0.0f, 1.0f);
    screen_max.x = std::clamp(screen_max.x, 0.0f, 1.0f);
    screen_max.y = std::clamp(screen_max.y, 0.0f, 1.0f);

    float c = (screen_max.x - screen_min.x) * (screen_max.y - screen_min.y);
    return std::clamp(c, 0.01f, 1.0f);
    */
  }
}


bool KRViewport::visible(const AABB& b) const
{
  // test if bounding box would be within the visible range of the clip space transformed by matViewProjection
  // This is used for view frustrum culling

  int outside_count[6] = { 0, 0, 0, 0, 0, 0 };

  for (int iCorner = 0; iCorner < 8; iCorner++) {
    Vector4 sourceCornerVertex = Vector4::Create(
                                             (iCorner & 1) == 0 ? b.min.x : b.max.x,
                                             (iCorner & 2) == 0 ? b.min.y : b.max.y,
                                             (iCorner & 4) == 0 ? b.min.z : b.max.z, 1.0f);

    Vector4 cornerVertex = Matrix4::Dot4(m_matViewProjection, sourceCornerVertex);

    if (cornerVertex.x < -cornerVertex.w) {
      outside_count[0]++;
    }
    if (cornerVertex.y < -cornerVertex.w) {
      outside_count[1]++;
    }
    if (cornerVertex.z < -cornerVertex.w) {
      outside_count[2]++;
    }
    if (cornerVertex.x > cornerVertex.w) {
      outside_count[3]++;
    }
    if (cornerVertex.y > cornerVertex.w) {
      outside_count[4]++;
    }
    if (cornerVertex.z > cornerVertex.w) {
      outside_count[5]++;
    }
  }

  bool is_visible = true;
  for (int iFace = 0; iFace < 6; iFace++) {
    if (outside_count[iFace] == 8) {
      is_visible = false;
    }
  }

  return is_visible;
}
