/*
 * cputime.c --
 *
 *      Tools to measure cpu usage
 *      of different parts of code.
 * 
 * Copyright (c) 2016-2017 by Simon Bachmann <simonbachmann@bluewin.ch>
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 * 
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <tcl.h>

#include "cputime.h"
#include "debugmsg.h"

const char **scopeNames = NULL;
clock_t *timeSums = NULL;
clock_t previousTime = 0;
clock_t overhead = 0;
int scopeCount = 0;

int *stack, curStackIdx, stackSize=100;

/*
 *--------------------------------------------------------------
 *
 * Cputime_SetScopes_L --
 *
 *      Initialize time counters and scope names.
 *      Previous data (if any) is discarded.
 *
 * Results:
 *      1 on succes, 0 if args are wrong
 *
 * Side effects:
 *      Sets new names for scopes, discards any previous data
 *
 *--------------------------------------------------------------
 */
int Cputime_SetScopes_L (interp, names)
Tcl_Interp *interp;     /* used for debugmsg, maybe in the future */
                        /* for interp-safe timing */
const char * const* names;  /* Names of the scopes, NULL terminates the list */
{
    /* 
     * Cleanup, if not the first call
     */
    Cputime_Cleanup_L(interp);
    
    /*
     * prepare
     */
    int count;
    for (count = 0; names[count] != NULL; count++);
    
    scopeNames = malloc(count * sizeof(char *));
    timeSums = malloc(count * sizeof(clock_t));
    for (int i = 0; i < count; i++) {
        scopeNames[i] = names[i]; 
        timeSums[i] = 0;
    }
    previousTime = 0;
    overhead = 0;
    scopeCount = count;
    
    stack = malloc(sizeof(int)*stackSize);
    stack[0] = -1;
    curStackIdx = 0;
    
    return 1;
}

/*
 *--------------------------------------------------------------
 *
 * Cputime_NewScope_L --
 *
 *      Register the beginning of a new scope
 *
 * Results:
 *      returns 0 on errors, 1 otherwise 
 *
 * Side effects:
 *      Calculates the time elapsed for the previous scope and 
 *      adds it to the appropriate counter
 *
 *--------------------------------------------------------------
 */
int Cputime_NewScope_L (interp, scopeIndex)
Tcl_Interp *interp;     /* used for debugmsg, maybe in the future */
                        /* for interp-safe timing */
int scopeIndex;         /* index of the new scope */
{
    clock_t currentTime;
    
    currentTime = clock();
    
    if (scopeIndex < 0 || scopeIndex > scopeCount) {
        return 0;
    }
    
    if (stack[curStackIdx] != -1) {
        timeSums[stack[curStackIdx]] += currentTime - previousTime;
    }
    
    if (++curStackIdx >= stackSize) {
        //fprintf (stderr, "ERROR: Cputime stack full. results will be meaningless\n");
        debugmsg(interp, DEBUG_CPUTIME_DEBUG, "Cputime_NewScope_L: Cputime stack full. results will be meaningless\n");
        curStackIdx--;
    }
    stack[curStackIdx] = scopeIndex;
    
    previousTime = clock();
    overhead += previousTime - currentTime;
    
    return 1;
}

/*
 *--------------------------------------------------------------
 *
 * Cputime_EndScopeSecure_L --
 *
 *      End current scope and return to previous one
 *      Checks if we are leving the scope the user thinks he's leaving.
 *
 * Results:
 *      1 on succes, 0 on error
 *
 * Side effects:
 *      Calculate elapsed time for current scope, returns to previous 
 *      scope
 *      
 *--------------------------------------------------------------
 */
int Cputime_EndScopeSecure_L (interp, scopeIndex)
Tcl_Interp *interp;     /* used for debugmsg, maybe in the future */
                        /* for interp-safe timing */
int scopeIndex;         /* index of scope to end */
{
    if (stack[curStackIdx] != scopeIndex) {
        //fprintf(stderr, "HOOONK! Cputime_EndScope says: you think you're ending scope %d, in reality your're leaving #%d.\n", scopeIndex, stack[curStackIdx]);
        debugmsg(interp, DEBUG_CPUTIME_DEBUG, "Cputime_EndScopeSecure_L: WARNING: you think you're ending scope %d, in reality your're leaving #%d.\n", scopeIndex, stack[curStackIdx]);
    }
    return Cputime_EndScope_L(interp);
}

/*
 *--------------------------------------------------------------
 *
 * Cputime_EndScope_L --
 *
 *      End current scope and return to previous one
 *
 * Results:
 *      1 on succes, 0 on error
 *
 * Side effects:
 *      Calculate elapsed time for current scope, returns to previous 
 *      scope
 *      
 *--------------------------------------------------------------
 */
int Cputime_EndScope_L (interp)
Tcl_Interp *interp;     /* used for debugmsg, maybe in the future */
                        /* for interp-safe timing */

{
    clock_t currentTime;
    currentTime = clock();
    
    if (stack[curStackIdx] != -1) {
        timeSums[stack[curStackIdx]] += currentTime - previousTime;
        --curStackIdx;
    } else {
        //fprintf(stderr, "Cputime_EndScope: trying to leave bottom of the stack...\n");
        debugmsg(interp, DEBUG_CPUTIME_DEBUG, "Cputime_EndScope_L: WARNING: trying to leave bottom of stack.\n");
    }
    
    previousTime = clock();
    
    overhead += previousTime - currentTime;
    
    return 1;
}

/*
 *--------------------------------------------------------------
 *
 * Cputime_PrintSummary_L --
 *
 *      Prints a summary of the gathered data to stdout
 *
 * Results:
 *      none
 *
 * Side effects:
 *      prints summary to stdout
 *
 *--------------------------------------------------------------
 */

int Cputime_PrintSummary_L (interp)
Tcl_Interp *interp;     /* used for debugmsg, maybe in the future */
                        /* for interp-safe timing */

{
    clock_t totalTime = 0;
    int longestStr;
    double percent;
    
    char *totalStr = "Total";
    char *overheadStr = "Cputime overhead";
    longestStr = strlen(overheadStr);
    
    totalTime = overhead;
    for (int i = 0; i < scopeCount; i++) {
        totalTime += timeSums[i];
        longestStr = strlen(scopeNames[i]) > longestStr ? strlen(scopeNames[i]) : longestStr;
    }
    longestStr += 2;
    
    debugmsg(interp, DEBUG_CPUTIME, "\nCputime Summary:\n");
    debugmsg(interp, DEBUG_CPUTIME, "=================\n");
    for (int i = 0; i < scopeCount; i++) {
        percent = 100.0 * (timeSums[i]) / totalTime;
        debugmsg(interp, DEBUG_CPUTIME, "%s%-*s%3.0f%% (%.3f seconds)\n", scopeNames[i], (int)(longestStr-strlen(scopeNames[i])), ": ", percent, (double)(timeSums[i])/CLOCKS_PER_SEC);
    }
    percent = 100.0 * overhead / totalTime;
    debugmsg(interp, DEBUG_CPUTIME, "\n%s%-*s%3.0f%% (%.3f seconds)\n", overheadStr, (int)(longestStr-strlen(overheadStr)), ": ", percent, (double)overhead/CLOCKS_PER_SEC);
    debugmsg(interp, DEBUG_CPUTIME, "\n%s%-*s%3d%% (%.3f seconds)\n\n", totalStr, (int)(longestStr-strlen(totalStr)), ": ", 100, (double)totalTime/CLOCKS_PER_SEC);
    
    return 1;
}

/*
 *--------------------------------------------------------------
 *
 * Cputime_Cleanup_L --
 *
 *      Free all allocated memory
 * 
 * Results:
 *      none
 *
 * Side effects:
 *      Allocated memory is freed, data lost
 *
 *--------------------------------------------------------------
 */
void Cputime_Cleanup_L (interp)
Tcl_Interp *interp;     /* used for debugmsg, maybe in the future */
                        /* for interp-safe timing */

{
    scopeCount = 0;
    
    free(scopeNames);
    free(timeSums);
    
    scopeNames = NULL;
    timeSums = NULL;
    
    return;
}
