// -----------------------------------------------
// PositionalPrintfTest.cpp

#define UNICODE
#include <tchar.h>
#include <stdio.h>

#include "PositionalPrintf.h"

// Try different things with the fancy printf function
int _tmain(int argc, _TCHAR* argv[])
{
  printf(_T("%s %g %d %c\n"), _T("Hola"), 3.4f, 34, _T('3'));

  PositionalPrintf("%s %g %d %c\n", "Hola", 3.4f, 34, '3');
  PositionalPrintf(L"%s %g %d %c\n", L"Hola", 3.4f, 34, L'3');
  PositionalPrintf(_T("%s %g %d %c\n"), _T("Hola"), 3.4f, 34, _T('3'));

  PositionalPrintf("%{3}c %g - %{0}s's %{1}s\n%{3}c %{4}g - El %{1}s de %{0}s", "Rick", "Bar", 'S', 'E', 3.1415f);
}

// -----------------------------------------------
// PositionalPrintf.h

// Only two versions of this template are instantiated, char and wchar_t
// Any other attempt to use this function will result in a link error.
template<typename T>
void PositionalPrintf(const T *pszFmt, ...);

// -----------------------------------------------
// PositionalPrintf.cpp

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


// Templated functions for char/wchar_t operations.
template <typename T> int TempAtoi(const T *p);

template<> int TempAtoi(const char *p) { return atoi(p); }
template<> int TempAtoi(const wchar_t *p) { return _wtoi(p); }

template <typename T> void TempVPrintf(const T *p, va_list va);

template<> void TempVPrintf(const char *p, va_list va)     { vprintf(p, va); }
template<> void TempVPrintf(const wchar_t *p, va_list va)  { vwprintf(p, va); }

// The actual function
template<typename T>
void PositionalPrintf(const T *pszFmt, ...)
{
  enum TParam
  {
    TP_UNK,
    TP_CHAR,
    TP_INT,
    TP_STR,
    TP_FLOAT,
  };
  static const int MAX_PARAMS = 100;

  // Here we store the parameters we should be expecting on the stack
  // We decide this based on the format string.
  int nParams  = 0;
  int nextParam = 0;
  TParam aParams[MAX_PARAMS];

  // Here we store each '%' format element's data from the format string.
  struct
  {
    TParam param;
    int    pos;
  } aFormats[MAX_PARAMS];
  int nFormats = 0;

  for (int i = 0; i < MAX_PARAMS; ++i)
    aParams[i] = TP_UNK;

  // Scan the format string to detect expected parameters and the way to extract them from the stack.
  bool bError = false;
  for (const T *s = pszFmt; *s; ++s)
  {
    if (*s == T('%'))
    {
      // Found a format element. First thing to do is identify the position of the parameter
      // on the stack.
      int pos;
      if (*(s+1) == T('{'))
      {
        // Explicit position modifier
        pos = TempAtoi(s+2);
        nextParam = pos+1;
      }
      else
        pos = nextParam++; // Just se the next position.
      while (*s && *s != T('c') && *s != T('d') && *s != T('g') && *s != T('s'))
      {
        // Here we could detect '*' parameters (which won't work), missing '}', etc.
        s++;
      }
      if (!*s)
        break;

      if (pos >= MAX_PARAMS)
      {
        // error
        printf("ERROR! Parameter %d out of range.\n", pos);
        bError = true;
      }
      else 
      {
        // Identifica el tipo de parámetro que es
        // Aqui podemos extender esto un montón para cubrir todos los tipos de formato
        TParam p;
        if (*s == T('c'))      p = TP_CHAR;
        else if (*s == T('d')) p = TP_INT;
        else if (*s == T('s')) p = TP_STR;
        else                   p = TP_FLOAT;

        if (aParams[pos] != TP_UNK && aParams[pos] != p)
        {
          // error
          printf("ERROR! Parameter %d used with different format specifiers (%d and %d).\n", pos, aParams[pos], p);
          bError = true;
        }

        // Store the parameter type to be expected at position 'pos' on the stack.
        aParams[pos] = p;
        if (nParams <= pos)
          nParams = pos+1;

        // Store the parameter type and position on the stack, for this format element.
        if (nFormats >= MAX_PARAMS)
        {
          // error
          printf("ERROR! Too many format elements (%d)!\n", nFormats);
          bError = true;
        }
        else
        {
          aFormats[nFormats].param = p;
          aFormats[nFormats].pos   = pos;
          nFormats++;
        }
      }
    }
  }

  // Verify that all parameters on the stack are referenced. (optional)
  for (int i = 0; i < nParams; i++)
  {
    if (aParams[i] == TP_UNK)
    {
      // error
      // Benign if we assume that unused parameters are of INT size.
//      printf("ERROR! Parameter %d undefined.\n", i);
//      bError = true;
    }
  }
  if (bError)
    return;

  // Build a new format string removing the {n} modifiers
  T szNewFmt[3000];
  {
    T *p = szNewFmt;

    szNewFmt[0] = T(0);

    for (const T *s = pszFmt; *s; s++)
    {
      *p++ = *s;
      if (*s == T('%'))
      {
        if (*(s+1) == T('{'))
        {
          s += 2;
          while (*s && *s != T('}'))
            s++;
          if (!*s)
            break;
        }
      }
    }
    *p = T(0); // Zero-end the new format string
  }
  
  // Copy the parameters to the parameter buffer in correct order.
  // Here comes the system-dependent part
  char aParamBuf[3000];
  {
    char *p = aParamBuf;

    for (int i = 0; i < nFormats; ++i)
    {
      va_list va;
      va_start(va, pszFmt);

      // Skip stack parameters until the one we're looking for.
      // System-dependent: assumes that anything that is not of type double is of INT size
      for (int j = 0; j < aFormats[i].pos; ++j)
      {
        if (aParams[j] == TP_FLOAT)
          va_arg(va, double);
        else
          va_arg(va, int);
      }

      // Copy the parameter into the new parameter buffer
      // System-dependent: the size thing again.
      // System-dependent: assumes things about the order in which parameters are stored on the stack.
      if (aParams[j] == TP_FLOAT)
      {
        double d = va_arg(va, double);
        memcpy(p, &d, sizeof(d));
        p += sizeof(d);
      }
      else
      {
        int d = va_arg(va, int);
        memcpy(p, &d, sizeof(d));
        p += sizeof(d);
      }
    }
  }

  // System-dependent: assumes that aParamBuf can be cast straight to va_list.
  TempVPrintf(szNewFmt, (va_list)aParamBuf);
}

// Instantiate the char and wchar_t versions of the function.
template void PositionalPrintf(const char *pszFmt, ...);
template void PositionalPrintf(const wchar_t *pszFmt, ...);

