/***************************************************************************
                          breakpoint.c  -  breakpoint implementation
                             -------------------
    begin                : Fri Nov 2 2001
    copyright            : (C) 2001 by Keith Isdale
    email                : k_isdale@tpg.com.au
 ***************************************************************************/

#include "xsldbg.h"
#include "breakpoint.h"
#include "arraylist.h"
#include "options.h"

extern int xsldbgValidateBreakpoints; /*located in debugXSL.c*/

/*-----------------------------------------------------------
       Private functions
-----------------------------------------------------------*/

/**
 * lineNoItemNew:
 * 
 * Returns a new hash table for break points
 */
xmlHashTablePtr lineNoItemNew(void);


/**
 * lineNoItemFree:
 * @item: valid hashtable of break points
 * 
 * Free @item and all its contents
 */
void lineNoItemFree(void *item);


/**
 * lineNoItemDelete:
 * @breakPointHash: Is valid
 * @breakPtr: Is valid
 * 
 * Returns 1 if able to delete @breakPtr from @breakPointHash,
 *         0 otherwise
 */
int lineNoItemDelete(xmlHashTablePtr breakPointHash,
                     breakPointPtr breakPtr);

/**
 * lineNoItemAdd:
 * @breakPointHash: is valid
 * @breakPtr: is valid
 *
 * Add breakpoint to hash 
 *
 * Returns 1 if able to add @breakPtr to @breakPointHash,
 *         0 otherwise
 */
int lineNoItemAdd(xmlHashTablePtr breakPointHash, breakPointPtr breakPtr);

/*-----------------------------------------------------------
       Breakpoint debugger functions
-----------------------------------------------------------*/


/* This is our major structure, it is a list of hash tables. Each 
 hash table has breakpoints with the same line number. A line
 number is used as an index into this list to get the right hash table.
 Then its just a matter of a simple hash table lookup  */
arrayListPtr breakList;

/* keep track of what break point id we're up to*/
int breakPointCounter = 0;

/* What is the current breakpoint is only valid up to the start of 
 xsldbg command prompt. ie don't use it after deletion of breakpoints */
breakPointPtr activeBreakPointItem = NULL;


/**
 * lineNoItemNew:
 * 
 * Returns a new hash table for break points
 */
xmlHashTablePtr
lineNoItemNew(void)
{
    xmlHashTablePtr hash;

    hash = xmlHashCreate(4);

    return hash;
}


/**
 * lineNoItemFree:
 * @item: valid hashtable of break points
 * 
 * Free @item and all its contents
 */
void
lineNoItemFree(void *item)
{
    xmlHashTablePtr hash = (xmlHashTablePtr) item;

    if (item) {
#if 0
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Freeing breakpoint line hash"
                         " with %d elements \n", xmlHashSize(item));
#endif
#endif
        xmlHashFree(hash, breakPointItemFree);
    }
}


/**
 * lineNoItemDelete:
 * @breakPointHash: is valid
 * @breakPtr: is valid
 * 
 * Returns 1 if able to delete @breakPtr from @breakPointHash,
 *         0 otherwise
 */
int
lineNoItemDelete(xmlHashTablePtr breakPointHash, breakPointPtr breakPtr)
{
    int result = 0;

    if (breakPointHash && breakPtr) {
        if (xmlHashRemoveEntry(breakPointHash, breakPtr->url,
                               breakPointItemFree) == 0){
            result = 1;
	}else{
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
    xsltGenericError(xsltGenericErrorContext,"lineNoItemDelete failed");
#endif
	}

    }else {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
    xsltGenericError(xsltGenericErrorContext, "lineNoItemDelete failed args %d %d", breakPointHash, breakPtr);
#endif
    }
    return result;
}


/**
 * lineNoItemAdd:
 * @breakPointHash: is valid
 * @breakPtr: is valid
 *
 * Returns 1 if able to add @breakPtr to @breakPointHash,
 *         0 otherwise
 */
int
lineNoItemAdd(xmlHashTablePtr breakPointHash, breakPointPtr breakPtr)
{
    int result = 0;

    if (breakPointHash && breakPtr) {
        if (xmlHashAddEntry(breakPointHash, breakPtr->url, breakPtr) == 0)
            result = 1;
    }
    return result;
}

/**
 * breakPointGetLineNoHash:
 * @lineNo: Line number of of breakpoints of interest
 *
 * Return A hash of breakpoints with same line number
 *
 * Returns A hash of breakpoints with a line number of @lineNo
 */
xmlHashTablePtr
breakPointGetLineNoHash(long lineNo)
{
    if (!breakList) {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Breakpoints structures not initialized\n");
#endif
        return NULL;
    } else
        return (xmlHashTablePtr) arrayListGet(breakList, lineNo);
}


/**
 * breakPointInit:
 *
 * Returns 1 if breakpoints have been initialized properly and all
 *               memory required has been obtained,
 *         0 otherwise
*/
int
breakPointInit(void)
{
    int result = 0;

    /* the average file has 395 lines of code so add 100 lines now */
    breakList = arrayListNew(100, lineNoItemFree);
    if (breakList) {
        /*
         * We don't need to do any thing else, as its done when we add the 
         *    breakPoints
         */
        result = 1;
    } else {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Unable to intialize breakPoints: memory error\n");
#endif
    }
    return result;
}


/**
 * breakPointFree:
 *
 * Free all memory used by breakPoints 
 */
void
breakPointFree(void)
{
    if (breakList)
        arrayListFree(breakList);
    breakList = NULL;
}


/**
 * breakPointEmpty:
 *
 * Empty the break point collection
 *
 * Returns 1 if able to empty the breakpoint list of its contents,
 *         0  otherwise
 */
int
breakPointEmpty(void)
{
    return arrayListEmpty(breakList);
}


/** 
 * breakPointItemNew:
 * 
 * Create a new break point item
 * Returns valid break point with default values set if successful, 
 *         NULL otherwise
 */
breakPointPtr
breakPointItemNew(void)
{
    breakPointPtr breakPtr = (breakPointPtr) xmlMalloc(sizeof(breakPoint));

    if (breakPtr) {
        breakPtr->url = NULL;
        breakPtr->lineNo = -1;
        breakPtr->templateName = NULL;
	breakPtr->modeName = NULL;
        breakPtr->flags = BREAKPOINT_ENABLED;
        breakPtr->id = ++breakPointCounter;
        breakPtr->type = DEBUG_BREAK_SOURCE;
    }
    return breakPtr;
}


/**
 * breakPointItemFree:
 * @payload: valid breakPointPtr 
 * @name: not used
 *
 * Free memory associated with this break point
 */
void
breakPointItemFree(void *payload, xmlChar * name)
{
    Q_UNUSED(name);
    if (payload) {
        breakPointPtr breakPtr = (breakPointPtr) payload;

        if (breakPtr->url)
            xmlFree(breakPtr->url);
        if (breakPtr->templateName)
            xmlFree(breakPtr->templateName);
	if (breakPtr->modeName)
	  xmlFree(breakPtr->modeName);
        xmlFree(breakPtr);
    }
}


/**
 * breakPointActiveBreakPoint:
 * 
 * Get the active break point
 *
 * Returns The last break point that we stoped at
 *
 * Depreciated
 */
breakPointPtr
breakPointActiveBreakPoint(void)
{
    /* This function is depreciated */
    return NULL;                /* activeBreakPointItem; */
}



/**
 * breakPointSetActiveBreakPoint:
 * @breakPtr: Is valid break point or NULL
 *
 * Set the active break point
 *
 * Depreciated
 */
void
breakPointSetActiveBreakPoint(breakPointPtr breakPtr)
{
    Q_UNUSED(breakPtr);
    /*
     * activeBreakPointItem = breakPtr;
     */

}


/**
 * breakPointAdd:
 * @url: Non-null, non-empty file name that has been loaded by
 *                    debugger
 * @lineNumber: @lineNumber >= 0 and is available in url specified and
 *                points to an xml element
 * @templateName: The template name of breakPoint or NULL
 * @modeName : The mode of breakpoint or NULL
 * @type: Valid BreakPointTypeEnum
 *
 * Add break point at file and line number specified
 *
 * Returns 1 if successful,
 *	   0 otherwise
*/
int
breakPointAdd(const xmlChar * url, long lineNumber,
              const xmlChar * templateName, 
	      const xmlChar * modeName,
	      BreakPointTypeEnum type)
{
    int result = 0, breakPointType = type;
    xmlHashTablePtr breakPointHash = NULL;     /* hash of breakPoints */
    breakPointPtr breakPtr;

    if (!breakList) {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Breakpoints structures not initialized\n");
#endif
        return result;
    }

    if (!url || (lineNumber == -1)) {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Invalid url or line number to breakPointAdd\n");
#endif
        return result;
    }

    /* if breakpoint already exists then don;t add it */
    if (breakPointIsPresent(url, lineNumber)) {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Warning: Breakpoint at file %s: line %d exists\n",
                         url, lineNumber);
#endif
        return result;
    }

    breakPtr = breakPointItemNew();
    if (breakPtr) {
        breakPtr->url = (xmlChar *) xmlMemStrdup((char *) url);
        breakPtr->lineNo = lineNumber;
        if (templateName)
            breakPtr->templateName =
                xmlStrdup( templateName);
        else
            breakPtr->templateName = NULL;
	if (modeName)
	    breakPtr->modeName = 
	      xmlStrdup(modeName);
	else
	  breakPtr->modeName = NULL;
        breakPtr->type = BreakPointTypeEnum(breakPointType);

        /* add new breakPoint to the right hash table */
        breakPointHash = breakPointGetLineNoHash(lineNumber);
        if (breakPointHash) {
            result = lineNoItemAdd(breakPointHash, breakPtr);
        } else {
            /* Grow breakList size */
            int lineIndex;
            int newEntries = breakList->count;
            xmlHashTablePtr hash;

            result = 1;
            if ((lineNumber < breakList->count) && breakList->count) {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
                xsltGenericError(xsltGenericErrorContext,
                                 "Error: Unable to find breakpoint line hash at %d\n",
                                 lineNumber);
#endif
            } else {
                if (breakList->count + newEntries < lineNumber)
                    newEntries = lineNumber - breakList->count + 1;


#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
                /*
                 * xsltGenericError(xsltGenericErrorContext,
                 * "Size of line list was %d adding %d entries\n",
                 * breakList->count, newEntries);
                 */
#endif
                lineIndex = 0;
                while ((lineIndex < newEntries) && result) {
                    hash = lineNoItemNew();
                    if (hash) {
                        result = result && arrayListAdd(breakList, hash);
                    } else {
                        result = 0;
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
                        xsltGenericError(xsltGenericErrorContext,
                                         "Error: Unable to create hash table breakPoint list: memory error\n");
#endif
                        return result;
                    }
                    lineIndex++;
                }
                /* find the newly added hashtable of breakpoints */
                breakPointHash = breakPointGetLineNoHash(lineNumber);
                if (breakPointHash) {
                    result = lineNoItemAdd(breakPointHash, breakPtr);
                } else {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
                    xsltGenericError(xsltGenericErrorContext,
                                     "Error: Unable to create new breakPoint:interal error\n");
#endif
                    return result;
                }
            }

        }
    } else {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Unable to create new breakPoint: memory error\n");
#endif
    }

    if (result && (optionsGetIntOption(OPTIONS_GDB) > 1) && 
	    (xsldbgValidateBreakpoints != BREAKPOINTS_BEING_VALIDATED)){
      breakPointPrint(breakPtr);
      xsldbgGenericErrorFunc("\n");
    }
    return result;
}


/**
 * breakPointDelete:
 * @breakPtr: Is valid
 *
 * Delete the break point specified if it can be found using 
 *    @breakPoint's url and lineNo
 *
 * Returns 1 if successful,
 *	   0 otherwise
*/
int
breakPointDelete(breakPointPtr breakPtr)
{
    int result = 0;
    xmlHashTablePtr breakPointHash;     /* hash of breakPoints */

    if (!breakPtr)
        return result;

    breakPointHash = breakPointGetLineNoHash(breakPtr->lineNo);
    if (breakPointHash) {
        result = lineNoItemDelete(breakPointHash, breakPtr);
    } else {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Breakpoint not found: xslDeleteBreakPoint\n");
#endif
    }
    return result;
}


/**
 * breakPointEnable:
 * @breakPtr: A valid breakpoint
 * @enable: Enable break point if 1, disable if 0, toggle if -1
 *
 * Enable or disable a break point
 *
 * Returns 1 if successful,
 *	   0 otherwise
*/
int
breakPointEnable(breakPointPtr breakPtr, int enable)
{
    int result = 0;

    if (breakPtr) {
	int enableFlag = 1;
        if (enable != XSL_TOGGLE_BREAKPOINT){
	    enableFlag = enable;
        }else {
	    if (breakPtr->flags & BREAKPOINT_ENABLED)
		enableFlag = 0;
        }
	if (enableFlag)
	    breakPtr->flags |= BREAKPOINT_ENABLED;
	else
	    breakPtr->flags = breakPtr->flags & (BREAKPOINT_ALLFLAGS ^ BREAKPOINT_ENABLED);
        result = 1;
    }
    return result;
}


/**
 * breakPointLinesCount:
 *
 * Return the number of hash tables of break points with the same line number
 *
 * Returns The number of hash tables of break points with the same line number
 */
int
breakPointLinesCount(void)
{
    if (!breakList) {
#ifdef WITH_XSLDBG_DEBUG_BREAKPOINTS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Breakpoints structures not initialized\n");
#endif
        return 0;
    } else
        return arrayListCount(breakList);
}


/**
 * breakPointLinesList:
 *
 * Returns The list of hash tables for break points
 *        Dangerous function to use!! 
 */
arrayListPtr
breakPointLineList(void)
{
    return breakList;
}


/**
 * breakPointGet:
 * @url: Non-null, non-empty file name that has been loaded by
 *                    debugger
 * @lineNumber: lineNumber >= 0 and is available in @url
 *
 * Get a break point for the breakpoint collection
 *
 * Returns break point if break point exists at location specified,
 *	   NULL otherwise
*/
breakPointPtr
breakPointGet(const xmlChar * url, long lineNumber)
{
    xmlHashTablePtr breakHash = breakPointGetLineNoHash(lineNumber);
    breakPointPtr breakPtr = NULL;

    if (!breakHash || !url)
        return breakPtr;

    breakPtr = (breakPointPtr)xmlHashLookup(breakHash, url);
    return breakPtr;
}


/**
 * breakPointPrint:
 * @breakPtr: A valid break point
 *
 * Print the details of @breakPtr
 *
 * Returns 1 if successful,
 *	   0 otherwise
 */
int
breakPointPrint(breakPointPtr breakPtr)
{
    int result = 0;
    const char *breakStatusText[2] = {
	I18N_NOOP("disabled"),
	I18N_NOOP("enabled")
	};
    const char *breakTemplate="";
    const char *breakMode = "";
    const char *breakStatus;


    if (!breakPtr)
        return result;

    if (breakPtr->templateName){
	 if (breakPtr->modeName)
	     breakMode = (const char*)breakPtr->modeName;
	breakTemplate = (const char*)breakPtr->templateName;
    }
    

    breakStatus = breakStatusText[breakPtr->flags & BREAKPOINT_ENABLED];
    if (breakPtr->url)
	xsldbgGenericErrorFunc(i18n("Breakpoint %1 %2 for template: \"%3\" mode: \"%4\" in file \"%5\" at line %6").arg(breakPtr->id).arg(i18n(breakStatus)).arg(xsldbgText(breakTemplate)).arg(xsldbgText(breakMode)).arg(xsldbgUrl(breakPtr->url)).arg(breakPtr->lineNo));
    else
	xsldbgGenericErrorFunc(i18n("Breakpoint %1 %2 for template: \"%3\" mode: \"%4\"").arg(breakPtr->id).arg(i18n(breakStatus)).arg(xsldbgText(breakTemplate)).arg(xsldbgText(breakMode)));
    return ++result;
}


/**
 * breakPointIsPresent:
 * @url: Non-null, non-empty file name that has been loaded by
 *                    debugger
 * @lineNumber: @lineNumber >= 0 and is available in @url
 *
 * Determine if there is a break point at file and line number specified
 *
 * Returns 1  if successful,  
 *         0 otherwise
*/
int
breakPointIsPresent(const xmlChar * url, long lineNumber)
{
    int result = 0;

    if (!url || (lineNumber == -1))
        return result;

    result = (breakPointGet(url, lineNumber) != NULL);

    return result;
}


/**
 * breakPointIsPresentNode:
 * @node: node != NULL
 *
 * Determine if a node is a break point
 *
 * Returns 1 on success, 
 *         0 otherwise
 */
int
breakPointIsPresentNode(xmlNodePtr node)
{
    int result = 0;

    if (!node || !node->doc)
        return result;

    if (xmlGetLineNo(node) == -1)
        return result;

    if (node->doc->URL) {
        result = breakPointIsPresent(node->doc->URL, xmlGetLineNo(node));
    }

    return result;
}