/*
 * 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>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "wintype.h"
#include "scode.h"
#include "log.h"
#include "devctxt.h"
#include "dc_screen.h"
#include "ep_init.h"
#include "ep_types.h"

static PyObject *devctxt_set_pixel(DevCtxtObject *self, PyObject *args)
{
  INT32 x, y;
  COLORREF color, rc;

  if (!PyArg_ParseTuple(args, "iik", &x, &y, &color))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  rc = DC_SetPixel(self->pdctxt, x, y, color);
  return PyLong_FromUnsignedLong(rc);
}

static PyObject *devctxt_line_to(DevCtxtObject *self, PyObject *args)
{
  INT32 x, y;
  BOOL rc;

  if (!PyArg_ParseTuple(args, "ii", &x, &y))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  rc = DC_LineTo(self->pdctxt, x, y);
  return PyBool_FromLong(rc);
}

static PyObject *devctxt_move_to(DevCtxtObject *self, PyObject *args)
{
  INT32 x, y;
  BOOL rc;

  if (!PyArg_ParseTuple(args, "ii", &x, &y))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  rc = DC_MoveTo(self->pdctxt, x, y, NULL);
  return PyBool_FromLong(rc);
}

static PyObject *devctxt_rectangle(DevCtxtObject *self, PyObject *args)
{
  INT32 left, top, right, bottom;
  BOOL rc;

  if (!PyArg_ParseTuple(args, "iiii", &left, &top, &right, &bottom))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  rc = DC_Rectangle(self->pdctxt, left, top, right, bottom);
  return PyBool_FromLong(rc);
}

static PyObject *devctxt_solid_rectangle(DevCtxtObject *self, PyObject *args)
{
  INT32 left, top, right, bottom;
  BOOL rc;

  if (!PyArg_ParseTuple(args, "iiii", &left, &top, &right, &bottom))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  rc = DC_SolidRectangle(self->pdctxt, left, top, right, bottom);
  return PyBool_FromLong(rc);
}

static PyObject *devctxt_bitblt(DevCtxtObject *self, PyObject *args)
{
  INT32 x, y, width, height, x1, y1;
  UINT32 rop;
  DevCtxtObject *source;
  BOOL rc;

  if (!PyArg_ParseTuple(args, "iiiiO!iik", &x, &y, &width, &height, &DevCtxtType, &source, &x1, &y1, &rop))
    return NULL;
  if (!(self->pdctxt) || !(source->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }

  rc = DC_BitBlt(self->pdctxt, x, y, width, height, source->pdctxt, x1, y1, rop);
  return PyBool_FromLong(rc);
}

static PyObject *devctxt_get_clip_rect(DevCtxtObject *self, PyObject *args)
{
  RECT rect;

  if (!PyArg_ParseTuple(args, ""))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  DC_GetClipRect(self->pdctxt, &rect);
  return Py_BuildValue("(i,i,i,i)", rect.left, rect.top, rect.right, rect.bottom);
}

static PyObject *devctxt_set_clip_rect(DevCtxtObject *self, PyObject *args)
{
  RECT rect;
  BOOL rc;

  if (!PyArg_ParseTuple(args, "iiii", &(rect.left), &(rect.top), &(rect.right), &(rect.bottom)))
    return NULL;
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  rc = DC_SetClipRect(self->pdctxt, &rect);
  return PyBool_FromLong(rc);
}

static PyObject *devctxt_select_bitmap(DevCtxtObject *self, BitmapObject *newbmp)
{
  BitmapObject *old_bitmap = NULL;
  PBITMAP old_pbmp;

  if ((self->pdctxt->flags & DCFLG_TYPES) != DCFLG_IS_MEMORY)
  {
    PyErr_SetString(PyExc_RuntimeError, "must select bitmap into memory device context");
    return NULL;
  }
  old_bitmap = self->selected_bitmap;
  old_pbmp = (PBITMAP)DC_SelectObject(self->pdctxt, (PGFXOBJECT)(newbmp->pbmp));
  if (!old_bitmap)
  {
    old_bitmap = (BitmapObject *)Epython_wrap_bitmap(old_pbmp);
    if (!old_bitmap)
    {
      DC_SelectObject(self->pdctxt, (PGFXOBJECT)old_pbmp);
      return NULL;
    }
    else
    {
      Py_INCREF(old_bitmap);
      self->selected_bitmap = old_bitmap;
    }
  }
  ASSERT(old_bitmap);
  ASSERT(self->selected_bitmap);
  ASSERT(newbmp);
  Py_DECREF(self->selected_bitmap);
  self->selected_bitmap = newbmp;
  Py_INCREF(self->selected_bitmap);
  return (PyObject *)old_bitmap;
}

static PyObject *devctxt_select_object(DevCtxtObject *self, PyObject *args)
{
  PyObject *obj;

  if (!PyArg_ParseTuple(args, "O", &obj))
    return NULL;
  if (!obj)
  {
    PyErr_SetString(PyExc_RuntimeError, "bad object selected");
    return NULL;
  }
  if (PyObject_TypeCheck(obj, &BitmapType))
    return devctxt_select_bitmap(self, (BitmapObject *)obj);
  PyErr_SetString(PyExc_RuntimeError, "unknown type of object selected");
  return NULL;
}

static PyMethodDef DevCtxtMethods[] = {
  {"set_pixel", (PyCFunction)devctxt_set_pixel, METH_VARARGS,
   "Sets a single pixel on the display."},
  {"line_to", (PyCFunction)devctxt_line_to, METH_VARARGS,
   "Draws a line from the current position to the specified location."},
  {"move_to", (PyCFunction)devctxt_move_to, METH_VARARGS,
   "Draws a line from the current position to the specified location."},
  {"rectangle", (PyCFunction)devctxt_rectangle, METH_VARARGS,
   "Draws a rectangle."},
  {"solid_rectangle", (PyCFunction)devctxt_solid_rectangle, METH_VARARGS,
   "Draws a solid rectangle."},
  {"bitblt", (PyCFunction)devctxt_bitblt, METH_VARARGS,
   "Copy bits from one device context to another."},
  {"select_object", (PyCFunction)devctxt_select_object, METH_VARARGS,
   "Selects a graphic object into the device context."},
  {"get_clip_rect", (PyCFunction)devctxt_get_clip_rect, METH_VARARGS,
   "Returns the current clipping rectangle of the device context."},
  {"set_clip_rect", (PyCFunction)devctxt_set_clip_rect, METH_VARARGS,
   "Sets the current clipping rectangle of the device context."},
  {NULL, NULL, 0, NULL}
};

static PyObject *devctxt_get_rop2(DevCtxtObject *self, void *closure)
{
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  return PyLong_FromUnsignedLong(DC_GetROP2(self->pdctxt));
}

static int devctxt_set_rop2(DevCtxtObject *self, PyObject *value, void *closure)
{
  UINT32 v;

  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return -1;
  }
  if (value == NULL)
  {
    PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
    return -1;
  }
  v = PyLong_AsUnsignedLong(value);
  if (PyErr_Occurred())
    return -1;
  DC_SetROP2(self->pdctxt, v);
  return 0;
}

static PyObject *devctxt_get_text_color(DevCtxtObject *self, void *closure)
{
  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return NULL;
  }
  return PyLong_FromUnsignedLong(DC_GetTextColor(self->pdctxt));
}

static int devctxt_set_text_color(DevCtxtObject *self, PyObject *value, void *closure)
{
  COLORREF v;

  if (!(self->pdctxt))
  {
    PyErr_SetString(PyExc_RuntimeError, "bad device context");
    return -1;
  }
  if (value == NULL)
  {
    PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
    return -1;
  }
  v = PyLong_AsUnsignedLong(value);
  if (PyErr_Occurred())
    return -1;
  DC_SetTextColor(self->pdctxt, v);
  return 0;
}

static PyGetSetDef DevCtxtProperties[] = {
  {"rop2", (getter)devctxt_get_rop2, (setter)devctxt_set_rop2,
   "Current raster operation", NULL},
  {"text_color", (getter)devctxt_get_text_color, (setter)devctxt_set_text_color,
   "Current text color", NULL},
  {NULL, NULL, NULL, NULL, NULL}
};

static void devctxt_dealloc(DevCtxtObject *self)
{
  if (self->pdctxt)
    DC_release(self->pdctxt);
  Py_TYPE(self)->tp_free((PyObject *)self);
}

static int devctxt_init(DevCtxtObject *self, PyObject *args, PyObject *kwds)
{
  static char *kwlist[] = { "type", NULL };
  const char *type;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "$s", kwlist, &type))
    return -1;
  if (strcmp(type, "screen") == 0)
  {
    self->pdctxt = DC_CreateScreenContext();
    if (!(self->pdctxt))
    {
      PyErr_SetString(PyExc_RuntimeError, "unable to create screen context");
      return -1;
    }
  }
  else if (strcmp(type, "memory") == 0)
  {
    self->pdctxt = _DC_CreateScreenCompatibleContext();
    if (!(self->pdctxt))
    {
      PyErr_SetString(PyExc_RuntimeError, "unable to create memory context");
      return -1;
    }
  }
  else
  {
    PyErr_Format(PyExc_RuntimeError, "invalid type '%s'", type);
    return -1;
  }
  return 0;
}

PyTypeObject DevCtxtType = {
  PyVarObject_HEAD_INIT(NULL, 0)
  .tp_name = "upiwin.DevCtxt",
  .tp_doc = "Device context object",
  .tp_basicsize = sizeof(DevCtxtObject),
  .tp_itemsize = 0,
  .tp_flags = Py_TPFLAGS_DEFAULT,
  .tp_new = PyType_GenericNew,
  .tp_init = (initproc)devctxt_init,
  .tp_dealloc = (destructor)devctxt_dealloc,
  .tp_methods = DevCtxtMethods,
  .tp_getset = DevCtxtProperties,
};

HRESULT Epython_register_devctxt(PyObject *module)
{
  if (PyType_Ready(&DevCtxtType) < 0)
	return E_FAIL;
  Py_INCREF(&DevCtxtType);
  if (PyModule_AddObject(module, "DevCtxt", (PyObject *)(&DevCtxtType)) < 0)
  {
    Py_DECREF(&DevCtxtType);
    return E_FAIL;
  }
  return S_OK;
}
