/*
 * Atom table functions
 *
 * Copyright 1993, 1994, 1995 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Warning: The code assumes that LocalAlloc() returns a block aligned
 * on a 4-bytes boundary (because of the shifting done in
 * HANDLETOATOM).  If this is not the case, the allocation code will
 * have to be changed.
 */

#include "config.h"
#include "wine/port.h"

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "windef.h"
#include "winbase.h"
#include "winerror.h"

#include "wine/server.h"
#include "wine/unicode.h"
#include "kernel_private.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(atom);

#define MAX_ATOM_LEN 255

/***********************************************************************
 *           ATOM_IsIntAtomA
 */
static BOOL ATOM_IsIntAtomA(LPCSTR atomstr,WORD *atomid)
{
    UINT atom = 0;
    if (!HIWORD(atomstr)) atom = LOWORD(atomstr);
    else
    {
        if (*atomstr++ != '#') return FALSE;
        while (*atomstr >= '0' && *atomstr <= '9')
        {
            atom = atom * 10 + *atomstr - '0';
            atomstr++;
        }
        if (*atomstr) return FALSE;
    }
    if (atom >= MAXINTATOM)
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        atom = 0;
    }
    *atomid = atom;
    return TRUE;
}


/***********************************************************************
 *           ATOM_IsIntAtomW
 */
static BOOL ATOM_IsIntAtomW(LPCWSTR atomstr,WORD *atomid)
{
    UINT atom = 0;
    if (!HIWORD(atomstr)) atom = LOWORD(atomstr);
    else
    {
        if (*atomstr++ != '#') return FALSE;
        while (*atomstr >= '0' && *atomstr <= '9')
        {
            atom = atom * 10 + *atomstr - '0';
            atomstr++;
        }
        if (*atomstr) return FALSE;
    }
    if (atom >= MAXINTATOM)
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        atom = 0;
    }
    *atomid = atom;
    return TRUE;
}


/***********************************************************************
 *           InitAtomTable   (KERNEL32.@)
 *
 * Initialise the global atom table.
 *
 * PARAMS
 *  entries [I] The number of entries to reserve in the table.
 *
 * RETURNS
 *  Success: TRUE.
 *  Failure: FALSE.
 */
BOOL WINAPI InitAtomTable( DWORD entries )
{
    BOOL ret;
    SERVER_START_REQ( init_atom_table )
    {
        req->entries = entries;
        ret = !wine_server_call_err( req );
    }
    SERVER_END_REQ;
    return ret;
}


static ATOM ATOM_AddAtomA( LPCSTR str, BOOL local )
{
    ATOM atom = 0;
    if (!ATOM_IsIntAtomA( str, &atom ))
    {
        WCHAR buffer[MAX_ATOM_LEN];

        DWORD len = MultiByteToWideChar( CP_ACP, 0, str, strlen(str), buffer, MAX_ATOM_LEN );
        if (!len)
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return 0;
        }
        SERVER_START_REQ( add_atom )
        {
            wine_server_add_data( req, buffer, len * sizeof(WCHAR) );
            req->local = local;
            if (!wine_server_call_err(req)) atom = reply->atom;
        }
        SERVER_END_REQ;
    }
    TRACE( "(%s) %s -> %x\n", local ? "local" : "global", debugstr_a(str), atom );
    return atom;
}


/***********************************************************************
 *           GlobalAddAtomA   (KERNEL32.@)
 *
 * Add a character string to the global atom table and return a unique
 * value identifying it.
 *
 * RETURNS
 *	Success: The atom allocated to str.
 *	Failure: 0.
 */
ATOM WINAPI GlobalAddAtomA( LPCSTR str /* [in] String to add */ )
{
    return ATOM_AddAtomA( str, FALSE );
}


/***********************************************************************
 *           AddAtomA   (KERNEL32.@)
 *
 * Add a character string to the global atom table and return a unique
 * value identifying it.
 *
 * RETURNS
 *      Success: The atom allocated to str.
 *      Failure: 0.
 */
ATOM WINAPI AddAtomA( LPCSTR str /* [in] String to add */ )
{
    return ATOM_AddAtomA( str, TRUE );
}


static ATOM ATOM_AddAtomW( LPCWSTR str, BOOL local )
{
    ATOM atom = 0;
    if (!ATOM_IsIntAtomW( str, &atom ))
    {
        DWORD len = strlenW(str);
        if (len > MAX_ATOM_LEN)
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return 0;
        }
        SERVER_START_REQ( add_atom )
        {
            req->local = local;
            wine_server_add_data( req, str, len * sizeof(WCHAR) );
            if (!wine_server_call_err(req)) atom = reply->atom;
        }
        SERVER_END_REQ;
    }
    TRACE( "(%s) %s -> %x\n", local ? "local" : "global", debugstr_w(str), atom );
    return atom;
}


/***********************************************************************
 *           GlobalAddAtomW   (KERNEL32.@)
 *
 * Unicode version of GlobalAddAtomA.
 */
ATOM WINAPI GlobalAddAtomW( LPCWSTR str )
{
    return ATOM_AddAtomW( str, FALSE );
}


/***********************************************************************
 *           AddAtomW   (KERNEL32.@)
 *
 * Unicode version of AddAtomA.          
 */
ATOM WINAPI AddAtomW( LPCWSTR str )
{
    return ATOM_AddAtomW( str, TRUE );
}


static ATOM ATOM_DeleteAtom( ATOM atom,  BOOL local)
{
    TRACE( "(%s) %x\n", local ? "local" : "global", atom );
    if (atom >= MAXINTATOM)
    {
        SERVER_START_REQ( delete_atom )
        {
            req->atom = atom;
            req->local = local;
            wine_server_call_err( req );
        }
        SERVER_END_REQ;
    }
    return 0;
}


/***********************************************************************
 *           GlobalDeleteAtom   (KERNEL32.@)
 *
 * Decrement the reference count of a string atom.  If the count is
 * zero, the string associated with the atom is removed from the table.
 *
 * RETURNS
 *	Success: 0.
 *	Failure: atom.
 */
ATOM WINAPI GlobalDeleteAtom( ATOM atom /* [in] Atom to delete */ )
{
    return ATOM_DeleteAtom( atom, FALSE);
}


/***********************************************************************
 *           DeleteAtom   (KERNEL32.@)
 *
 * Decrement the reference count of a string atom.  If the count becomes
 * zero, the string associated with the atom is removed from the table.
 *
 * RETURNS
 *	Success: 0.
 *	Failure: atom
 */
ATOM WINAPI DeleteAtom( ATOM atom /* [in] Atom to delete */ )
{
    return ATOM_DeleteAtom( atom, TRUE );
}


static ATOM ATOM_FindAtomA( LPCSTR str, BOOL local )
{
    ATOM atom = 0;
    if (!ATOM_IsIntAtomA( str, &atom ))
    {
        WCHAR buffer[MAX_ATOM_LEN];

        DWORD len = MultiByteToWideChar( CP_ACP, 0, str, strlen(str), buffer, MAX_ATOM_LEN );
        if (!len)
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return 0;
        }
        SERVER_START_REQ( find_atom )
        {
            req->local = local;
            wine_server_add_data( req, buffer, len * sizeof(WCHAR) );
            if (!wine_server_call_err(req)) atom = reply->atom;
        }
        SERVER_END_REQ;
    }
    TRACE( "(%s) %s -> %x\n", local ? "local" : "global", debugstr_a(str), atom );
    return atom;
}


/***********************************************************************
 *           GlobalFindAtomA   (KERNEL32.@)
 *
 * Get the atom associated with a string.
 *
 * RETURNS
 *	Success: The associated atom.
 *	Failure: 0.
 */
ATOM WINAPI GlobalFindAtomA( LPCSTR str /* [in] Pointer to string to search for */ )
{
    return ATOM_FindAtomA( str, FALSE );
}

/***********************************************************************
 *           FindAtomA   (KERNEL32.@)
 *
 * Get the atom associated with a string.
 *
 * RETURNS
 *	Success: The associated atom.
 *	Failure: 0.
 */
ATOM WINAPI FindAtomA( LPCSTR str /* [in] Pointer to string to find */ )
{
    return ATOM_FindAtomA( str, TRUE );
}


static ATOM ATOM_FindAtomW( LPCWSTR str, BOOL local )
{
    ATOM atom = 0;
    if (!ATOM_IsIntAtomW( str, &atom ))
    {
        DWORD len = strlenW(str);
        if (len > MAX_ATOM_LEN)
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return 0;
        }
        SERVER_START_REQ( find_atom )
        {
            wine_server_add_data( req, str, len * sizeof(WCHAR) );
            req->local = local;
            if (!wine_server_call_err( req )) atom = reply->atom;
        }
        SERVER_END_REQ;
    }
    TRACE( "(%s) %s -> %x\n", local ? "local" : "global", debugstr_w(str), atom );
    return atom;
}


/***********************************************************************
 *           GlobalFindAtomW   (KERNEL32.@)
 *
 * Unicode version of GlobalFindAtomA.
 */
ATOM WINAPI GlobalFindAtomW( LPCWSTR str )
{
    return ATOM_FindAtomW( str, FALSE );
}


/***********************************************************************
 *           FindAtomW   (KERNEL32.@)
 *
 * Unicode version of FindAtomA.
 */
ATOM WINAPI FindAtomW( LPCWSTR str )
{
    return ATOM_FindAtomW( str, TRUE );
}


static UINT ATOM_GetAtomNameA( ATOM atom, LPSTR buffer, INT count, BOOL local )
{
    INT len;

    if (count <= 0)
    {
        SetLastError( ERROR_MORE_DATA );
        return 0;
    }
    if (atom < MAXINTATOM)
    {
        char name[8];
        if (!atom)
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return 0;
        }
        len = sprintf( name, "#%d", atom );
        lstrcpynA( buffer, name, count );
    }
    else
    {
        WCHAR full_name[MAX_ATOM_LEN];

        len = 0;
        SERVER_START_REQ( get_atom_name )
        {
            req->atom = atom;
            req->local = local;
            wine_server_set_reply( req, full_name, sizeof(full_name) );
            if (!wine_server_call_err( req ))
            {
                len = WideCharToMultiByte( CP_ACP, 0, full_name,
                                           wine_server_reply_size(reply) / sizeof(WCHAR),
                                           buffer, count - 1, NULL, NULL );
                if (!len) len = count; /* overflow */
                else buffer[len] = 0;
            }
        }
        SERVER_END_REQ;
    }

    if (len && count <= len)
    {
        SetLastError( ERROR_MORE_DATA );
        buffer[count-1] = 0;
        return 0;
    }
    TRACE( "(%s) %x -> %s\n", local ? "local" : "global", atom, debugstr_a(buffer) );
    return len;
}


/***********************************************************************
 *           GlobalGetAtomNameA   (KERNEL32.@)
 *
 * Get a copy of the string associated with an atom.
 *
 * RETURNS
 *	Success: The length of the returned string in characters.
 *	Failure: 0.
 */
UINT WINAPI GlobalGetAtomNameA(
              ATOM atom,    /* [in]  Atom identifier */
              LPSTR buffer, /* [out] Pointer to buffer for atom string */
              INT count )   /* [in]  Size of buffer */
{
    return ATOM_GetAtomNameA( atom, buffer, count, FALSE );
}


/***********************************************************************
 *           GetAtomNameA   (KERNEL32.@)
 *
 * Get a copy of the string associated with an atom.
 *
 * RETURNS
 *	Success: The length of the returned string in characters.
 *	Failure: 0.
 */
UINT WINAPI GetAtomNameA(
              ATOM atom,    /* [in]  Atom */
              LPSTR buffer, /* [out] Pointer to string for atom string */
              INT count)    /* [in]  Size of buffer */
{
    return ATOM_GetAtomNameA( atom, buffer, count, TRUE );
}


static UINT ATOM_GetAtomNameW( ATOM atom, LPWSTR buffer, INT count, BOOL local )
{
    INT len;

    if (count <= 0)
    {
        SetLastError( ERROR_MORE_DATA );
        return 0;
    }
    if (atom < MAXINTATOM)
    {
        char name[8];
        if (!atom)
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return 0;
        }
        sprintf( name, "#%d", atom );
        len = MultiByteToWideChar( CP_ACP, 0, name, -1, buffer, count );
        if (!len) buffer[count-1] = 0;  /* overflow */
    }
    else
    {
        WCHAR full_name[MAX_ATOM_LEN];

        len = 0;
        SERVER_START_REQ( get_atom_name )
        {
            req->atom = atom;
            req->local = local;
            wine_server_set_reply( req, full_name, sizeof(full_name) );
            if (!wine_server_call_err( req ))
            {
                len = wine_server_reply_size(reply) / sizeof(WCHAR);
                if (count > len) count = len + 1;
                memcpy( buffer, full_name, (count-1) * sizeof(WCHAR) );
                buffer[count-1] = 0;
            }
        }
        SERVER_END_REQ;
        if (!len) return 0;
    }
    TRACE( "(%s) %x -> %s\n", local ? "local" : "global", atom, debugstr_w(buffer) );
    return len;
}


/***********************************************************************
 *           GlobalGetAtomNameW   (KERNEL32.@)
 *
 * Unicode version of GlobalGetAtomNameA.
 */
UINT WINAPI GlobalGetAtomNameW( ATOM atom, LPWSTR buffer, INT count )
{
    return ATOM_GetAtomNameW( atom, buffer, count, FALSE);
}


/***********************************************************************
 *           GetAtomNameW   (KERNEL32.@)
 *
 * Unicode version of GetAtomNameA.
 */
UINT WINAPI GetAtomNameW( ATOM atom, LPWSTR buffer, INT count )
{
    return ATOM_GetAtomNameW( atom, buffer, count, TRUE );
}
