/*
 * UPIWIN - Micro Pi Windowing Framework Kernel
 * Copyright (C) 2019 Amy Bowersox/Erbosoft Metaverse Design Solutions
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *-------------------------------------------------------------------------
 */
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "fbinit.h"
#include "devctxt.h"
#include "dc_screen.h"
#include "bitmap.h"

inline static PUINT16 loc_from_coords(PSCREENPRIVDATA priv, INT32 x, INT32 y)
{
  return priv->pdata + (y * priv->pix_per_row) + x;
}

inline static UINT16 native_from_COLORREF(COLORREF cr)
{
  return (UINT16)(((cr << 8) & 0xF800) | ((cr >> 5) & 0x7E0) | ((cr >> 19) & 0x1F));
}

inline static COLORREF COLORREF_from_native(UINT16 cr)
{
  UINT32 tmp = cr;
  return (COLORREF)(((tmp << 19) & 0xF80000) | ((tmp << 5) & 0xFC00) | ((tmp >> 8) & 0xF800));
}

inline static UINT16 apply_rop2(INT32 op, UINT16 disp, UINT16 pen)
{
  switch (op)
  {
    case R2_BLACK:
	    return 0;
    case R2_NOTMERGEPEN:
      return ~(disp | pen);
    case R2_MASKNOTPEN:
      return disp & (~pen);
    case R2_NOTCOPYPEN:
      return ~pen;
    case R2_MASKPENNOT:
      return (~disp) & pen;
    case R2_NOT:
      return ~disp;
    case R2_XORPEN:
      return disp ^ pen;
    case R2_NOTMASKPEN:
      return ~(disp & pen);
    case R2_MASKPEN:
      return disp & pen;
    case R2_NOTXORPEN:
      return ~(disp ^ pen);
    case R2_NOP:
      return disp;
    case R2_MERGENOTPEN:
      return disp | (~pen);
    case R2_COPYPEN:
      return pen;
    case R2_MERGEPENNOT:
      return (~disp) | pen;
    case R2_MERGEPEN:
      return disp | pen;
    case R2_WHITE:
      return (UINT16)(-1);
  }
  return pen; /* last ditch default */
}

static COLORREF screen_set_pixel(PVOID privdata, INT32 x, INT32 y, COLORREF color, INT32 op)
{
  PSCREENPRIVDATA priv = (PSCREENPRIVDATA)privdata;
  UINT16 pencolor = native_from_COLORREF(color);
  PUINT16 loc = loc_from_coords(priv, x, y);
  UINT16 screen = *loc;
  *loc = apply_rop2(op, screen, pencolor);
  return COLORREF_from_native(screen);
}

static BOOL screen_line(PVOID privdata, INT32 x1, INT32 y1, INT32 x2, INT32 y2, COLORREF color, INT32 op)
{
  PSCREENPRIVDATA priv = (PSCREENPRIVDATA)privdata;
  UINT16 pencolor = native_from_COLORREF(color);
  INT32 dx = x2 - x1;
  INT32 dy = y2 - y1;
  INT32 tmp;
  PUINT16 loc;

  ASSERT(x1 >= 0);
  ASSERT(x1 < Fb_Info->width);
  ASSERT(y1 >= 0);
  ASSERT(y1 < Fb_Info->height);
  ASSERT(x2 >= 0);
  ASSERT(x2 < Fb_Info->width);
  ASSERT(y2 >= 0);
  ASSERT(y2 < Fb_Info->height);

  /* uses Bresenham's line algorithm with 16-bit fixed-point arithmetic */
  if (ABS(dx) < ABS(dy))
  {
    if (y1 > y2)
    {
      tmp = x1;
      x1 = x2;
      x2 = tmp;
      tmp = y1;
      y1 = y2;
      y2 = tmp;
      dx = -dx;
      dy = -dy;
    }
    loc = loc_from_coords(priv, x1, y1);
    tmp = x1;
    x1 <<= 16;
    dx = (dx << 16) / dy;
    while (y1 <= y2)
    {
      *loc = apply_rop2(op, *loc, pencolor);
      x1 += dx;
      ++y1;
      loc += priv->pix_per_row;
      if (tmp != (x1 >> 16))
      {
        loc += ((x1 >> 16) - tmp);
        tmp = x1 >> 16;
      }
    }
  }
  else
  {
    if (x1 > x2)
    {
      tmp = x1;
      x1 = x2;
      x2 = tmp;
      tmp = y1;
      y1 = y2;
      y2 = tmp;
      dx = -dx;
      dy = -dy;
    }
    loc = loc_from_coords(priv, x1, y1);
    tmp = y1;
    y1 <<= 16;
    dy = dx ? (dy << 16) / dx : 0;
    while (x1 <= x2)
    {
      *loc = apply_rop2(op, *loc, pencolor);
      y1 += dy;
      ++x1;
      ++loc;
      if (tmp != (y1 >> 16))
      {
        loc += (((y1 >> 16) - tmp) * priv->pix_per_row);
        tmp = y1 >> 16;
      }
    }
  }
  return TRUE;
}

static BOOL screen_solid_rect(PVOID privdata, PRECT rect, COLORREF color, INT32 op)
{
  PSCREENPRIVDATA priv = (PSCREENPRIVDATA)privdata;
  UINT16 pencolor = native_from_COLORREF(color);
  PUINT16 ps, p;
  int y, x;

  ps = loc_from_coords(priv, rect->left, rect->top);
  for (y = rect->top; y < rect->bottom; y++)
  {
    p = ps;
    for (x = rect->left; x < rect->right; x++)
    {
      *p = apply_rop2(op, *p, pencolor);
      ++p;
    }
    ps += priv->pix_per_row;
  }
  return TRUE;
}

static const DCFUNTABLE screen_funtable; /* forward declaration */
static void screen_context_destroy(PVOID obj);

static PDCTXT screen_create_compat(PVOID privdata)
{
  PSCREENPRIVDATA priv_new;
  PBITMAP pbmp;
  PDCTXT rc;

  pbmp = BMP_Create(1, 1, NULL);
  if (!pbmp)
    return NULL;
  priv_new = (PSCREENPRIVDATA)malloc(sizeof(SCREENPRIVDATA));
  if (!priv_new)
  {
    Go_release(&(pbmp->hdr));
    return NULL;
  }
  priv_new->pix_per_row = pbmp->width;
  priv_new->pdata = pbmp->bits;

  rc = _DC_Allocate(&screen_funtable, priv_new);
  if (rc)
  {
    rc->hdr.dtor = screen_context_destroy;
    rc->flags = DCFLG_IS_MEMORY;
    rc->baserect.left = rc->baserect.top = 0;
    rc->baserect.right = pbmp->width;
    rc->baserect.bottom = pbmp->height;
    memcpy(&(rc->cliprect), &(rc->baserect), sizeof(RECT));
    rc->cur_bitmap = pbmp;
  }
  else
  {
    free(priv_new);
    Go_release(&(pbmp->hdr));
  }
  return rc;
}

BOOL screen_new_bitmap(PVOID privdata, PBITMAP pbmp)
{
  PSCREENPRIVDATA priv = (PSCREENPRIVDATA)privdata;
  priv->pix_per_row = pbmp->width;
  priv->pdata = pbmp->bits;
  return TRUE;
}

BOOL screen_bitblt(PVOID p_dest, PRECT r_dest, PVOID p_src, PRECT r_src, UINT32 op)
{
  PSCREENPRIVDATA dest = (PSCREENPRIVDATA)p_dest;
  PSCREENPRIVDATA src = (PSCREENPRIVDATA)p_src;
  PUINT16 pd, ps;
  INT32 width, i;

  pd = loc_from_coords(dest, r_dest->left, r_dest->top);
  ps = loc_from_coords(src, r_src->left, r_src->top);
  width = r_src->right - r_src->left;
  for (i = r_src->top; i < r_src->bottom; ++i)
  {
    memcpy(pd, ps, width * sizeof(UINT16));
    pd += dest->pix_per_row;
    ps += src->pix_per_row;
  }
  return TRUE;
}

static const DCFUNTABLE screen_funtable = {
  screen_set_pixel,
  screen_line,
  screen_solid_rect,
  screen_create_compat,
  screen_new_bitmap,
  screen_bitblt
};

static void screen_context_destroy(PVOID obj)
{
  PDCTXT pdctxt = (PDCTXT)obj;
  _DC_FinalizeCommon(pdctxt);
  free(pdctxt->privdata);
}

PDCTXT DC_CreateScreenContext(void)
{
  PDCTXT rc;
  PSCREENPRIVDATA priv;

  priv = (PSCREENPRIVDATA)malloc(sizeof(SCREENPRIVDATA));
  if (!priv)
    return NULL;
  priv->pix_per_row = Fb_Info->width;
  priv->pdata = Fb_Ptr;

  rc = _DC_Allocate(&screen_funtable, priv);
  if (rc)
  {
    rc->hdr.dtor = screen_context_destroy;
    rc->flags = DCFLG_IS_SCREEN;
    rc->baserect.left = rc->baserect.top = 0;
    rc->baserect.right = Fb_Info->width;
    rc->baserect.bottom = Fb_Info->height;
    memcpy(&(rc->cliprect), &(rc->baserect), sizeof(RECT));
  }
  else
    free(priv);
  return rc;
}

PDCTXT _DC_CreateScreenCompatibleContext(void)
{
  return screen_create_compat(NULL);
}
