#ifndef __SEC_DEBUG_H
#define __SEC_DEBUG_H

#include <map>
#include <string>
#include <ostream>

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

enum debug_from_e
{
  SECBUS_DEBUG_SRC_IO,
  SECBUS_DEBUG_SRC_IN,
  SECBUS_DEBUG_SRC_OUT,
  SECBUS_DEBUG_SRC_FSM,
  SECBUS_DEBUG_SRC_REGISTER,
  SECBUS_DEBUG_SRC_STD
};

enum
{
  SECBUS_DEBUG_EMPTY,
  SECBUS_DEBUG_NONE,
  SECBUS_DEBUG_ERROR,
  SECBUS_DEBUG_STAT,
  SECBUS_DEBUG_TRACE
};

#define SECBUS_DEBUG SECBUS_DEBUG_ERROR

#define DEBUG(level, type, mes, ...)					\
  do									\
  {									\
    if (SECBUS_DEBUG_##level <= SECBUS_DEBUG)				\
    {									\
      Debug *dbg = Debug::getDebug();					\
      dbg->push_debug(SECBUS_DEBUG_SRC_##type, mes, ##__VA_ARGS__);	\
    }									\
  } while(0)


#define ADD_DEBUG(t, f)				\
  do						\
  {						\
    Debug *dbg = Debug::getDebug();		\
    dbg->add_type(SECBUS_DEBUG_SRC_##t, f);	\
  } while(0)					\
    
#define CLOSE_DEBUG()				\
  do						\
  {						\
    Debug::destroy();				\
  } while(0)

class Debug
{
private:
  /**
   * constructor is privated as it is a singleton class
   */
  Debug()
  {
    _src = std::map<enum debug_from_e, FILE*>();
  }
  
  /**
   * @this closes the opened file
   */
  ~Debug()
  {
    for (std::map<enum debug_from_e, FILE*>::iterator it = _src.begin() ;
	 it != _src.end() ;
	 ++it)
    {
      fclose(it->second);
    }
  }
  
  static Debug *_sgt;
  std::map<enum debug_from_e, FILE*> _src;

public:
  /**
   * singleton class, @return the instance of the singleton object
   */
  static Debug *getDebug()
  {
    if (_sgt == NULL)
      _sgt = new Debug();

    return _sgt;
  }

  /**
   * clean ressources
   */
  static void destroy()
  {
    if (_sgt != NULL)
      delete _sgt;
  }

  /**
   * @this adds a new output file to print the debug, you must provide
   * a type (@see enum debug_from_e to add new type) and a
   * filename. The file will be opened in writing mode, waiting for
   * debug
   */
  void add_type(enum debug_from_e type, const char *filename)
  {
    FILE* f = fopen(filename, "w");
    if (f == NULL)
    {
      fprintf(stderr, "[ERROR] Can't open %s as debug file, "
	      "print debug message to /dev/null\n", filename);
      _src.insert(std::pair<enum debug_from_e, FILE*>(type, fopen("/dev/null", "w")));
      return;
    }

    _src.insert(std::pair<enum debug_from_e, FILE*>(type, f));
  }

  /**
   * @this adds a new output stream directly pointed by a FILE*, it
   * allows to provide another output that a file, like stderr,
   * stdout, ...
   */
  void add_type(enum debug_from_e type, FILE* out)
  {
    _src.insert(std::pair<enum debug_from_e, FILE*>(type, out));
  }
   
  /**
   * @this adds msg to the output stream linked to type
   */
  void push_debug(enum debug_from_e type, const char *msg, ...)
  {
    va_list ap;
    FILE* f = _src.at(type);

    va_start(ap, msg);
    vfprintf(f, msg, ap);
    va_end(ap);

    fflush(f);
  }
};

Debug *Debug::_sgt = NULL;
  
#endif
