/**
 *
 * ChoX11: XLib replacement for RISC OS
 *
 * Font handling
 *
 * Copyright 2003 by Peter Naulls
 * Written by Peter Naulls and Alan Buckley
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation. No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 */


#define XLIB_ILLEGAL_ACCESS
#define NeedFunctionPrototypes 1
#define NeedNestedPrototypes   1

#include <X11/Xlib.h>
#include <X11/Xutil.h> 

#include <stdio.h>
#include <ctype.h>

#include <kernel.h>

#include "chox11.h"

/* Setup translation buffers for the sprite plotting */
#ifndef SWI_ColourTrans_GenerateTable
#define SWI_ColourTrans_GenerateTable 0x40763
#endif


/* Structure to hold cursor data */
typedef struct {
   sprite_areainfo *pointer;
   int hot_x;
   int hot_y;
} Chox_Cursor;

Cursor current_cursor = None;

/* Actual colours will be read before we change the pointer */
unsigned char default_pointer_palette[3][5] = {
  {1, 25, 255, 255, 255},
  {2, 25, 255, 255, 255},
  {3, 25, 255, 255, 255}
};
  

/* Set the pointer shape for the current window */
 
void Chox11Cursor_SetPointer(XDrawable *window) {
  while (window != &Chox11_root_window && window->data.window.cursor == None) {
     window = window->data.window.parent.draw;
  }
  if (window == &Chox11_root_window) {
    /* Root uses default desktop cursor */
    Chox11Cursor_UnsetPointer();
  } else {
    Chox_Cursor *chox_cursor = (Chox_Cursor *)window->data.window.cursor;
    _kernel_swi_regs regs;
    static Bool exit_handler_installed = False;

    if (!exit_handler_installed) {
      atexit(Chox11Cursor_UnsetPointer);
      exit_handler_installed = True;
    }

    if (current_cursor == None) {
      /* Save pointer palette to be restore when we are no longer
         defining a pointer */
      int colour;
      for (colour = 0; colour < 3; colour++) {
	  regs.r[0] = (int)default_pointer_palette[colour][0];
        regs.r[1] = 25;
        /* Read settings with OS_ReadPalette */
        if (_kernel_swi(0x2f, &regs, &regs) == NULL) {
          default_pointer_palette[colour][2] = (unsigned char)((regs.r[2] >> 8) & 0xFF);
          default_pointer_palette[colour][3] = (unsigned char)((regs.r[2] >> 16) & 0xFF);
          default_pointer_palette[colour][4] = (unsigned char)((regs.r[2] >> 24) & 0xFF);
        }
      }
    }

    /* Now set the pointer from the sprite */
    regs.r[0] = 36 + 256;
    regs.r[1] = (int)chox_cursor->pointer;
    regs.r[2] = (int)X_SPRITE_NAME;
    regs.r[3] = 2; /* Define as pointer shape 2 */
    regs.r[4] = chox_cursor->hot_x;
    regs.r[5] = chox_cursor->hot_y;
    regs.r[6] = 0; /*scale factors (0 to scale for the mode)*/
    regs.r[7] = 0; /* No pixel translation */
    
    if (_kernel_swi(SWI_OS_SpriteOp, &regs, &regs) == NULL) {
      /* Switch to our new pointer shape */
      current_cursor = (Cursor)chox_cursor;
    }
  }
}

void Chox11Cursor_UnsetPointer() {
  if (current_cursor != None) {
    int colour;

    /* Reset to pointer shape 1 */
    _kernel_osbyte(106, 1, 0);

    /* Reset pointer colours */
    for (colour = 0; colour < 3; colour++) {
      _kernel_osword(12, (int *)default_pointer_palette[colour]);
    }

    current_cursor = None;
  }
}

/* Create a blank sprite for the cursor - colours are specified in RISCOS palette format */
static sprite_areainfo *CreateCursorSprite(int width, int height, unsigned int foreground, unsigned int background) {
  int size = sizeof(sprite_areainfo) + Sprite_MemorySizeBpp(width, height, 2, sprite_HASPAL);
  sprite_areainfo *cursor_sprite = (sprite_areainfo *)calloc(size, 1);
  screen_modeval mode;
  unsigned int *palette_data;

  mode.sprite_mode.istype   = 1;
  mode.sprite_mode.horz_dpi = 90;
  mode.sprite_mode.vert_dpi = 90;
  mode.sprite_mode.type = 2;

  cursor_sprite->areasize    = size;
  cursor_sprite->numsprites  = 0;
  cursor_sprite->firstoffset = 16;
  cursor_sprite->freeoffset  = 16;

  Sprite_Create(cursor_sprite, X_SPRITE_NAME, 1, width, height, mode);

  /* Set up initial palette */
  palette_data = (unsigned int *)cursor_sprite;
  palette_data += (sizeof(sprite_areainfo) + sizeof(sprite_header)) / sizeof(int);

  *palette_data++ = 0;
  *palette_data++ = 0;
  *palette_data++ = background;
  *palette_data++ = background;
  *palette_data++ = foreground;
  *palette_data++ = foreground;
  *palette_data++ = 0xFF000000;
  *palette_data++ = 0xFF000000;

  return cursor_sprite;
}


Cursor Chox11Cursor_CreateFontCursor(int which) {
  Chox_Cursor *chox_cursor = (Chox_Cursor *)calloc(sizeof(Chox_Cursor), 1);
  int *sprite_data, *source_data, *mask_data;
  int word;

  chox_cursor->pointer = CreateCursorSprite(16,16, 0xFFFFFF00, 0);

  if (which < 0 || which >= CURSOR_FONT_SIZE-1) which = 0;

  source_data = Chox11_CursorFont[which];
  mask_data = Chox11_CursorFont[which+1];
  sprite_data = (int *)chox_cursor->pointer;
  /* +8 below is size of palette */
  sprite_data += ((sizeof(sprite_areainfo) + sizeof(sprite_header)) / sizeof(int)) + 8;

  chox_cursor->hot_x = (*source_data) >> 8;
  chox_cursor->hot_y = (*source_data) & 0xFF;
  source_data++;
  mask_data++;

  for (word = 0; word < 16; word++) {
    *sprite_data++ = (*mask_data++) & (*source_data++ ^ 0xAAAAAAAA);
  }
  
  return (Cursor)chox_cursor;
}


Cursor XCreatePixmapCursor(
    Display *	 display,
    Pixmap 	 source,
    Pixmap 		 mask,
    XColor *	 foreground_color,
    XColor *	 background_color,
    unsigned int 	 x,
    unsigned int 	 y) {
  XDrawable *source_draw = (XDrawable *)source;
  sprite_areainfo *source_sprite = &(source_draw->data.image.pixmap);
  sprite_areainfo *cursor_sprite;
  sprite_outputstate previous;
  Chox_Cursor *chox_cursor = (Chox_Cursor *)calloc(sizeof(Chox_Cursor), 1);
  unsigned int foreground = ((foreground_color->blue & 0xFF00) << 16)
                            | ((foreground_color->green & 0xFF00) << 8)
                            | (foreground_color->red & 0xFF00);
  unsigned int background = ((background_color->blue & 0xFF00) << 16)
                            | ((background_color->green & 0xFF00) << 8)
                            | (background_color->red & 0xFF00); 

  cursor_sprite = CreateCursorSprite(source_draw->width, source_draw->height, foreground, background);

  Sprite_Redirect(cursor_sprite, X_SPRITE_NAME, NULL, &previous);

  if (mask == None || mask == source) {
    _kernel_swi_regs regs;
    char pixtrans[2];
    pixtrans[0] = 0;
    pixtrans[1] = 2; /* Map 1bpp pixel foreground to colour 2 */

    regs.r[0] =  52 + 256;
    regs.r[1] = (unsigned int)source_sprite;
    regs.r[2] = (unsigned int)X_SPRITE_NAME;
    regs.r[3] = 0;
    regs.r[4] = 0;
    regs.r[5] = 0; /* plot action */
    regs.r[6] = 0; /* No scale factors i.e. 1:1 */
    regs.r[7] = (int)pixtrans;

    _kernel_swi(SWI_OS_SpriteOp, &regs, &regs);
  } else {
    _kernel_swi_regs regs;
    char pixtrans[2];
    pixtrans[0] = 0;
    pixtrans[1] = 3;

    /* Plot mask */
    regs.r[0] =  52 + 256;
    regs.r[1] = (unsigned int)(&(((XDrawable *)mask)->data.image.pixmap));
    regs.r[2] = (unsigned int)X_SPRITE_NAME;
    regs.r[3] = 0;
    regs.r[4] = 0;
    regs.r[5] = 0; /* plot action */
    regs.r[6] = 0; /* No scale factors i.e. 1:1 */
    regs.r[7] = (int)pixtrans;
    _kernel_swi(SWI_OS_SpriteOp, &regs, &regs);

    /* Plot source */   
    pixtrans[0] = 1; /* Set pixels so ANDing with mask will give correct result */
    pixtrans[1] = 2;

    regs.r[0] =  52 + 256;
    regs.r[1] = (unsigned int)source_sprite;
    regs.r[2] = (unsigned int)X_SPRITE_NAME;
    regs.r[3] = 0;
    regs.r[4] = 0;
    regs.r[5] = 2; /* plot action - and */
    regs.r[6] = 0; /* No scale factors i.e. 1:1 */
    regs.r[7] = (int)pixtrans;

    _kernel_swi(SWI_OS_SpriteOp, &regs, &regs);
  }

  Sprite_UnRedirect(&previous);

  chox_cursor->pointer = cursor_sprite;
  chox_cursor->hot_x = x;
  chox_cursor->hot_y = y;

  return (Cursor)chox_cursor;
}

extern Cursor XCreateGlyphCursor(
    Display*		 display,
    Font		 source_font,
    Font		 mask_font,
    unsigned int	 source_char,
    unsigned int	 mask_char,
    XColor _Xconst*	 foreground_color,
    XColor _Xconst*	 background_color
) {
  Chox_Cursor *chox_cursor = (Chox_Cursor *)calloc(sizeof(Chox_Cursor), 1);
  font_handle source_handle = ((Chox_Font *)source_font)->handle;
  font_info source_info;
  sprite_areainfo *cursor_sprite;
  unsigned int foreground = ((foreground_color->blue & 0xFF00) << 16)
                            | ((foreground_color->green & 0xFF00) << 8)
                            | (foreground_color->red & 0xFF00);
  unsigned int background = ((background_color->blue & 0xFF00) << 16)
                            | ((background_color->green & 0xFF00) << 8)
                            | (background_color->red & 0xFF00); 
  sprite_outputstate previous;

  Font_CharBBox(source_handle, source_char, font_OSCOORS, &source_info);

  if (mask_font == None || (mask_font == source_font && mask_char == source_char)) {
    /* Simple case - single character from a font */
    char c = (char)source_char;

    cursor_sprite = CreateCursorSprite((source_info.maxx - source_info.minx)/2+1, (source_info.maxy - source_info.miny)/2+1, foreground, background);
    Sprite_Redirect(cursor_sprite, X_SPRITE_NAME, NULL, &previous);

    /* Use Font_SetFontColours as we want to use specific logical colours */
    SWI(3,0, 0x40092, source_handle, 0, 2, 0);

    Font_Paint3(source_handle,
                      &c,
                      font_plot_OSCOORS | font_STRLEN | font_plot_GIVENHANDLE,
                      -source_info.minx,
                      -source_info.miny,
                      NULL, NULL,
                      1);

    Sprite_UnRedirect(&previous);
  } else {
    font_handle mask_handle = ((Chox_Font *)source_font)->handle;
    font_info mask_info;
    char c;

    /* Calculate bounding box for both characters */
    Font_CharBBox(mask_handle, mask_char, font_OSCOORS, &mask_info);
    if (mask_info.minx < source_info.minx) source_info.minx = mask_info.minx;
    if (mask_info.maxx > source_info.maxx) source_info.maxx = mask_info.maxx;
    if (mask_info.miny < source_info.miny) source_info.miny = mask_info.miny;
    if (mask_info.maxy > source_info.maxy) source_info.maxy = mask_info.maxy;

    cursor_sprite = CreateCursorSprite((source_info.maxx - source_info.minx)/2+1, (source_info.maxy - source_info.miny)/2+1, foreground, background);
    Sprite_Redirect(cursor_sprite, X_SPRITE_NAME, NULL, &previous);

    /* First paint mask character */
    c = (char)mask_char;

    /* Use Font_SetFontColours as we want to use specific logical colours */
    SWI(3,0, 0x40092, mask_handle, 0, 1, 0);

    Font_Paint3(mask_handle,
                      &c,
                      font_plot_OSCOORS | font_STRLEN | font_plot_GIVENHANDLE,
                      -source_info.minx,
                      -source_info.miny,
                      NULL, NULL,
                      1);

    /* Now overpaint with the source character */
    c = (char)source_char;

    /* Use Font_SetFontColours as we want to use specific logical colours */
    SWI(3,0, 0x40092, source_handle, 0, 2, 0);

    Font_Paint3(source_handle,
                      &c,
                      font_plot_OSCOORS | font_STRLEN | font_plot_GIVENHANDLE,
                      source_info.minx,
                      -source_info.miny,
                      NULL, NULL,
                      1);

    Sprite_UnRedirect(&previous);
  }

  chox_cursor->pointer = cursor_sprite;
  chox_cursor->hot_x = source_info.minx/2;
  chox_cursor->hot_y = source_info.maxy/2; /* Position is from the top */

  return (Cursor)chox_cursor;
}

extern XFreeCursor(

    Display*		 display,
    Cursor		 cursor

) {
  Chox_Cursor *chox_cursor = (Chox_Cursor *)cursor;
  free(chox_cursor->pointer);
  free((void *)cursor);
}

extern Status XQueryBestCursor(

    Display*		 display,
    Drawable		 d,
    unsigned int         width,
    unsigned int	 height,
    unsigned int*	 width_return,
    unsigned int*	 height_return

) {
  *width_return = 16;
  *height_return = 16;

  return 0;
}

extern XRecolorCursor(
    Display*		 display,
    Cursor		 cursor,
    XColor*		 foreground_color,
    XColor*		 background_color

) {
  Chox_Cursor *chox_cursor = (Chox_Cursor *)cursor;
  unsigned int *palette_data;
  unsigned int foreground = ((foreground_color->blue & 0xFF00) << 16)
                            | ((foreground_color->green & 0xFF00) << 8)
                            | (foreground_color->red & 0xFF00);
  unsigned int background = ((background_color->blue & 0xFF00) << 16)
                            | ((background_color->green & 0xFF00) << 8)
                            | (background_color->red & 0xFF00); 

  palette_data = (unsigned int *)chox_cursor->pointer;
  palette_data += (sizeof(sprite_areainfo) + sizeof(sprite_header)) / sizeof(int);
  palette_data+= 2; /* Skip clear entry */
  *palette_data++ = background;
  *palette_data++ = background;
  *palette_data++ = foreground;
  *palette_data++ = foreground;

   if (cursor == current_cursor) {
     /* Reflect new colours in cursor if its showing */
     _kernel_swi_regs regs;
     regs.r[0] = 36 + 256;
     regs.r[1] = (int)chox_cursor->pointer;
     regs.r[2] = (int)X_SPRITE_NAME;
     regs.r[3] = 2; /* Define as pointer shape 2 */
     regs.r[4] = chox_cursor->hot_x;
     regs.r[5] = chox_cursor->hot_y;
     regs.r[6] = 0; /*scale factors (0 to scale for the mode)*/
     regs.r[7] = 0; /* No pixel translation */
    
     _kernel_swi(SWI_OS_SpriteOp, &regs, &regs);
   }
}


extern XDefineCursor(

    Display*		 display,
    Window		 w,
    Cursor		 cursor

) {
  XDrawable *draw = (XDrawable *)w;
  mouse_block pointer;

  draw->data.window.cursor = cursor;

  Wimp_GetPointerInfo(&pointer);
  if (pointer.window == draw->data.window.handle) {
     Chox11Cursor_SetPointer(draw);
  }
}


extern XUndefineCursor(

    Display*		 display,
    Window		 w

) {
  XDefineCursor(display, w, None);
}


extern XWarpPointer(

    Display*		 display,
    Window		 src_w,
    Window		 dest_w,
    int			 src_x,
    int			 src_y,
    unsigned int	 src_width,
    unsigned int	 src_height,
    int			 dest_x,
    int			 dest_y	     

) {
  BOOL ok_to_move = True;
  convert_block coords;
  mouse_block pointer;
 
  if (src_w != None || dest_w == None) Wimp_GetPointerInfo(&pointer);

  if (src_w != None) {
    /* Check the pointers in the source window rectangle */
    XDrawable *src_draw = (XDrawable *)src_w;
    window_handle src_handle = src_draw->data.window.handle;
    if (pointer.window == src_handle) {
      wimp_rect src_rect;
      src_rect.min.x = src_x * 2;
      src_rect.max.y = -src_y * 2;
      if (src_width == 0) src_rect.max.x = (src_draw->width - src_x) * 2;
      else src_rect.max.x = (src_x + src_width) * 2;
      if (src_height == 0) src_rect.min.y = -(src_draw->height - src_y) * 2;
      else src_rect.min.y = -(src_y + src_height) * 2;

      /* Adjust for border */
      src_rect.min.x += src_draw->data.window.border_width;
      src_rect.min.y -= src_draw->data.window.border_width;
      src_rect.max.x += src_draw->data.window.border_width;
      src_rect.max.y -= src_draw->data.window.border_width;

      Window_GetCoords(src_draw->data.window.handle, &coords);
      Coord_RectToScreen(&src_rect, &coords);

      ok_to_move = Coord_PointInRect(&pointer.pos, &src_rect);
    } else {
      ok_to_move = False;
    }
  }

  if (ok_to_move) {
    _kernel_swi_regs regs;
    char block[5];
    int new_x, new_y;

    if (dest_w == None) {
      new_x = pointer.pos.x + dest_x * 2;
      new_y = pointer.pos.y - dest_y * 2;
    } else {
      XDrawable *dest_draw = (XDrawable *)dest_w;
      Window_GetCoords(dest_draw->data.window.handle, &coords);
      new_x = Coord_XToScreen(dest_x * 2 + dest_draw->data.window.border_width, &coords);
      new_y = Coord_YToScreen(-dest_y * 2 - dest_draw->data.window.border_width, &coords);
    }

    block[0] = 3;
    block[1] = new_x & 0xFF;
    block[2] = (new_x >> 8) & 0xFF;
    block[3] = new_y & 0xFF;
    block[4] = (new_y >> 8) & 0xFF;

    regs.r[0] = 21;
    regs.r[1] = (int)block;
    _kernel_swi(SWI_OS_Word, &regs, &regs);
  }
}
