/**
 *
 * ChoX11: XLib replacement for RISC OS
 *
 * Resource, context, property and atom managers
 *
 * Copyright 2003 by Peter Naulls
 * Written by Peter Naulls and Chris Williams
 *
 * 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 NeedFunctionPrototypes 1
#define NeedNestedPrototypes   1

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

#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>

#include "chox11.h"

/* Default Atom names - why this isn't defined by XLib headers, I don't know */
unsigned char *Chox11_Default_Atoms[] = {
  "",
  "XA_PRIMARY",
  "XA_SECONDARY",
  "XA_ARC",
  "XA_ATOM",
  "XA_BITMAP",
  "XA_CARDINAL",
  "XA_COLORMAP",
  "XA_CURSOR",
  "XA_CUT_BUFFER0",
  "XA_CUT_BUFFER1",
  "XA_CUT_BUFFER2",
  "XA_CUT_BUFFER3",
  "XA_CUT_BUFFER4",
  "XA_CUT_BUFFER5",
  "XA_CUT_BUFFER6",
  "XA_CUT_BUFFER7",
  "XA_DRAWABLE",
  "XA_FONT",
  "XA_INTEGER",
  "XA_PIXMAP",
  "XA_POINT",
  "XA_RECTANGLE",
  "XA_RESOURCE_MANAGER",
  "XA_RGB_COLOR_MAP",
  "XA_RGB_BEST_MAP",
  "XA_RGB_BLUE_MAP",
  "XA_RGB_DEFAULT_MAP",
  "XA_RGB_GRAY_MAP",
  "XA_RGB_GREEN_MAP",
  "XA_RGB_RED_MAP",
  "XA_STRING",
  "XA_VISUALID",
  "XA_WINDOW",
  "XA_WM_COMMAND",
  "XA_WM_HINTS",
  "XA_WM_CLIENT_MACHINE",
  "XA_WM_ICON_NAME",
  "XA_WM_ICON_SIZE",
  "XA_WM_NAME",
  "XA_WM_NORMAL_HINTS",
  "XA_WM_SIZE_HINTS",
  "XA_WM_ZOOM_HINTS",
  "XA_MIN_SPACE",
  "XA_NORM_SPACE",
  "XA_MAX_SPACE",
  "XA_END_SPACE",
  "XA_SUPERSCRIPT_X",
  "XA_SUPERSCRIPT_Y",
  "XA_SUBSCRIPT_X",
  "XA_SUBSCRIPT_Y",
  "XA_UNDERLINE_POSITION",
  "XA_UNDERLINE_THICKNESS",
  "XA_STRIKEOUT_ASCENT",
  "XA_STRIKEOUT_DESCENT",
  "XA_ITALIC_ANGLE",
  "XA_X_HEIGHT",
  "XA_QUAD_WIDTH",
  "XA_WEIGHT",
  "XA_POINT_SIZE",
  "XA_RESOLUTION",
  "XA_COPYRIGHT",
  "XA_NOTICE",
  "XA_FONT_NAME",
  "XA_FAMILY_NAME",
  "XA_FULL_NAME",
  "XA_CAP_HEIGHT",
  "XA_WM_CLASS",
  "XA_WM_TRANSIENT_FOR" };

/* Maintain list of resources allocated to the X client */
static XResourceID **Chox11_resource_list;
static int Chox11_resource_max   = 4;

/* Maintain list of properties associated with windows */
static XProperty **Chox11_property_list;
static int Chox11_property_max   = 1;

/* Maintain list of registered atoms */
static XAtom **Chox11_atom_list;
static int Chox11_atom_max   = 1;

/* Maintain database of contexts */
static XContextEntry **Chox11_context_list;
static int Chox11_context_max   = 4;

/* ------------------------------------------------------------------------ */
/* Context database routines */

void Chox11DB_InitContexts(void) {
  Chox11_context_list = calloc(Chox11_context_max, sizeof(XContextEntry *));
  assert(Chox11_context_list != NULL);
}


int Chox11DB_FindContext(XID rid, XContext context,
                         XContextEntry **data_return) {
  int loop;
  XContextEntry *entry;

  printf("Chox11DB_FindContext: %x\n", context);

  /* Scan through the list looking for a match */
  for (loop = 0; loop < Chox11_context_max; loop++)
    if (Chox11_context_list[loop] != NULL) {
    
      entry = Chox11_context_list[loop];
      
      if (entry->rid == rid && entry->context == context) {
        /* Once a match is found, return the pointer to the client */
        *data_return = entry;
        printf("Chox11DB_FindContext: Found: %p\n", entry);
        return 0;
      }
    }

  /* if there was no match, inform the client */
  puts("Chox11DB_FindContext: Not found");
  *data_return = NULL;
  return XCNOENT;
}


int Chox11DB_StoreContext(XID rid, XContext context, XPointer data) {
  XContextEntry *entry;

  printf("Chox11DB_StoreContext: %x\n", context);

  /* First, check to see if the context already exists */
  if (Chox11DB_FindContext(rid, context, &entry) == NULL) {
    /* Overwrite context details */
    entry->data = data;

  } else {
    int item;

    /* We have to create a new context entry */
    entry = malloc(sizeof(XContextEntry));
    if (entry == NULL) return XCNOMEM;

    /* Scan through to find the first available empty slot */
    for (item = 0; item < Chox11_context_max; item++) {
      if (Chox11_context_list[item] == NULL) {
        break;
      }
    }

    if (item == Chox11_context_max) {
      XContextEntry **new_list;
      int item;

      /* No more free slots in the list, need to extend it */
      new_list = realloc(Chox11_context_list,
                         Chox11_context_max * sizeof(XContextEntry *) * 2);

      /* Report realloc() failure but still keep current list */
      if (new_list == NULL) {
        return XCNOMEM;
      }
      Chox11_context_list = new_list;
      
      /* Double list size and zero second half to mark them as free slots */
      for (item = Chox11_context_max; item < (Chox11_context_max * 2); item++)
        Chox11_context_list[item] = NULL;

      item = Chox11_context_max;
      Chox11_context_max *= 2;

    }

    /* Store context's details */
    entry->rid     = rid;
    entry->context = context;
    entry->data    = data;
    Chox11_context_list[item] = entry;
  }

  return NULL;
}


int Chox11DB_RemoveContext(XID rid, XContext context) {
  int find;

  printf("Chox11DB_RemoveContext: %x\n", context);

  /* Find context entry in list and remove */
  for (find = 0; find < Chox11_context_max; find++) {
    XContextEntry *entry = Chox11_context_list[find];

    if (entry->rid == rid && entry->context == context) {
      free(Chox11_context_list[find]);
      Chox11_context_list[find] = NULL;
      return 0;
    }
  }

  return XCNOENT;
}


/* ------------------------------------------------------------------------ */
/* Property database routines */


void Chox11DB_InitProperties(void) {
  Chox11_property_list = calloc(Chox11_property_max, sizeof(XProperty *));
  assert(Chox11_property_list != NULL);

  Chox11_atom_list = calloc(Chox11_atom_max, sizeof(XAtom *));
  assert(Chox11_atom_list != NULL);
}


XProperty *Chox11DB_PropertyFind(Window w, Atom property, Atom type) {
  XProperty *entry;
  int find;

  printf("Chox11DB_PropertyFind: Attempting to find w = %x, atom = %x "
         "type = %i\n", w, property, type);
         
  for (find = 0; find < Chox11_property_max; find++)
    /* Compare stored property details with given details */
    if (Chox11_property_list[find] != NULL) {
      entry = Chox11_property_list[find];
      
      if (property == entry->property && type == entry->type) {
        /* Return pointer to structure on success */
        printf("Chox11DB_PropertyFind: returning found object %p\n", entry);
        return entry;
      }
    }
  
  /* fall through to return a failed search */
  printf("Chox11DB_PropertyFind: failed to find property pointer\n");
  return NULL;  
}


int Chox11DB_PropertyAdd(Window w, Atom property, Atom type, int format,
                         int mode, _Xconst unsigned char *data, int nelements)
{
  XProperty *entry;
  int item;

  /* Allocate space for property structure */
  entry = malloc(sizeof(XProperty));
  if (entry == NULL) return BadAlloc;

  printf("Chox11DB_PropertyAdd: w = %x, atom = %x, will return entry = %p\n",
         w, property, entry);

  /* allocate space for property data */
  /* todo: check format value */
  entry->data = calloc(nelements, Chox11_ConvertBppToBytes(format)); 
  if (entry->data == NULL) return BadAlloc;

  /* fill in property details */
  entry->w         = w;
  entry->property  = property;
  entry->type      = type;
  entry->format    = format;
  entry->mode      = mode;
  entry->nelements = nelements;

  /* Copy property data into privately managed buffer */
  memcpy(entry->data, data, nelements * Chox11_ConvertBppToBytes(format));

  /* Scan through to find the first available empty slot */
  for (item = 0; item < Chox11_property_max; item++) {
    if (Chox11_property_list[item] == NULL) {
      break;
    }
  }

  /* First, ensure that there is enough space in the list for a new
     property. if there isn't then extend the list to make new free slots */
  if (item == Chox11_property_max) {
     XProperty **new_list;

     new_list = realloc(Chox11_property_list,
                        Chox11_property_max * sizeof(XProperty *) * 2);

     /* Report realloc() failure but still keep current list */
     if (new_list == NULL) {
       return BadAlloc;
     }
     Chox11_property_list = new_list;

     /* Double list size and zero second half to mark them as free slots */
     for (item = Chox11_property_max; item < (Chox11_property_max * 2); item++)
       Chox11_property_list[item] = NULL;

     item = Chox11_property_max;
     Chox11_property_max *= 2;
  }

  Chox11_property_list[item] = entry;

  Fortify_CheckAllMemory();
  return 0;
}


int Chox11DB_PropertyRemove(XProperty *entry) {
  int find;

  /* loop through list to mark list pointer as empty */
  for (find = 0; find < Chox11_property_max; find++) {
    if (Chox11_property_list[find] == entry) {
      Chox11_property_list[find] = NULL;
      /* free() all claimed memory */
      free(entry->data);
      free(entry);
      return 0;
    }
  }

  return BadMatch;
}


void Chox11DB_PropertyReplace(XProperty *entry, const unsigned char *data) {
  /* We assume the property retains its data format so we just
     copy the new data */
  printf("Chox11DB_PropertyReplace: entry = %p, data = %p\n",
         entry, data);
  printf("Chox11DB_PropertyReplace: copying %i bytes from %p to %p\n",
         entry->nelements * Chox11_ConvertBppToBytes(entry->format),
         data, entry->data); 

  memcpy(entry->data, data, entry->nelements *
         Chox11_ConvertBppToBytes(entry->format));
}

/* ------------------------------------------------------------------------ */
/* Atom database routines */


XAtom *Chox11DB_AtomAdd(const char *name) {
  XAtom *entry;
  int item;

  /* Allocate space for atom structure and the atom name */
  entry = malloc(sizeof(XAtom));
  if (entry == NULL) return NULL;

  entry->name = strdup(name);
  if (entry->name == NULL) return NULL;

  Fortify_CheckAllMemory();
  printf("Chox11DB_AtomAdd: Adding %s, will return entry = %p\n", name, entry);

  /* Scan through to find the first available empty slot */
  for (item = 0; item < Chox11_atom_max; item++) {
    if (Chox11_atom_list[item] == NULL) {
      break;
    }
  }

  if (item == Chox11_atom_max) {
    XAtom **new_list;

    new_list = realloc(Chox11_atom_list,
                       Chox11_atom_max * sizeof(XAtom *) * 2);

    /* Report realloc() failure but still keep current list */
    if (new_list == NULL) {
      return NULL;
    }

    Chox11_atom_list = new_list;

    /* Double list size and zero second half to mark them as free slots */
    for (item = Chox11_atom_max; item < (Chox11_atom_max * 2); item++)
      Chox11_atom_list[item] = NULL;

    item = Chox11_atom_max;
    Chox11_atom_max *= 2;
  }

  Chox11_atom_list[item] = entry;
  Fortify_CheckAllMemory();

  /* Use atom pointer as the Atom value */
  return entry;
}


XAtom *Chox11DB_AtomFind(const char *name) {
  int find;

  for (find = 0; find < Chox11_atom_max; find++) {
    /* Compare stored name with given atom name */
    if (Chox11_atom_list[find] != NULL) {
      XAtom *entry = Chox11_atom_list[find];
      if (strcmp(name, entry->name) == 0)
      {
        printf("Chox11DB_AtomFind: returning %p from %s\n", entry, name);
        return entry;
      }
    }
  }

  /* Fall through to return a failed search */
  printf("Chox11DB_AtomFind: failed to find atom %s\n", name);
  return NULL;
}


extern char *XGetAtomName(

    Display*		 display,
    Atom		 atom

) {
  int find;

  printf("XGetAtomName: atom = %x\n", atom);

  /* The atom may be one of the default ones */
  if (atom <= XA_LAST_PREDEFINED)
    return Chox11_Default_Atoms[atom];

  for (find = 0; find < Chox11_atom_max; find++)
    if (Chox11_atom_list[find] == (XAtom *)atom)
      return ((XAtom *)atom)->name;
  /* Fall through to return failure */

  return (char *)BadAtom;
}


/* ------------------------------------------------------------------------ */
/* Resource database routines */

XResourceID *Chox11DB_FindResource(XID id) {
  XResourceID *resource;

  /* If XID exists in the list then return pointer to resource's
     internal structure */
  if (id <= Chox11_resource_max && id >= 0) {
    resource = Chox11_resource_list[id];
    if (resource != NULL)
      if (resource->type != CXDeleted) return resource;
  }

  /* return null pointer if XID is invalid */
  return NULL;
}


XID Chox11DB_AllocateResource(void *data, XResourceType type) {
  XResourceID *resource;
  int item;

  /* Allocate space for resource structure */
  resource = malloc(sizeof(XResourceID));
  if (resource == NULL) return BadAlloc | CX_ERROR_BIT;

  /* Now scan through to find the first available empty slot */
   for (item = 0; item < Chox11_resource_max; item++) {
     if (Chox11_resource_list[item] == NULL) {
       break;
     }
   }

  /* First, ensure that there is enough space in the list for a new
     resource. if there isn't then extend the list to make new free slots */
  if (item == Chox11_resource_max) {
    XResourceID **new_list = realloc(Chox11_resource_list,
                            Chox11_resource_max * sizeof(XResourceID *) * 2);

    /* Report realloc() failure but still keep current list */
    if (new_list == NULL) {
      return BadAlloc | CX_ERROR_BIT;
    }

    Chox11_resource_list = new_list;

    /* Double list size and zero second half to mark them as free slots */
    for (item = Chox11_resource_max; item < (Chox11_resource_max * 2); item++)
      Chox11_resource_list[item] = NULL;

    item = Chox11_resource_max;
    Chox11_resource_max *= 2;
  }

  /* Fill in resources details */
  resource->resourceid    = item;
  resource->type          = type;
  resource->internal_data = data;

  /* Save resource entry into list */
  Chox11_resource_list[item] = resource;

  printf("+++ Allocating XID %i to type %i, resource data at %p\n",
         resource->resourceid, resource->type, resource->internal_data);

  return 0;
}


Bool Chox11DB_DeleteResource(XID id) {
  XResourceID *resource;

  /* Check that the resource exists in the first place */
  resource = Chox11DB_FindResource(id);
  
  if (resource != NULL) {
    resource->type = CXDeleted;

    /* This may have to change, we have to let the library core to
       deallocate memory claimed by the resource */
    free(resource->internal_data);
    return True;
  }

  return False;
}
