/*
 * debugmsg.c --
 *      
 *      Utilities to print nice and useful debugging messages
 * 
 * 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.
 * 
 */

/*
 * Concept:
 * 
 * Messages are printed to stdout, stdout is configured unbuffered at init.
 * 
 * -debug optstr option, accepts a series of letters:
 *      p   print progress messages
 *      w   list the image-wide values used for scaing/rotating/etc (10-20 lines per operation)
 *      m   more information about calculated data (100-200 lines), implies w
 *      a   full report about calculations (10-20 lines PER PIXEL), implies m and w
 *      f   follow pixel (cannot be specified as arg, actually - only available through -followpixel option)
 *      c   cputime messages (if compiled in, no effect otherwise)
 * 
 * -followpixel coordlist: print detailed information about single pixel specified by coords
 *      (implies -debug f
 *      
 * C functions:
 * 
 * void debugmsg (Tcl_Interp *interp, int level, char * formatStr, args ...);
 *      Implemented as a preprocessor macro, that translates to no code if IMGTOOLS_DEBUG is not set.
 * void Imgtools_SetDebugLevel (Tcl_Interp *interp, int newLevel);
 * 
 */

#include <stdio.h>
#include <stdarg.h>
#include <tcl.h>

#include "debugmsg.h"
#include "imgtoolsDecl.h"
#include "interpolatedOps.h"

/*
 * Debug modes
 */
typedef struct {
    const char * letter;
    const int flags;
    const char *descr;
} DebugMode;

// to add a mode, add it here and, if needed, define a flag in the headerfile
// letter element of last mode must be NULL
const DebugMode modes[] = {
    {"p", DEBUG_PROGRESS,       "progress information"},
    {"w", DEBUG_PARAMETERS,     "top-level parameters for operation"},
    {"m", DEBUG_PARAMETERS | DEBUG_MORE, "more information about operation"},
    {"a", DEBUG_PARAMETERS | DEBUG_MORE | DEBUG_ALL, "all information about image operation"},
    {"f", DEBUG_FOLLOW,         "print all parameters for one specific pixel"}, 
    {"c", DEBUG_CPUTIME,        "print cputime summary"},
    {"d", DEBUG_CPUTIME_DEBUG,  "debug info about cputime"},
    {"o", DEBUG_OPTIONPARSING,  "details about parsing of options"},
    {"t", DEBUG_TESTS,          "messages from the test functions"},
    {"e", DEBUG_WARNING,       "warnings about non critical errors"},
    {NULL, 0, NULL},
};

 /*
  *------------------------------------------------------------------
  * 
  * Imgtools_SetDebugLevel --
  * 
  *     Set debug level - duh.
  * 
  * Results:
  *     Sets a interpreter-local variable to the given integer
  * 
  * Side effects
  *     debugmsg() will print only those messages for which the corres-
  *     ponding bit in level is set
  */
 void Imgtools_SetDebugLevel(interp, newLevel)
 Tcl_Interp *interp;    /* Set level in a Tcl variable in this interpreter */
 int newLevel;          /* bitwise ORed debug modes (see header) */
 {
     Tcl_Obj *levelPtr = Tcl_NewIntObj(newLevel);
     //char * errorMsg;
     // Create namespace??
     
     if (Tcl_SetVar2Ex(interp, "::imgtools::debug::level", NULL, levelPtr, TCL_LEAVE_ERR_MSG) == NULL) {
         printf("Imgtools_setDebugLevel: failed to set Tcl variable. The message was:\n%s\n",
             Tcl_GetStringResult(interp)
         );
         Tcl_ResetResult(interp);
     }
     
     return;
 }
 
 /*
  *------------------------------------------------------------------
  * 
  * Imgtools_GetDebugLevel --
  * 
  *     Returns the current setting for debug level.
  * 
  * Results:
  *     The current debug level.
  * 
  * Side effects
  *     none
  * 
  * -----------------------------------------------------------------
  */
 int Imgtools_GetDebugLevel(interp)
 Tcl_Interp *interp;
 {
    int level;
    Tcl_Obj *valuePtr;
    valuePtr = Tcl_GetVar2Ex(interp, "::imgtools::debug::level", NULL, TCL_LEAVE_ERR_MSG);
    if (valuePtr == NULL) {
        printf("Imgtools_DebugMsg: failed to get Tcl variable. The message was:\n%s\n",
            Tcl_GetStringResult(interp)
        );
        Tcl_ResetResult(interp);
        level = 0;
    } else if (Tcl_GetIntFromObj(interp, valuePtr, &level) != TCL_OK) {
        printf("Imgtools_DebugMsg: failed to get Int from Obj. The message was:\n%s\n",
            Tcl_GetStringResult(interp)
        );
        Tcl_ResetResult(interp);
        level = 0;
    }
    return level;
 }
 
 /*
  *------------------------------------------------------------------
  * 
  * Imgtools_GetDebugLevelStr --
  * 
  *     Returns the current setting for debug level as a string.
  * 
  * Results:
  *     The current debug level.
  * 
  * Side effects
  *     none
  * 
  * -----------------------------------------------------------------
  */
Tcl_Obj* Imgtools_GetDebugLevelStr(interp)
Tcl_Interp *interp;
{
    int level;
    Tcl_Obj *levelObj;
    levelObj = Tcl_NewStringObj("", -1);
    
    level = Imgtools_GetDebugLevel(interp);
    
    for (int i = 0; modes[i].letter != NULL; i++) {
        if ((modes[i].flags & level) == modes[i].flags) {
            Tcl_AppendStringsToObj(levelObj, modes[i].letter, NULL);
        }
    }
    
    return levelObj;
 }
 
 /*
  *------------------------------------------------------------------
  * 
  * Imgtools_SetDebugLevelFromObj --
  * 
  *     Set debug level parsing the string passed to the -debug option
  * 
  * Results:
  *     Sets a interpreter-local variable to the correct debug level
  *     Retruns a standard Tcl result
  * 
  * Side effects
  *     debugmsg() will print only those messages for which the corres-
  *     ponding bit in level is set.
  *     On parsing errors, leaves a message in interp and sets 
  *     an appropriate errorCode.
  *     
  */
 int Imgtools_SetDebugLevelFromObj(interp, levelObj)
 Tcl_Interp *interp;    /* used to set variable and for error messages */
 Tcl_Obj *levelObj;     /* read debug level string from here */
 {
     int level = 0;
     int index;
     //char *levelStr, *curChar;
     //levelStr = Tcl_GetString(levelObj);
     
     
     for (Tcl_Size i = 0; i < Tcl_GetCharLength(levelObj); i++) {
         //curChar = Tcl_UtfAtIndex(levelStr, i);
         if (Tcl_GetIndexFromObjStruct(interp, Tcl_GetRange(levelObj, i, i), 
                                   (const void *)modes, sizeof(DebugMode), 
                                   "mode", TCL_EXACT, &index) != TCL_OK
            ) {
             Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
             return TCL_ERROR;
         }
         level |= modes[index].flags;
     }
     
     Imgtools_SetDebugLevel(interp, level);
     
     return TCL_OK;
 }
 
 /*
  * ------------------------------------------------------------------
  * 
  * Imgtools_DebugMsg --
  * 
  *     Prints a debug message, if debuglevel matches the setting
  * 
  * Results:
  *     Prints the message to stdout.
  * 
  * Side effects
  *     None.
  */
 void Imgtools_DebugMsg(Tcl_Interp *interp, const int level, const char *format, ...)
 //Tcl_Interp *interp;        /* used to retrieve the debug level setting */
 //const int level;           /* level(s) of the message */
 //const char *format;        /* printf-style format string */
 {
    int levelSetting;
    va_list args;
    va_start(args, format);
    
    levelSetting = Imgtools_GetDebugLevel(interp);
    
    /*
     * Print message
     */
    if (level & levelSetting) {
        vprintf(format, args);
    }
    
    va_end(args);
    
    return;
 }
 
 /*
  * These commands expose get and set to Tcl scripts
  */
int Imgtools_SetDebugLevelCmd(clientData, interp, objc, objv)
ClientData clientData;
Tcl_Interp *interp;
int objc;
Tcl_Obj *const objv[];
{
    if(objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "debuglevel");
        return TCL_ERROR;
    }
    
    return Imgtools_SetDebugLevelFromObj(interp, objv[1]);
}
 
int Imgtools_GetDebugLevelCmd(clientData, interp, objc, objv)
ClientData clientData;
Tcl_Interp *interp;
int objc;
Tcl_Obj *const objv[];
{
    if(objc != 1) {
        Tcl_WrongNumArgs(interp, 1, objv, "");
        return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, Imgtools_GetDebugLevelStr(interp));
    return TCL_OK;
} 


/*
 * Dedicated printing functions
 */

/*
  * ------------------------------------------------------------------
  * 
  * Imgtools_PrintOptions --
  * 
  *     Prints the contents of a options struct
  *     If the debug level ist not DEBUG_PARAMETERS,  does nothing.
  * 
  * Results:
  *     Prints the message to stdout.
  * 
  * Side effects
  *     None.
  */
void Imgtools_PrintOptions(interp, options)
Tcl_Interp *interp;             /* needed to get debug level */
const Imgtools_CommonOpts *options;   /* print the values found in here */
{
    int levelSetting;
    
    levelSetting = Imgtools_GetDebugLevel(interp);
    
    /*
     * Skip if level does not match
     */
    if (! (DEBUG_PARAMETERS & levelSetting)) {
        return;
    }
    /*
     * Print
     */
    printf("Options:\n");
    printf("\t-compositingrule: %s (%s)\n",
           options->composite == TK_PHOTO_COMPOSITE_OVERLAY ? "overlay" : "set",
           options->options & OPT_COMPOSITE ? "user" : "default");
    printf("\t-shrink: %s (%s)\n",
           options->shrink ? "yes" : "no",
           options->options & OPT_SHRINK ? "user" : "default");
    printf("\t-interpolation: %s (%s)\n", filterNames[options->filter],
           options->options & OPT_FILTER ? "user" : "default");
    printf("\t-background: #%02X%02X%02X (%s)\n", options->background[0],
           options->background[1], options->background[2],
           options->options & OPT_BACKGROUND ? "user" : "default");
    printf("\t-alpha: %.3f (%s)\n", options->alpha, 
           options->options & OPT_ALPHA ? "user" : "default");
    printf("\t-from: (%d,%d) (%d,%d) (%s)\n", options->fromCorners[0].x,
           options->fromCorners[0].y, options->fromCorners[1].x, options->fromCorners[1].y,
           options->options & OPT_FROM ? "user" : "default");
    printf("\t-to: (%d,%d) (%d,%d) (%s)\n", options->toCorners[0].x,
           options->toCorners[0].y, options->toCorners[1].x, options->toCorners[1].y,
           options->options & OPT_TO ? "user" : "default");
    printf("\t-orient: %s (%s)\n",
           options->orient == VERTICAL ? "vertical": "horizontal",
           options->options & OPT_ORIENT ? "user" : "default");
    printf("\t-clipping: %d (%s)\n", options->clipping,
           options->options & OPT_CLIPPING ? "user" : "default");
    
    return;
}
