/*
 * 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 <string.h>
#include <stdlib.h>
#include "log.h"
#include "gfxtype.h"
#include "gfxobj.h"
#include "devctxt.h"
#include "bitmap.h"
#include "dc_screen.h"

inline static BYTE line_clip_outcode(INT32 x, INT32 y, INT32 xmin, INT32 ymin, INT32 xmax, INT32 ymax)
{
  BYTE rc = 0;
  if (y < ymin)
    rc |= 0x8;
  else if (y >= ymax)
    rc |= 0x4;
  if (x < xmin)
    rc |= 0x1;
  else if (x >= xmax)
    rc |= 0x2;
  return rc;
}

#define CPX 8  /* clipping precision in bits */

#define ONE (1 << CPX)  /* constant for mathematics */

/* these macros keep the number of bits straight when doing fixed-point multiply & divide */
#define M(a, b)  ((((a) * (b))) >> CPX)
#define D(a, b)  (((a) << CPX) / (b))

static BOOL line_clip(PINT32 output, INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 xmin, INT32 ymin, INT32 xmax, INT32 ymax)
{
  BYTE outcode1, outcode2;
  INT32 tmp;
  int nloop = 0;

  /* Cohen-Sutherland line-clipping algorithm (see Foley & Van Dam, pp. 145-149) */
  for (;;)
  {
    if (++nloop == 20)
    {
      Log(LDEBUG, "POSSIBLE INFINITE LOOP DETECTED - REJECTING");
      return FALSE;
    }
    outcode1 = line_clip_outcode(x1, y1, xmin, ymin, xmax, ymax);
    outcode2 = line_clip_outcode(x2, y2, xmin, ymin, xmax, ymax);
    if ((outcode1 & outcode2) != 0)
      return FALSE; /* trivial rejection */
    else if ((outcode1 == 0) && (outcode2 == 0))
      break; /* trivial acceptance */
    if (outcode1 == 0)
    {
      tmp = x1;
      x1 = x2;
      x2 = tmp;
      tmp = y1;
      y1 = y2;
      y2 = tmp;
      outcode1 = outcode2;  /* we don't reference outcode2 in the rest of the loop */
    }
    if (outcode1 & 0x8)
    {
      x1 += M(x2 - x1, D(ymin - y1, y2 - y1));
      y1 = ymin;
    }
    else if (outcode1 & 0x4)
    {
      x1 += M(x2 - x1, D(ymax - ONE - y1, y2 - y1));
      y1 = ymax - ONE;
    }
    else if (outcode1 & 0x2)
    {
      y1 += M(y2 - y1, D(xmax - ONE - x1, x2 - x1));
      x1 = xmax - ONE;
    }
    else if (outcode1 & 0x1)
    {
      y1 += M(y2 - y1, D(xmin - x1, x2 - x1));
      x1 = xmin;
    }
  }
  output[0] = x1;
  output[1] = y1;
  output[2] = x2;
  output[3] = y2;
  return TRUE;
}

static BOOL internal_line(PDCTXT pdctxt, INT32 x1, INT32 y1, INT32 x2, INT32 y2)
{
  INT32 buffer[4];
  if (line_clip(buffer, x1 << CPX, y1 << CPX, x2 << CPX, y2 << CPX, pdctxt->cliprect.left << CPX, pdctxt->cliprect.top << CPX,
				pdctxt->cliprect.right << CPX, pdctxt->cliprect.bottom << CPX))
    return (*pdctxt->funcs->line)(pdctxt->privdata, buffer[0] >> CPX, buffer[1] >> CPX, buffer[2] >> CPX, buffer[3] >> CPX, pdctxt->color, pdctxt->rop2);
  return TRUE;
}

PDCTXT _DC_Allocate(PDCFUNTABLE funcs, PVOID privdata)
{
  PDCTXT rc = (PDCTXT)malloc(sizeof(DCTXT));
  if (!rc)
    return NULL;
  memset(rc, 0, sizeof(DCTXT));
  _Go_init(&(rc->hdr), DCTXT_SIG_WORD, sizeof(DCTXT));
  rc->funcs = funcs;
  rc->privdata = privdata;
  rc->rop2 = R2_COPYPEN;
  return rc;
}

void _DC_FinalizeCommon(PDCTXT pdctxt)
{
  if (pdctxt->cur_bitmap)
    Go_release(&(pdctxt->cur_bitmap->hdr));
}

COLORREF DC_SetPixel(PDCTXT pdctxt, INT32 x, INT32 y, COLORREF color)
{
  if (!G_coords_in_rect(&(pdctxt->cliprect), x, y))
    return (COLORREF)(-1);
  return (*(pdctxt->funcs->set_pixel))(pdctxt->privdata, x, y, color, pdctxt->rop2);
}

BOOL DC_LineTo(PDCTXT pdctxt, INT32 x, INT32 y)
{
  BOOL rc = internal_line(pdctxt, pdctxt->pos.x, pdctxt->pos.y, x, y);
  if (rc)
  {
    pdctxt->pos.x = x;
    pdctxt->pos.y = y;
  }
  return rc;
}

BOOL DC_MoveTo(PDCTXT pdctxt, INT32 x, INT32 y, PPOINT oldpt)
{
  if (oldpt)
    memcpy(oldpt, &(pdctxt->pos), sizeof(POINT));
  pdctxt->pos.x = x;
  pdctxt->pos.y = y;
  return TRUE;
}

BOOL DC_Rectangle(PDCTXT pdctxt, INT32 left, INT32 top, INT32 right, INT32 bottom)
{
  internal_line(pdctxt, left, top, right - 1, top);
  internal_line(pdctxt, left, top + 1, left, bottom - 2);
  internal_line(pdctxt, right - 1, top + 1, right - 1, bottom - 2);
  internal_line(pdctxt, left, bottom - 1, right - 1, bottom - 1);
  return TRUE;
}

BOOL DC_SolidRectangle(PDCTXT pdctxt, INT32 left, INT32 top, INT32 right, INT32 bottom)
{
  RECT rect, actual;
  G_set_rect(&rect, left, top, right, bottom);
  if (!G_rect_intersect(&actual, &rect, &(pdctxt->cliprect)))
    return TRUE;
  return (*(pdctxt->funcs->solid_rect))(pdctxt->privdata, &actual, pdctxt->color, pdctxt->rop2);
}

UINT32 DC_GetROP2(PDCTXT pdctxt)
{
  return pdctxt->rop2;
}

UINT32 DC_SetROP2(PDCTXT pdctxt, UINT32 rop)
{
  UINT32 rc = pdctxt->rop2;
  pdctxt->rop2 = rop;
  return rc;
}

COLORREF DC_GetTextColor(PDCTXT pdctxt)
{
  return pdctxt->color;
}

COLORREF DC_SetTextColor(PDCTXT pdctxt, COLORREF cr)
{
  COLORREF rc = pdctxt->color;
  pdctxt->color = cr;
  return rc;
}

BOOL DC_GetClipRect(PDCTXT pdctxt, PRECT prect)
{
  memcpy(prect, &(pdctxt->cliprect), sizeof(RECT));
  return TRUE;
}

BOOL DC_SetClipRect(PDCTXT pdctxt, PRECT prect)
{
  memcpy(&(pdctxt->cliprect), prect, sizeof(RECT));
  return TRUE;
}

PDCTXT DC_CreateCompatible(PDCTXT pdctxt)
{
  if (pdctxt)
    return (*(pdctxt->funcs->create_compat))(pdctxt->privdata);
  return _DC_CreateScreenCompatibleContext();
}

PGFXOBJECT DC_SelectObject(PDCTXT pdctxt, PGFXOBJECT pobj)
{
  if (pobj->sig == BMP_SIG_WORD)
  {
    if ((pdctxt->flags & DCFLG_TYPES) == DCFLG_IS_MEMORY)
    {
      RECT rtmp;
      PBITMAP rbmp = pdctxt->cur_bitmap;
      Go_addref(pobj);
      if ((*(pdctxt->funcs->new_bitmap))(pdctxt->privdata, (PBITMAP)pobj))
      {
        pdctxt->cur_bitmap = (PBITMAP)pobj;
        pdctxt->baserect.left = pdctxt->baserect.top = 0;
        pdctxt->baserect.right = ((PBITMAP)pobj)->width;
        pdctxt->baserect.bottom = ((PBITMAP)pobj)->height;
        G_rect_intersect(&rtmp, &(pdctxt->baserect), &(pdctxt->cliprect));
        memcpy(&(pdctxt->cliprect), &rtmp, sizeof(RECT));
        return (PGFXOBJECT)rbmp;
      }
      Go_release(pobj);
    }
  }
  return NULL;
}

BOOL DC_BitBlt(PDCTXT dest, INT32 x, INT32 y, INT32 width, INT32 height, PDCTXT source, INT32 x1, INT32 y1, UINT32 rop)
{
  RECT destrect, actualdest, srcrect, actualsrc;

  G_set_rect(&destrect, x, y, x + width, y + height);
  if (!G_rect_intersect(&actualdest, &destrect, &(dest->cliprect)))
    return TRUE;  /* no-op */
  G_set_rect(&srcrect, x1, y1, x1 + (actualdest.right - actualdest.left), y1 + (actualdest.bottom - actualdest.top));
  if (!G_rect_intersect(&actualsrc, &srcrect, &(source->baserect)))
    return TRUE;
  actualdest.right = actualdest.left + (actualsrc.right - actualsrc.left);
  actualdest.bottom = actualdest.top + (actualsrc.bottom - actualsrc.top);
  return (*(dest->funcs->bitblt))(dest->privdata, &actualdest, source->privdata, &actualsrc, rop);
}
