/***************************************************************************
 * Copyright 1995, Technion, Israel Institute of Technology
 * Electrical Eng, Software Lab.
 * Author:    Michael Veksler.
 ***************************************************************************
 * File:      dde_atom.c
 * Purpose :  atom functionality for DDE
 */
#ifdef CONFIG_IPC

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "dde_atom.h"
#include "shm_main_blk.h"
#include "shm_fragment.h"
#include "ldt.h"
#include "debugtools.h"

DEFAULT_DEBUG_CHANNEL(atom)

typedef struct
{
	WORD        count;
	BYTE        str[1];
} AtomData, *AtomData_ptr;

#define EMPTY   0		   /* empty hash entry */
#define DELETED -1		   /* deleted hash entry */
#define MIN_STR_ATOM 0xfc00

/* OFS2AtomData_ptr: extract AtomData_ptr from ofs */
#define OFS2AtomData_ptr(ofs) ((AtomData*)((int)&main_block->block+(ofs)))

/* OFS2AtomStr: find the string of the atom */
#define OFS2AtomStr(ofs)      (OFS2AtomData_ptr(atom_ofs)->str)

/* offset of an atom according to index */
#define ATOM_OFS(idx) (main_block->atoms[idx])

/* rot_left: rotate (with wrap-around) */
static inline int rot_left(unsigned var,int count)
{
  return (var<<count) | (var>> (sizeof(var)-count));
}
/* find the entry in the atom table for this string */
static int FindHash(LPCSTR str)	   /* ignore str case */
{
  int i,j;
  unsigned hash1,hash2;
  int deleted=-1;		   /* hash for deleted entry */
  int atom_ofs;

  /* get basic hash parameters */
  for (i= hash1= hash2= 0; str[i] ; i++) {
     hash1= rot_left(hash1,5) ^ toupper(str[i]);
     hash2= rot_left(hash2,4) ^ toupper(str[i]);
  }

  hash1%= DDE_ATOMS;
  atom_ofs=ATOM_OFS(hash1);
  switch (atom_ofs) {
    case EMPTY:			   /* empty atom entry */
      return hash1;		   
    case DELETED:		   /* deleted atom entry */
      deleted=hash1;
      break;
    default :			   /* non empty atom entry */
      if (lstrcmpi16( OFS2AtomStr(atom_ofs) , str) == 0)
	 return hash1;		   /* found string in atom table */
  }
  hash2%= DDE_ATOMS-1 ;		   /* hash2=0..(DDE_ATOMS-2) */
  hash2++;			   /* hash2=1..(DDE_ATOMS-1) */

  /* make jumps in the hash table by hash2 steps */
  for (i=hash1+hash2 ; ; i+=hash2) {
     /* i wraps around into j */
     j=i-DDE_ATOMS;
     if (j >= 0)
	i=j;			   /* i wraps around */
     
     if (i==hash1) 
	/* here if covered all hash locations, and got back to beginning */
	return deleted;		   /* return first empty entry - if any */
     atom_ofs=ATOM_OFS(i);
     switch (atom_ofs) {
       case EMPTY:		   /* empty atom entry */
	 return i;		   
       case DELETED:		   /* deleted atom entry */
	 if (deleted < 0)
	    /* consider only the first deleted entry */
	    deleted= i;
	 break;
       default :		   /* nonempty atom entry */
	 if (lstrcmpi16( OFS2AtomStr(atom_ofs) , str) == 0)
	    return i;	   /* found string in atom table */
     }
  }
}

void ATOM_GlobalInit(void)
{
  int i;
  
  for (i=0 ; i < DDE_ATOMS ; i++)
     ATOM_OFS(i)=EMPTY;
}

/***********************************************************************
 *           DDE_GlobalAddAtom
 */

/* important! don't forget to unlock semaphores before return */
ATOM DDE_GlobalAddAtom( SEGPTR name )
{
  int atom_idx;
  int atom_ofs;
  AtomData_ptr ptr;
  ATOM atom;
  char *str;

  /* First check for integer atom */

  if (!HIWORD(name)) return (ATOM)LOWORD(name);

  str = (char *)PTR_SEG_TO_LIN( name );
  if (str[0] == '#')
  {
     ATOM atom= (ATOM) atoi(&str[1]);
     return (atom<MIN_STR_ATOM) ? atom : 0;
  }

  TRACE("(\"%s\")\n",str);

  DDE_IPC_init();		/* will initialize only if needed */
  
  shm_write_wait(main_block->sem);

  atom_idx=FindHash(str);
  atom=(ATOM)0;

  /* use "return" only at the end so semaphore handling is done only once */
  if (atom_idx>=0) {
     /* unless table full and item not found */
     switch (atom_ofs= ATOM_OFS(atom_idx)) {
       case DELETED:
       case EMPTY:		   /* need to allocate new atom */
	 atom_ofs= shm_FragmentAlloc(&main_block->block,
				     strlen(str)+sizeof(AtomData));
	 if (atom_ofs==NIL)
	    break;		   /* no more memory (atom==0) */
	 ATOM_OFS(atom_idx)=atom_ofs;
	 ptr=OFS2AtomData_ptr(atom_ofs);
	 strcpy(ptr->str,str);
	 ptr->count=1;
	 atom=(ATOM)(atom_idx+MIN_STR_ATOM);
	 break;
       default :		   /* has to update existing atom */
	 OFS2AtomData_ptr(atom_ofs)->count++;
	 atom=(ATOM)(atom_idx+MIN_STR_ATOM);
     } /* end of switch */
  } /* end of if */
  shm_write_signal(main_block->sem);
  return atom;
}

/***********************************************************************
 *           DDE_GlobalDeleteAtom
 */

ATOM DDE_GlobalDeleteAtom( ATOM atom )
{
  int atom_idx;
  int atom_ofs;
  AtomData_ptr atom_ptr;
  ATOM retval=(ATOM) 0;
  
  TRACE("(\"%d\")\n",(int)atom);
  atom_idx=(int)atom - MIN_STR_ATOM;
  
  if (atom_idx < 0 )
     return 0;

  DDE_IPC_init();	   /* will initialize only if needed */

  shm_write_wait(main_block->sem);
  /* return used only once from here on -- for semaphore simplicity */
  switch (atom_ofs=ATOM_OFS(atom_idx)) {
    case DELETED:
    case EMPTY:
      WARN("Trying to free unallocated atom %d\n", atom);
      retval=atom;
      break;
    default :
      atom_ptr=OFS2AtomData_ptr(atom_ofs);
      if ( --atom_ptr->count == 0) {
	 shm_FragmentFree(&main_block->block,atom_ofs);
	 ATOM_OFS(atom_idx)=DELETED;
      }
  }
  
  shm_write_signal(main_block->sem);
  return retval;
}

/***********************************************************************
 *           DDE_GlobalFindAtom
 */
ATOM DDE_GlobalFindAtom( SEGPTR name )
{
  int atom_idx;
  int atom_ofs;
  char *str;

  TRACE("(%08lx)\n", name );

  /* First check for integer atom */

  if (!HIWORD(name)) return (ATOM)LOWORD(name);

  str = (char *)PTR_SEG_TO_LIN( name );
  if (str[0] == '#')
  {
     ATOM atom= (ATOM) atoi(&str[1]);
     return (atom<MIN_STR_ATOM) ? atom : 0;
  }
  TRACE("(\"%s\")\n",str);

  DDE_IPC_init();		/* will initialize only if needed */

  shm_read_wait(main_block->sem);
  atom_idx=FindHash(str);
  if (atom_idx>=0)		   
     atom_ofs=ATOM_OFS(atom_idx);  /* is it free ? */
  else
     atom_ofs=EMPTY;
  shm_read_signal(main_block->sem);

  if (atom_ofs==EMPTY || atom_ofs==DELETED)
     return 0;
  else
     return (ATOM)(atom_idx+MIN_STR_ATOM);
}

/***********************************************************************
 *           DDE_GlobalGetAtomName
 */
WORD DDE_GlobalGetAtomName( ATOM atom, LPSTR buffer, short count )
{
  int atom_idx, atom_ofs;
  int size;
  /* temporary buffer to hold maximum "#65535\0" */
  char str_num[7];
  
  if (count<2)			   /* no sense to go on */
     return 0;
  atom_idx=(int)atom - MIN_STR_ATOM;
  
  if (atom_idx < 0) {		   /* word atom */
     /* use wine convention... */
     sprintf(str_num,"#%d%n",(int)atom,&size);
     if (size+1>count) {	   /* overflow ? */
	/* truncate the string */
	size=count-1;
	str_num[size]='\0';
     }
     strcpy(buffer,str_num);
     return size;
  }

  DDE_IPC_init();		/* will initialize only if needed */

  /* string atom */
  shm_read_wait(main_block->sem);
  atom_ofs=ATOM_OFS(atom_idx);
  if (atom_ofs==EMPTY || atom_ofs==DELETED) {
     WARN("Illegal atom=%d\n",(int)atom);
     size=0;
  } else {			   /* non empty entry */
     /* string length will be at most count-1, find actual size */
     sprintf(buffer,"%.*s%n",count-1, OFS2AtomStr(atom_ofs), &size);
  }
  shm_read_signal(main_block->sem);
  return size;
}

#endif  /* CONFIG_IPC */