#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "config.h"
#include "inteldma.h"
#include "ka_log.h"
#include "ka_mem.h"

/**
 * Iyonix screen redraw through DMA transfer.
 *
 * Pocking the screen memory is slow, so when we just have to copy blocks of memory
 * from RGB image (the output of colorspcae conversion's drawers),i.e. when there
 * is no scaling just transfer the lines composing the block over DMA.
 */

// Maximal Screen lines segments
#define MAX_DMA_DEFINTIONS 4000

typedef struct
{
  void*     address;
  uint32_t  size;
} dma_transfer_request_t;

struct intel_dma_t
{
  uint32_t                 dma_swi;              // RISC OS DMA SWI if IntelDMA available
  // current dma
  void*                    dma_handle;           // Screen DMA if necassary/possible
  int                      no_of_requests;       // Number of DMA transfers
  uint32_t                 screen_bpr;           // Bytes in a screen row
  uint32_t                 data_bpr;             // Bytes in a source row
  // new dma
  int                      dma_rebuild_flag;
  int                      new_no_of_requests;
  dma_transfer_request_t   dma_source[MAX_DMA_DEFINTIONS]; // List of requests
  dma_transfer_request_t   dma_drain[MAX_DMA_DEFINTIONS];  // List of requests
};

/**
 * Init structure and check if IntelDMA module is present.
 * Return NULL if module cannot be used.
 */
intel_dma_t* new_inteldma(void)
{
  _kernel_swi_regs regs;
  intel_dma_t* idma = NULL;

  regs.r[0] = 18;
  regs.r[1] = (int) "IntelDMA";
  if (_kernel_swi(OS_Module, &regs, &regs) == NULL)
  {
    regs.r[0] = 12;
    // r1-r2 taken from previous call
    if (_kernel_swi(OS_Module, &regs, &regs) == NULL)
    {
      idma = ka_mem_calloc(sizeof(*idma));
      if (idma != NULL)
        idma->dma_swi = *((uint32_t*) (regs.r[3] + 28));
    }
  }

  return idma;
}

/**
 * Release DMA handle and frees the structure.
 */
void delete_inteldma(intel_dma_t** pidma)
{
  intel_dma_t*idma = *pidma;
  *pidma = NULL;

  if (idma == NULL)
    return;

  inteldma_cleanupTransfer(idma);
  ka_mem_free(idma);
}

/**
 * Start new screen rectangle list.
 * Do not release DMA handle yet, the final transfer list could be identical to the old one.
 * Return 0 if DMA transfer cannot be done.
 */
int inteldma_startScreen(intel_dma_t* idma, uint32_t screen_bpr, uint32_t data_bpr)
{
  if (idma == NULL)
    return 0;

  idma->dma_rebuild_flag = 0;
  idma->new_no_of_requests = 0;
  idma->screen_bpr = screen_bpr;
  idma->data_bpr = data_bpr;

  return 1;
}

/**
 * Add a screen rect to the transfer list.
 * Check if so far the transfer list still matches the old list.
 * Return 0 if it cannot memorize the transfer list required by the rectangle
 *          i.e. when caller will have to directly poke the rectangle on screen.
 */
int inteldma_addScreenRect(intel_dma_t* idma
                         , uint8_t* screen_pos, uint8_t* data_pos
                         , int width, int height)
{
  dma_transfer_request_t* source = &idma->dma_source[idma->new_no_of_requests];
  dma_transfer_request_t* drain = &idma->dma_drain[idma->new_no_of_requests];

  // If same width as screen, its a single block of memory
  if (width == idma->screen_bpr)
  {
    width *= height;
    height = 1;
  }

  // If cannot add all transfers to list don't add any
  if (idma->new_no_of_requests + height > MAX_DMA_DEFINTIONS)
    return 0;

  for (int i = height; i > 0; i--)
  {
    if (!idma->dma_rebuild_flag)
    {
      if ((source->address != data_pos)
      ||  (source->size != width)
      ||  (drain->address != screen_pos))
      {
        source->address = data_pos;
        source->size = width;
        drain->address = screen_pos;
        drain->size = width;
        idma->dma_rebuild_flag = 1;
      }
    }
    else
    {
      source->address = data_pos;
      source->size = width;
      drain->address = screen_pos;
      drain->size = width;
    }

    data_pos += idma->data_bpr;
    screen_pos += idma->screen_bpr;

    idma->new_no_of_requests++;
    source++;
    drain++;
  }

  return 1;
}

/**
 * Releases DMA handle, and void transfer list.
 */
void inteldma_cleanupTransfer(intel_dma_t* idma)
{
  if (idma == NULL)
    return;

  // Release existing DMA
  if (idma->dma_handle)
  {
    _swix(idma->dma_swi, _IN(0)|_IN(3), 2, idma->dma_handle);
    idma->dma_handle = NULL;
    idma->no_of_requests = 0;
  }
}

/**
 * If new transfer list does not match old one, release DMA handle and obtains
 * a new handle for the new transfer list.
 * Proceed with the DMA transfer but fallback to memcpy() transfer list if transfer fails.
 */
void inteldma_transferScreen(intel_dma_t* idma)
{
  const _kernel_oserror* e;
  int dma_failed = 0;

  if (idma == NULL)
    return;

  if (idma->dma_rebuild_flag || (idma->no_of_requests != idma->new_no_of_requests))
  {
    idma->no_of_requests = idma->new_no_of_requests;

    // Release existing DMA
    if (idma->dma_handle)
    {
      _swix(idma->dma_swi, _IN(0)|_IN(3), 2, idma->dma_handle);
      idma->dma_handle = NULL;
    }

    if (idma->no_of_requests > 0)
    {
      // Set up DMA
      e = _swix(idma->dma_swi, _INR(0,5)|_OUT(3)
               , 0
               , idma->dma_source
               , idma->no_of_requests
               , 0
               , idma->dma_drain
               , idma->no_of_requests
               , &idma->dma_handle);
      if (e == NULL)
      {
        if (!idma->dma_handle)
        {
          ka_log(ka_log_error | ka_log_draw, "IntelDMA 0: No handle returned");
          dma_failed = 1;
        }
        else
        {
          if (config.debug & cfg_printdrawstats)
            ka_log(ka_log_draw, "IntelDMA 0: Defined %d requests", idma->no_of_requests);
        }
      }
      else
      {
        ka_log(ka_log_error | ka_log_draw, "IntelDMA 0: %s", e->errmess);
        dma_failed = 1;
      }
    }
  }

  // Proceed with DMA
  if ((idma->dma_handle)
  &&  (dma_failed == 0))
  {
    e = _swix(idma->dma_swi, _INR(0,3), 1, 3, 1, idma->dma_handle);
    if (e != NULL)
    {
      ka_log(ka_log_error | ka_log_draw, "IntelDMA 1: %s", e->errmess);
      dma_failed = 1;
    }
  }

  if (dma_failed == 1)
  {
    int i;

    // Damn we need to do the transfer by hand
    for (i = 0; i < idma->no_of_requests; i++)
    {
      // Note: using the commented code trigger a compiler bug in cc 5.61
/*
      memcpy( idma->dma_drain[i].address
            , idma->dma_source[i].address
            , idma->dma_source[i].size
            );
*/
      dma_transfer_request_t*psource = &idma->dma_source[i];
      memcpy( idma->dma_drain[i].address
            , psource->address
            , psource->size
            );
    }

    idma->no_of_requests = 0;
  }

  if (idma->no_of_requests == 0)
  {
    inteldma_cleanupTransfer(idma);
  }
}
