/*
   Copyright (c) 2009-2016  mingw-w64 project

   Contributing authors: Kai Tietz, Jonathan Yong

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <inttypes.h>
#include <stdint.h>

#include "m_token.h"
#include "m_ms.h"

#ifdef __MSVCRT__
#define MY_LL  "I64"
#else
#define MY_LL  "ll"
#endif

static char *mt_strcat (char *h, const char *add);


libmangle_gc_context_t *
libmangle_generate_gc (void)
{
  libmangle_gc_context_t *h = (libmangle_gc_context_t *) malloc (sizeof (libmangle_gc_context_t));
  if (h)
    memset (h, 0, sizeof (libmangle_gc_context_t));
  return h;
}

static void *
alloc_gc (libmangle_gc_context_t *gc, size_t size)
{
  sGcElem *n = (sGcElem *) malloc (size + sizeof (sGcElem));
  if (!n)
    {
      fprintf (stderr, "error: Run out of memory for %" MY_LL "x byte(s)\n",
        (unsigned long long) size);
      abort ();
    }
  memset (n, 0, size + sizeof (sGcElem));
  n->length = size;
  if (gc->head == NULL)
    gc->head = n;
  else
    gc->tail->chain = n;
  gc->tail = n;
  return & n->dta[0];
}

void
libmangle_release_gc (libmangle_gc_context_t *gc)
{
  sGcElem *n;
  if (!gc)
    return;
  while ((n = gc->head) != NULL)
    {
      gc->head = n->chain;
      free (n);
    }
  gc->tail = NULL;
  free (gc);
}

static void
free_gc (libmangle_gc_context_t *gc, const void *p)
{
  sGcElem *n, *l = NULL;

  if (!gc || !p)
    return;
  n = gc->head;
  while (n != NULL)
    {
      const void *ptr = &n->dta[0];
      if (ptr == p)
        {
          if (!l)
            gc->head = n->chain;
          else
            l->chain = n->chain;
          if (gc->tail == n)
            gc->tail = l;
          free (n);
          return;
        }
      l = n;
      n = n->chain;
    }
}

uMToken *
gen_tok (libmangle_gc_context_t *gc, enum eMToken kind, enum eMSToken subkind, size_t addend)
{
  uMToken *ret;
  switch (kind)
    {
    case eMToken_value:
      addend += sizeof (sMToken_value);
      break;
    case eMToken_name:
      addend += sizeof (sMToken_name);
      break;
    case eMToken_dim:
      addend += sizeof (sMToken_dim);
      break;
    case eMToken_unary:
      addend += sizeof (sMToken_Unary);
      break;
    case eMToken_binary:
      addend += sizeof (sMToken_binary);
      break;
    default:
      abort ();
    }
  addend += (addend + 15) & ~15;
  ret = (uMToken *) alloc_gc (gc, addend);
  if (!ret)
    abort ();

  MTOKEN_KIND (ret) = kind;
  MTOKEN_SUBKIND (ret) = subkind;

  return ret;
}

static void
dump_tok1 (FILE *fp, uMToken *p)
{
  if (!p)
    return;
  switch (MTOKEN_KIND (p))
    {
      case eMToken_value:
        fprintf (fp, "'value:%d: ", MTOKEN_SUBKIND (p));
        if (MTOKEN_VALUE_SIGNED (p))
          {
            fprintf(fp, "%"MY_LL "dLL", MTOKEN_VALUE (p));
          }
        else
          {
            fprintf(fp, "0x%"MY_LL"xULL", MTOKEN_VALUE (p));
          }
        fprintf (fp,"'");
        break;
      case eMToken_name:
        fprintf (fp,"'name:%d %s'", MTOKEN_SUBKIND (p), MTOKEN_NAME (p));
        break;
      case eMToken_dim:
        fprintf (fp,"'dim:%d %s", MTOKEN_SUBKIND (p), MTOKEN_DIM_NEGATE (p) ? "-" : "");
        libmangle_dump_tok (fp, MTOKEN_DIM_VALUE (p));
        if (MTOKEN_DIM_NTTP (p))
          {
            fprintf (fp," ");
            libmangle_dump_tok (fp, MTOKEN_DIM_NTTP (p));
          }
        fprintf (fp,"'");
        break;
      case eMToken_unary:
        fprintf (fp,"'unary:%d ", MTOKEN_SUBKIND (p));
        libmangle_dump_tok (fp, MTOKEN_UNARY (p));
        fprintf (fp, "'");
        break;
      case eMToken_binary:
        fprintf (fp,"'binary:%d ", MTOKEN_SUBKIND (p));
        libmangle_dump_tok (fp, MTOKEN_BINARY_LEFT (p));
        fprintf (fp," ");
        libmangle_dump_tok (fp, MTOKEN_BINARY_RIGHT (p));
        fprintf (fp, "'");
        break;
      default:
        fprintf (fp,"'kind(%d/%d):", MTOKEN_KIND (p), MTOKEN_SUBKIND(p));
        abort ();
    }
}

void
libmangle_dump_tok (FILE *fp, uMToken *p)
{
  if (!p)
    return;
  fprintf (fp, "'[");

  while (p)
    {
      dump_tok1 (fp, p);
      p = MTOKEN_CHAIN (p);
      if (p) fprintf (fp,",");
    }
  fprintf (fp,"]'");
}

uMToken *
chain_tok (uMToken *l, uMToken *add)
{
  uMToken *p = l;
  if (!l)
    return add;
  if (!add)
    return l;
  while (MTOKEN_CHAIN (p))
    p = MTOKEN_CHAIN (p);
  MTOKEN_CHAIN (p) = add;

  return l;
}

uMToken *
gen_value (libmangle_gc_context_t *gc, enum eMSToken skind, uint64_t val, int is_signed, int size)
{
  uMToken *ret = gen_tok (gc, eMToken_value, skind, 0);
  MTOKEN_VALUE (ret) = val;
  MTOKEN_VALUE_SIGNED (ret) = is_signed;
  MTOKEN_VALUE_SIZE (ret) = size;
  
  return ret;
}

uMToken *
gen_name (libmangle_gc_context_t *gc, enum eMSToken skind, const char *name)
{
  uMToken *ret;
  
  if (!name)
    name = "";
  ret = gen_tok (gc, eMToken_name, skind, strlen (name) + 1);
  strcpy (MTOKEN_NAME (ret), name);

  return ret;
}

uMToken *
gen_dim (libmangle_gc_context_t *gc, enum eMSToken skind, uint64_t val, const char *non_tt_param, int fSigned, int fNegate)
{
  uMToken *ret = gen_tok (gc, eMToken_dim, skind, 0);
  
  MTOKEN_DIM_VALUE(ret) = gen_value (gc, eMST_val, val, fSigned, 8);
  if (non_tt_param)
    MTOKEN_DIM_NTTP(ret) = gen_name (gc, eMST_nttp, non_tt_param);
  MTOKEN_DIM_NEGATE(ret) = fNegate;
  return ret;
}

uMToken *
gen_unary (libmangle_gc_context_t *gc, enum eMSToken skind, uMToken *un)
{
  uMToken *ret = gen_tok (gc, eMToken_unary, skind, 0);
  MTOKEN_UNARY (ret) = un;
  return ret;
}

uMToken *
gen_binary (libmangle_gc_context_t *gc, enum eMSToken skind, uMToken *l, uMToken *r)
{
  uMToken *ret = gen_tok (gc, eMToken_binary, skind, 0);
  MTOKEN_BINARY_LEFT (ret) = l;
  MTOKEN_BINARY_RIGHT (ret) = r;
  return ret;
}

static char *
mt_strcat (char *h, const char *add)
{
  char *ret;
  if (!add || *add == 0)
    return h;
  if (!h)
    ret = strdup (add);
  else
    {
      ret = (char *) malloc (strlen (h) + strlen (add) + 1);
      if (ret)
        {
	  strcpy (ret, h);
	  strcat (ret, add);
        }
      free (h);
    }
  if (!ret)
    {
      fprintf (stderr, " *** Run out of memory.\n");
      abort ();
    }
  return ret;
}

static char *
sprint_decl1 (char *txt, uMToken *r)
{
  while (r != NULL)
    {
      switch (MTOKEN_KIND (r))
	{
        case eMToken_name:
	  switch (MTOKEN_SUBKIND (r))
	    {
	    default:
	    case eMST_unmangled: case eMST_name:
	    case eMST_vftable: case eMST_vbtable: case eMST_vcall:
	    case eMST_nttp: case eMST_rtti:
	    case eMST_cv: case eMST_type: case eMST_opname:
	      txt = mt_strcat (txt, MTOKEN_NAME (r));
	      break;
	    case eMST_colon:
	      txt = mt_strcat (txt, MTOKEN_NAME (r));
	      txt = mt_strcat (txt, ":");
	      break;
	    }
	  break;
	case eMToken_value:
	  switch (MTOKEN_SUBKIND (r))
	    {
	    default:
	      break;
	    case eMST_gcarray:
		txt = mt_strcat (txt, "__gc[");
	    case eMST_val:
	      if (! MTOKEN_VALUE_SIGNED (r))
	        {
		  char s[128];
		  if ((MTOKEN_VALUE (r) >> 32) != 0)
		    sprintf (s, "0x%lx%08lx", (unsigned long) (MTOKEN_VALUE (r) >> 32), (unsigned long) MTOKEN_VALUE (r));
		  else
		    sprintf (s,"0x%lx", (unsigned long) MTOKEN_VALUE (r));
		  strcat (s, "U");
		  txt = mt_strcat (txt, s);
	        }
	      else
	        {
		  char s[128];
		  sprintf (s,"%"MY_LL"d", (long long) MTOKEN_VALUE (r));
		  txt = mt_strcat (txt, s);
	        }
	      if (MTOKEN_VALUE_SIZE (r) == 8)
		txt = mt_strcat (txt, "LL");
	      else if (MTOKEN_VALUE_SIZE (r) == 4)
		txt = mt_strcat (txt, "L");
	      if (MTOKEN_SUBKIND (r) == eMST_gcarray)
		txt = mt_strcat (txt, "]");
	      break;
	    }
	  break;
	case eMToken_dim:
	  switch (MTOKEN_SUBKIND (r))
	    {
	      default:
		/* FALL THROUGH */
	      case eMST_dim:
		if (MTOKEN_DIM_NEGATE (r))
		  txt = mt_strcat (txt, "-");
		if (MTOKEN_DIM_NTTP (r))
		  txt = sprint_decl1 (txt, MTOKEN_DIM_NTTP (r));
		if (MTOKEN_DIM_VALUE (r))
		  txt = sprint_decl1 (txt, MTOKEN_DIM_VALUE (r));
		break;
	    }
	  break;
	case eMToken_unary:
	  switch (MTOKEN_SUBKIND (r))
	    {
  	    case eMST_slashed:
	      txt = mt_strcat (txt, "/");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      txt = mt_strcat (txt, "/");
	      break;
	    case eMST_array:
	      txt = mt_strcat (txt, "[");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      txt = mt_strcat (txt, "]");
	      break;
	    case eMST_ltgt:
	      txt = mt_strcat (txt, "<");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      txt = mt_strcat (txt, ">");
	      break;
	    case eMST_frame:
	      txt = mt_strcat (txt, "{");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      txt = mt_strcat (txt, "}");
	      break;
	    case eMST_rframe:
	      txt = mt_strcat (txt, "(");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      txt = mt_strcat (txt, ")");
	      break;
	    case eMST_lexical_frame:
	      txt = mt_strcat (txt, "'");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      txt = mt_strcat (txt, "'");
	      break;
	    case eMST_throw:
	      txt = mt_strcat (txt, "throw ");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      break;
	    case eMST_destructor:
	      txt = mt_strcat (txt, "~");
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      break;
	    case eMST_element: case eMST_template_argument_list:
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      if (MTOKEN_CHAIN (r) != NULL)
		txt = mt_strcat (txt, ",");
	      break;
	    default:
	    case eMST_oper: case eMST_scope:
	      txt = sprint_decl1 (txt, MTOKEN_UNARY (r));
	      break;
	    }
	  break;
	case eMToken_binary:
	  switch (MTOKEN_SUBKIND (r))
	    {
	    default:
	    case eMST_combine: case eMST_ecsu:
	    case eMST_based:
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_LEFT (r));
	      txt = mt_strcat (txt, " ");
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_RIGHT (r));
	      break;
	    case eMST_coloncolon:
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_LEFT (r));
	      txt = mt_strcat (txt, "::");
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_RIGHT (r));
	      break;
	    case eMST_assign:
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_LEFT (r));
	      txt = mt_strcat (txt, "=");
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_RIGHT (r));
	      break;
	    case eMST_templateparam: case eMST_nonetypetemplateparam:
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_LEFT (r));
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_RIGHT (r));
	      break;
	    case eMST_exp:
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_LEFT (r));
	      txt = mt_strcat (txt, "e");
	      txt = sprint_decl1 (txt, MTOKEN_BINARY_RIGHT (r));
	      break;
	    }
	  break;
	default:
	  break;
	}
      r = MTOKEN_CHAIN (r);
    }
  return txt;
}

static void
trim_str (char *str)
{
  char *r, *l;
  l = r = str;
  while ( *r != '\0')
  {
    if ( *r == ' ') 
    {
       if (r[1]=='(' || r[1]=='[' || r[1]==' ') r++;
       else
       if (l!=str && (l[-1]=='*' || l[-1]=='&' || l[-1]==')')) r++;
       else
       if (l!=r) *l++ = *r++;
       else
       r++, l++;
    }
    else if (l!=r) *l++ = *r++;
    else r++, l++;
  }
  *l = '\0';
}

char *
libmangle_sprint_decl (uMToken *r)
{
  char *ret = NULL;
  if (r)
    ret = sprint_decl1(NULL, r);
  trim_str (ret);
  return ret;
}

void
libmangle_print_decl (FILE *fp, uMToken *r)
{
  char *ret = libmangle_sprint_decl (r);
  if (ret)
    {
      fprintf (fp, "%s\n", ret);
      free (ret);
    }
  else
    {
      fprintf (fp, "<NULL>\n");
    }
}

#if defined (TEST)

static const char *szTest[]= {
	"??_C@_0BL@CNOONJFP@?$GAplacement?5delete?5closure?8?$AA@",
	"??_C@_07LFCOJCAC@__int64?$AA@",
	"?outputString@UnDecorator@@0PADA",
	"?outputString@UnDecorator@@1PADA",
	"?outputString@UnDecorator@@2PADA",
	"?outputString@UnDecorator@@3PADA",
	"?outputString@UnDecorator@@4PADA",
	"?outputString@UnDecorator@@5PADA",
	"?outputString@UnDecorator@@6PADA",
	"?outputString@UnDecorator@@7PADA",
	"?outputString@UnDecorator@@8PADA",
	"?outputString@UnDecorator@@9PADA",
	"??_5DName@@QAEAAV0@ABV0@@Z",
	"??_7pDNameNode@@6B@",
	"??_7exception@@6B@",
	"??_Ebad_cast@@UAEPAXI@Z",
	"??_Fbad_cast@@QAEXXZ",
	"??_Gbad_cast@@UAEPAXI@Z",
	"??_M@YGXPAXIHP6EX0@Z@Z",
	"??_R1A@?0A@A@exception@@8",
	"??_R2exception@@8","??_R3exception@@8",
	"??_R0?AVexception@@@8","??_R4exception@@6B@",
	"??0Block@HeapManager@@QAE@XZ",
	"??0DNameNode@@IAE@XZ",
	"??0__non_rtti_object@@QAE@ABV0@@Z",
	"??1bad_typeid@@UAE@XZ",
	"??2@YAPAXIAAVHeapManager@@H@Z",
	"??3@YAXPAX@Z",
	"??4DName@@QAEAAV0@ABV0@@Z",
	"??4DName@@QAEAAV0@W4DNameStatus@@@Z",
	"??4DName@@QAEAAV0@D@Z",
	"??4DName@@QAEAAV0@PBD@Z",
	"??YReplicator@@QAEAAV0@ABVDName@@@Z",
	"??H@YA?AVDName@@W4DNameStatus@@ABV0@@Z",
	"??H@YA?AVDName@@PBDABV0@@Z",
	"__CallSettingFrame@12",
	"??3@YAXPAX@Z",
	"??_Etype_info@@UAEPAXI@Z",
	"??_Gtype_info@@UAEPAXI@Z",
	"??1type_info@@UAE@XZ",
	"??_R1A@?0A@A@type_info@@8",
	"??_R2type_info@@8",
	"??_R0?AVtype_info@@@8",
	"??_R4type_info@@6B@",
	"??_7type_info@@6B@",
	"??_Gtype_info@@UAEPAXI@Z",
	"??8type_info@@QBEHABV0@@Z",
/*	"@foo@bar@$bctr$q", borland */
	NULL
};

int main()
{
  int i;
  uMToken *h;
  char *txt;
  for (i = 0; szTest[i]!=NULL; i++)
  {
    libmangle_gc_context_t *gc = libmangle_generate_gc ();
    h = libmangle_decode_ms_name (gc, szTest[i]);
    txt = libmangle_sprint_decl (h);
    fprintf (stderr, "%s\n", txt);
    free (txt);
    libmangle_release_gc (gc);
  }
  return 0;
}
#endif
