/**
 * Looking Glass
 * Copyright © 2017-2026 The Looking Glass Authors
 * https://looking-glass.io
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "transport/lgmp/CLGMPControl.h"

#include "CDebug.h"
#include "WCWrite.h"

#include <string.h>

#include <utility>

static const uint32_t MAX_POINTER_SIZE =
  (uint32_t)(sizeof(KVMFRCursor) + (512 * 512 * 4));
static const uint32_t POINTER_POSITION_SIZE =
  (uint32_t)sizeof(KVMFRCursor);
static const size_t WC_WRITE_MIN_SIZE = 4 * 1024;

static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
{
  LGMP_Q_POINTER,     //queueID
  LGMP_Q_POINTER_LEN, //numMesages
  1000                //subTimeout
};

CLGMPControl::~CLGMPControl()
{
  DeInit();
}

bool CLGMPControl::Initialize()
{
  if (m_pointerQueue)
    return true;

  LGMP_STATUS status;
  if ((status = m_host.CreateQueue(
      POINTER_QUEUE_CONFIG, &m_pointerQueue)) != LGMP_OK)
  {
    DEBUG_ERROR("lgmpHostQueueCreate Failed (Pointer): %s",
      lgmpStatusString(status));
    return false;
  }

  for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
  {
    if ((status = m_host.Allocate(
        POINTER_POSITION_SIZE, &m_pointerMemory[i])) != LGMP_OK)
    {
      DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s",
        lgmpStatusString(status));
      return false;
    }
    memset(lgmpHostMemPtr(m_pointerMemory[i]), 0, POINTER_POSITION_SIZE);
  }

  for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
  {
    if ((status = m_host.Allocate(
        MAX_POINTER_SIZE, &m_pointerShapeMemory[i])) != LGMP_OK)
    {
      DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shapes): %s",
        lgmpStatusString(status));
      return false;
    }
    memset(lgmpHostMemPtr(m_pointerShapeMemory[i]), 0, MAX_POINTER_SIZE);
  }

  for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
  {
    if ((status = m_host.Allocate(
        sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform),
        &m_pointerTransformMemory[i])) != LGMP_OK)
    {
      DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Transform): %s",
        lgmpStatusString(status));
      return false;
    }
    memset(lgmpHostMemPtr(m_pointerTransformMemory[i]), 0,
      sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform));
  }

  return true;
}

void CLGMPControl::DeInit()
{
  SetControlEvents(nullptr, {});
  for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
    lgmpHostMemFree(&m_pointerMemory[i]);
  for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
    lgmpHostMemFree(&m_pointerShapeMemory[i]);
  for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
    lgmpHostMemFree(&m_pointerTransformMemory[i]);

  m_pointerQueue          = nullptr;
  m_pointerMemoryIndex    = 0;
  m_pointerShapeIndex     = 0;
  m_pointerTransformIndex = 0;
}

LGMP_STATUS CLGMPControl::ReadDataWithSource(void * data, size_t * size,
  uint32_t * sourceClientID)
{
  return lgmpHostReadDataWithSource(
    m_pointerQueue, data, size, sourceClientID);
}

LGMP_STATUS CLGMPControl::AckData()
{
  return lgmpHostAckData(m_pointerQueue);
}

bool CLGMPControl::HasNewSubscribers()
{
  return lgmpHostQueueNewSubs(m_pointerQueue) != 0;
}

void CLGMPControl::SetControlEvents(
  IControlEvents * events, const ControlToken& token)
{
  CSRWExclusiveLock lock(m_eventLock);
  m_events = events;
  m_token  = events ? token : ControlToken {};
}

PLGMPMemory CLGMPControl::FindAvailable(
  PLGMPMemory * memory, int count, int& index) const
{
  for (int offset = 0; offset < count; ++offset)
  {
    const int candidate = (index + offset) % count;
    if (memory[candidate] &&
        !lgmpHostQueuePayloadPending(m_pointerQueue, memory[candidate]))
    {
      index = candidate;
      return memory[candidate];
    }
  }
  return nullptr;
}

ControlResult CLGMPControl::SendCursor(
  const IDARG_OUT_QUERY_HWCURSOR& info,
  const BYTE * data, size_t size, UINT sdrWhiteLevel)
{
  if (!m_pointerQueue)
    return ControlResult::FAILED;

  const bool hasShape = info.CursorShapeInfo.CursorType !=
    IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
  if (hasShape)
  {
    if (info.CursorShapeInfo.CursorType != IDDCX_CURSOR_SHAPE_TYPE_ALPHA &&
        info.CursorShapeInfo.CursorType !=
          IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR)
    {
      DEBUG_ERROR("Unsupported pointer shape type: %u",
        static_cast<unsigned>(info.CursorShapeInfo.CursorType));
      return ControlResult::FAILED;
    }

    if (info.CursorShapeInfo.Height &&
        info.CursorShapeInfo.Pitch > SIZE_MAX / info.CursorShapeInfo.Height)
    {
      DEBUG_ERROR("Pointer shape size overflow");
      return ControlResult::FAILED;
    }
    const size_t required = static_cast<size_t>(
      info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch;
    if (required != size || (required && !data) ||
        required > MAX_POINTER_SIZE - sizeof(KVMFRCursor))
    {
      DEBUG_ERROR("Invalid pointer shape payload: %zu bytes", size);
      return ControlResult::FAILED;
    }
  }

  PLGMPMemory mem;
  int * index;
  int count;
  if (!hasShape)
  {
    index = &m_pointerMemoryIndex;
    count = LGMP_Q_POINTER_LEN;
    mem = FindAvailable(m_pointerMemory, count, *index);
  }
  else
  {
    index = &m_pointerShapeIndex;
    count = POINTER_SHAPE_BUFFERS;
    mem = FindAvailable(m_pointerShapeMemory, count, *index);
  }
  if (!mem)
    return ControlResult::RETRY;

  KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
  cursor->sdrWhiteLevel = sdrWhiteLevel ?
    sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT;

  uint32_t flags  = CURSOR_FLAG_VISIBLE_VALID;

  if (info.IsCursorVisible)
  {
    cursor->x = (int16_t)info.X;
    cursor->y = (int16_t)info.Y;
    flags |= CURSOR_FLAG_POSITION | CURSOR_FLAG_VISIBLE;
  }

  if (hasShape)
  {
    if (size)
    {
      if (size < WC_WRITE_MIN_SIZE)
        memcpy(cursor + 1, data, size);
      else
        WCWrite::Copy(cursor + 1, data, size);
    }

    cursor->hx     = (int8_t  )info.CursorShapeInfo.XHot;
    cursor->hy     = (int8_t  )info.CursorShapeInfo.YHot;
    cursor->width  = (uint32_t)info.CursorShapeInfo.Width;
    cursor->height = (uint32_t)info.CursorShapeInfo.Height;
    cursor->pitch  = (uint32_t)info.CursorShapeInfo.Pitch;

    switch (info.CursorShapeInfo.CursorType)
    {
      case IDDCX_CURSOR_SHAPE_TYPE_ALPHA:
        cursor->type = CURSOR_TYPE_COLOR;
        break;

      case IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR:
        cursor->type = CURSOR_TYPE_MASKED_COLOR;
        break;
    }

    flags |= CURSOR_FLAG_SHAPE;
  }

  WCWrite::Flush();
  const LGMP_STATUS status =
    lgmpHostQueuePost(m_pointerQueue, flags, mem);
  if (status == LGMP_OK)
  {
    *index = (*index + 1) % count;
    return ControlResult::APPLIED;
  }
  if (status == LGMP_ERR_QUEUE_FULL)
    return ControlResult::RETRY;

  DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s",
    lgmpStatusString(status));
  return ControlResult::FAILED;
}

ControlResult CLGMPControl::SetColorTransform(
  std::shared_ptr<const D12ColorTransform> transform)
{
  return SendColorTransform(transform);
}

ControlResult CLGMPControl::SendColorTransform(
  const std::shared_ptr<const D12ColorTransform>& transform)
{
  if (!m_pointerQueue || !m_pointerTransformMemory[0])
    return ControlResult::FAILED;

  PLGMPMemory mem = FindAvailable(m_pointerTransformMemory,
    COLOR_TRANSFORM_BUFFERS, m_pointerTransformIndex);
  if (!mem)
    return ControlResult::RETRY;

  KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
  KVMFRColorTransform * output =
    (KVMFRColorTransform *)(cursor + 1);

  output->flags = 0;
  if (transform)
  {
    if (transform->matrixEnabled)
      output->flags |= KVMFR_COLOR_TRANSFORM_MATRIX;
    if (transform->lutEnabled)
      output->flags |= KVMFR_COLOR_TRANSFORM_LUT;
    memcpy(output->matrix, transform->matrix, sizeof(output->matrix));
    output->scalar = transform->scalar;
    WCWrite::Copy(output->lut, transform->lut, sizeof(output->lut));
  }

  WCWrite::Flush();
  const LGMP_STATUS status = lgmpHostQueuePost(m_pointerQueue,
    CURSOR_FLAG_COLOR_TRANSFORM, mem);
  if (status == LGMP_OK)
  {
    m_pointerTransformIndex =
      (m_pointerTransformIndex + 1) % COLOR_TRANSFORM_BUFFERS;
    return ControlResult::APPLIED;
  }
  if (status == LGMP_ERR_QUEUE_FULL)
    return ControlResult::RETRY;

  DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer Transform): %s",
    lgmpStatusString(status));
  return ControlResult::FAILED;
}

void CLGMPControl::RequestReplay()
{
  CSRWSharedLock lock(m_eventLock);
  if (m_events)
    m_events->OnControlReplay(m_token);
}
