/* asmstructs.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* structure handling                                                        */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"
#include <string.h>

#include "nls.h"
#include "nlmessages.h"
#include "strutil.h"

#include "trees.h"
#include "errmsg.h"
#include "symbolsize.h"

#include "as.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmlabel.h"
#include "asmstructs.h"
#include "errmsg.h"
#include "codepseudo.h"
#include "dyn_array.h"

#include "as.rsc"

/*****************************************************************************/

typedef struct sStructNode
{
  TTree Tree;
  Boolean Defined;
  PStructRec StructRec;
} TStructNode, *PStructNode;

/*****************************************************************************/

PStructStack StructStack,        /* momentan offene Strukturen */
             pInnermostNamedStruct;
as_addrspace_t StructSaveSeg;    /* gesichertes Segment waehrend Strukturdef.*/
PStructNode StructRoot = NULL;

/*****************************************************************************/

PStructRec CreateStructRec(void)
{
  PStructRec Neu;

  Neu = (PStructRec) malloc(sizeof(TStructRec));
  if (Neu)
  {
    Neu->TotLen = 0;
    Neu->Elems = NULL;
    Neu->ExtChar = '\0';
    Neu->DoExt = Neu->IsUnion = False;
  }
  return Neu;
}

/*!------------------------------------------------------------------------
 * \fn     DestroyStructElem(PStructElem pStructElem)
 * \brief  destroy/free struct element
 * \param  pStructElem element to destroy
 * ------------------------------------------------------------------------ */

void DestroyStructElem(PStructElem pStructElem)
{
  if (pStructElem->pElemName) free(pStructElem->pElemName);
  if (pStructElem->pRefElemName) free(pStructElem->pRefElemName);
  free(pStructElem);
}

void DestroyStructRec(PStructRec StructRec)
{
  PStructElem Old;

  while (StructRec->Elems)
  {
    Old = StructRec->Elems;
    StructRec->Elems = Old->Next;
    DestroyStructElem(Old);
  }
  free(StructRec);
}

/*!------------------------------------------------------------------------
 * \fn     CreateStructElem(const char *pElemName)
 * \brief  create a new entry for a structure definition
 * \param  pElemName name of the structure element
 * \return * to element or NULL if allocation failed
 * ------------------------------------------------------------------------ */

void ExpandStructStd(const tStrComp *pVarName, const struct sStructElem *pStructElem, LargeWord Base)
{
  LabelHandle(pVarName, Base + pStructElem->Offset, True);
  if (pStructElem->OpSize != eSymbolSizeUnknown)
  {
    String ext_name_buf;
    tStrComp ext_name;
    const tStrComp *p_ext_name;

    StrCompMkTemp(&ext_name, ext_name_buf, sizeof(ext_name_buf));
    p_ext_name = ExpandStrSymbol(&ext_name, pVarName, !CaseSensitive);
    if (p_ext_name)
      SetSymbolOrStructElemSize(p_ext_name, pStructElem->OpSize);
  }
}

PStructElem CreateStructElem(const tStrComp *pElemName)
{
  String ext_name_buf;
  tStrComp ext_name;
  const tStrComp *p_ext_name;
  PStructElem pNeu;

  StrCompMkTemp(&ext_name, ext_name_buf, sizeof(ext_name_buf));
  p_ext_name = ExpandStrSymbol(&ext_name, pElemName, !CaseSensitive);
  if (!p_ext_name)
    return NULL;

  pNeu = (PStructElem) calloc(1, sizeof(TStructElem));
  if (pNeu)
  {
    pNeu->pElemName = as_strdup(p_ext_name->str.p_str);
    pNeu->pRefElemName = NULL;
    pNeu->ExpandFnc = ExpandStructStd;
    pNeu->Offset = 0;
    pNeu->BitPos = pNeu->BitWidthM1 = -1;
    pNeu->OpSize = eSymbolSizeUnknown;
    pNeu->Next = NULL;
  }
  return pNeu;
}

/*!------------------------------------------------------------------------
 * \fn     CloneStructElem(const struct sStrComp *pCloneElemName, const struct sStructElem *pSrc)
 * \brief  generate copy of struct element, with different name
 * \param  pCloneElemName new element's name
 * \param  pSrc source to clone from
 * \return * to new element or NULL
 * ------------------------------------------------------------------------ */

PStructElem CloneStructElem(const struct sStrComp *pCloneElemName, const struct sStructElem *pSrc)
{
  PStructElem pResult = CreateStructElem(pCloneElemName);
  if (!pResult)
    return pResult;

  pResult->Offset = pSrc->Offset;
  pResult->OpSize = pSrc->OpSize;
  pResult->BitPos = pSrc->BitPos;
  pResult->ExpandFnc = pSrc->ExpandFnc;
  pResult->pRefElemName = as_strdup(pSrc->pRefElemName);
  return pResult;
}

/*!------------------------------------------------------------------------
 * \fn     AddStructElem(PStructRec pStructRec, PStructElem pElement)
 * \brief  add a new element to a structure definition
 * \param  pStructRec structure to extend
 * \param  pElement new element
 * \return True if element was added
 * ------------------------------------------------------------------------ */

Boolean AddStructElem(PStructRec pStructRec, PStructElem pElement)
{
  PStructElem pRun, pPrev;
  Boolean Duplicate = False;

  if (!CaseSensitive && pElement->pRefElemName)
    NLS_UpString(pElement->pRefElemName);

  for (pPrev = NULL, pRun = pStructRec->Elems; pRun; pPrev = pRun, pRun = pRun->Next)
  {
    Duplicate = CaseSensitive ? !strcmp(pElement->pElemName, pRun->pElemName) : !as_strcasecmp(pElement->pElemName, pRun->pElemName);
    if (Duplicate)
    {
      WrXError(ErrNum_DuplicateStructElem, pElement->pElemName);
      break;
    }
  }

  if (!Duplicate)
  {
    if (pPrev)
      pPrev->Next = pElement;
    else
      pStructRec->Elems = pElement;
  }
  BumpStructLength(pStructRec, pElement->Offset);
  if (Duplicate)
    DestroyStructElem(pElement);
  return !Duplicate;
}

/*!------------------------------------------------------------------------
 * \fn     SetStructElemSize(PStructRec pStructRec, const tStrComp *pElemName, tSymbolSize Size)
 * \brief  set the operand size of a structure's element
 * \param  pStructRec structure the element is contained in
 * \param  pElemName element's name
 * \param  Size operand size to set
 * ------------------------------------------------------------------------ */

void SetStructElemSize(PStructRec pStructRec, const tStrComp *pElemName, tSymbolSize Size)
{
  PStructElem pRun;
  String exp_name_buf;
  tStrComp exp_name;
  const tStrComp *p_exp_name;

  StrCompMkTemp(&exp_name, exp_name_buf, sizeof(exp_name_buf));
  p_exp_name = ExpandStrSymbol(&exp_name, pElemName, !CaseSensitive);
  if (!p_exp_name)
    return;
  for (pRun = pStructRec->Elems; pRun; pRun = pRun->Next)
  {
    if (!strcmp(p_exp_name->str.p_str, pRun->pElemName))
    {
      pRun->OpSize = Size;
      return;
    }
  }
  fprintf(stderr, "SetStructElemSize: cannot set size of '%s', something wicked is going on\n", p_exp_name->str.p_str);
}

/*!------------------------------------------------------------------------
 * \fn     ResolveStructReferences(PStructRec pStructRec)
 * \brief  resolve referenced elements in structure
 * \param  pStructRec structure to work on
 * ------------------------------------------------------------------------ */

void ResolveStructReferences(PStructRec pStructRec)
{
  Boolean AllResolved, DidResolve;
  PStructElem pRun, pRef;

  /* iterate over list until all symbols resolved, or we failed to resolve at least one per pass */

  do
  {
    AllResolved = True;
    DidResolve = False;

    for (pRun = pStructRec->Elems; pRun; pRun = pRun->Next)
      if (pRun->pRefElemName)
      {
        /* Only resolve to elements that already have been resolved.
           That's why we may need more than one pass.  */

        for (pRef = pStructRec->Elems; pRef; pRef = pRef->Next)
        {
          if (!strcmp(pRun->pRefElemName, pRef->pElemName) && !pRef->pRefElemName)
          {
            pRun->Offset = pRef->Offset;
            if (pRun->OpSize == eSymbolSizeUnknown)
              pRun->OpSize = pRef->OpSize;
            free(pRun->pRefElemName);
            pRun->pRefElemName = NULL;
            DidResolve = True;
            break;
          }
        }
        if (!pRef)
          AllResolved = False;
      }
  }
  while (!AllResolved && DidResolve);
  if (!AllResolved)
  {
    String Str;

    for (pRun = pStructRec->Elems; pRun; pRun = pRun->Next)
      if (pRun->pRefElemName)
      {
        strmaxcpy(Str, pRun->pElemName, STRINGSIZE);
        strmaxcat(Str, " -> ", STRINGSIZE);
        strmaxcat(Str, pRun->pRefElemName, STRINGSIZE);
        WrXError(ErrNum_UnresolvedStructRef, Str);
      }
  }
}

void BuildStructName(char *pResult, unsigned ResultLen, const char *pName)
{
  PStructStack ZStruct;
  String tmp2;

  strmaxcpy(pResult, pName, ResultLen);
  for (ZStruct = StructStack; ZStruct; ZStruct = ZStruct->Next)
    if (ZStruct->StructRec->DoExt && ZStruct->Name[0])
    {
      as_snprintf(tmp2, sizeof(tmp2), "%s%c", ZStruct->pBaseName, ZStruct->StructRec->ExtChar);
      strmaxprep(pResult, tmp2, ResultLen);
    }
}

void AddStructSymbol(const char *pName, LargeWord Value)
{
  PStructStack ZStruct;

  /* what we get is offset/length in current structure.  Add to
     it all offsets in parent structures, i.e. leave out SaveCurrPC
     of bottom of stack which contains saved PC of non-struct segment: */

  for (ZStruct = StructStack; ZStruct->Next; ZStruct = ZStruct->Next)
    Value += ZStruct->SaveCurrPC;

  {
    String tmp;
    tStrComp TmpComp;

    as_snprintf(tmp, sizeof(tmp), "%s%c%s", pInnermostNamedStruct->Name, pInnermostNamedStruct->StructRec->ExtChar, pName);
    StrCompMkTemp(&TmpComp, tmp, sizeof(tmp));
    EnterIntSymbol(&TmpComp, Value, SegNone, False);
  }
}

void BumpStructLength(PStructRec StructRec, LongInt Length)
{
  if (StructRec->TotLen < Length)
    StructRec->TotLen = Length;
}

static Boolean StructAdder(PTree *PDest, PTree Neu, void *pData)
{
  PStructNode NewNode = (PStructNode) Neu, *Node;
  Boolean Protest = *((Boolean*)pData), Result = False;

  if (!PDest)
  {
    NewNode->Defined = TRUE;
    return True;
  }

  Node = (PStructNode*) PDest;
  if ((*Node)->Defined)
  {
    if (Protest)
      WrXError(ErrNum_DoubleStruct, Neu->Name);
    else
    {
      DestroyStructRec((*Node)->StructRec);
      (*Node)->StructRec = NewNode->StructRec;
    }
  }
  else
  {
    DestroyStructRec((*Node)->StructRec);
    (*Node)->StructRec = NewNode->StructRec;
    (*Node)->Defined = True;
    return True;
  }
  return Result;
}

void AddStruct(PStructRec StructRec, char *Name, Boolean Protest)
{
  PStructNode Node;
  PTree TreeRoot;

  Node = (PStructNode) malloc(sizeof(TStructNode));
  if (Node)
  {
    Node->Tree.Left = Node->Tree.Right = NULL;
    Node->Tree.Name = as_strdup(Name);
    if (!CaseSensitive)
      NLS_UpString(Node->Tree.Name);
    Node->Tree.Attribute = MomSectionHandle;
    Node->Defined = FALSE;
    Node->StructRec = StructRec;
    TreeRoot = &(StructRoot->Tree);
    EnterTree(&TreeRoot, (PTree)Node, StructAdder, &Protest);
    StructRoot = (PStructNode)TreeRoot;
  }
}

static PStructRec FoundStruct_FNode(LongInt Handle, const char *Name)
{
  PStructNode Lauf;

  Lauf = (PStructNode) SearchTree((PTree)StructRoot, Name, Handle);
  return Lauf ? Lauf->StructRec : NULL;
}

Boolean FoundStruct(PStructRec *Erg, const char *pName)
{
  PSaveSection Lauf;

  *Erg = FoundStruct_FNode(MomSectionHandle, pName);
  if (*Erg)
    return True;
  Lauf = SectionStack;
  while (Lauf)
  {
    *Erg = FoundStruct_FNode(Lauf->Handle, pName);
    if (*Erg)
      return True;
    Lauf = Lauf->Next;
  }
  return False;
}

static void ResDef(PTree Tree, void *pData)
{
  UNUSED(pData);

  ((PStructNode)Tree)->Defined = FALSE;
}

void ResetStructDefines(void)
{
  IterTree((PTree)StructRoot, ResDef, NULL);
}

typedef struct
{
  LongInt Sum;
  as_dynstr_t num_str;
} TPrintContext;

static void PrintDef(PTree Tree, void *pData)
{
  PStructNode Node = (PStructNode)Tree;
  PStructElem Elem;
  TPrintContext *pContext = (TPrintContext*)pData;
  String s;
  char NumStr[30];
  TempResult t;

  UNUSED(pData);
  as_tempres_ini(&t);

  WrLstLine("");
  pContext->Sum++;
  strmaxcpy(s, Node->Tree.Name, STRINGSIZE);
  if (Node->Tree.Attribute != -1)
  {
    strmaxcat(s, "[", STRINGSIZE);
    strmaxcat(s, GetSectionName(Node->Tree.Attribute), STRINGSIZE);
    strmaxcat(s, "]", STRINGSIZE);
  }
  WrLstLine(s);
  for (Elem = Node->StructRec->Elems; Elem; Elem = Elem->Next)
  {
    as_tempres_set_int(&t, Elem->Offset);
    StrSym(&t, False, &pContext->num_str, ListRadixBase);
    as_snprintf(s, sizeof(s), "%3s", pContext->num_str.p_str);
    if (Elem->BitPos >= 0)
    {
      if (Elem->BitWidthM1 >= 0)
        as_snprintf(NumStr, sizeof(NumStr), ".%d-%d", Elem->BitPos, Elem->BitPos + Elem->BitWidthM1);
      else
        as_snprintf(NumStr, sizeof(NumStr), ".%d", Elem->BitPos);
    }
    else
      *NumStr = '\0';
    as_snprcatf(s, sizeof(s), "%-6s", NumStr);
    if (Elem->OpSize != eSymbolSizeUnknown)
      as_snprcatf(s, sizeof(s), "(%s)", GetSymbolSizeName(Elem->OpSize));
    else
      strmaxcat(s, "   ", STRINGSIZE);
    as_snprcatf(s, sizeof(s), " %s", Elem->pElemName);
    WrLstLine(s);
  }
  as_tempres_free(&t);
}

void PrintStructList(void)
{
  TPrintContext Context;
  String s;

  if (!StructRoot)
    return;

  NewPage(ChapDepth, True);
  WrLstLine(getmessage(Num_ListStructListHead1));
  WrLstLine(getmessage(Num_ListStructListHead2));

  Context.Sum = 0;
  as_dynstr_ini(&Context.num_str, STRINGSIZE);
  IterTree((PTree)StructRoot, PrintDef, &Context);
  as_dynstr_free(&Context.num_str);
  as_snprintf(s, sizeof(s), "%" PRILongInt "%s",
              Context.Sum,
              getmessage((Context.Sum == 1) ? Num_ListStructSumMsg : Num_ListStructSumsMsg));
  WrLstLine(s);
}

static void ClearNode(PTree Tree, void *pData)
{
  PStructNode Node = (PStructNode) Tree;
  UNUSED(pData);

  DestroyStructRec(Node->StructRec);
}

void ClearStructList(void)
{
  PTree TreeRoot;

  TreeRoot = &(StructRoot->Tree);
  StructRoot = NULL;
  DestroyTree(&TreeRoot, ClearNode, NULL);
}

static void ExpandStruct_One(PStructRec StructRec, char *pVarPrefix, char *pStructPrefix, LargeWord Base)
{
  PStructElem StructElem;
  int VarLen, RemVarLen, StructLen, RemStructLen;
  tStrComp TmpComp;

  VarLen = strlen(pVarPrefix);
  pVarPrefix[VarLen] = StructRec->ExtChar;
  RemVarLen = STRINGSIZE - 3 - VarLen;

  StructLen = strlen(pStructPrefix);
  pStructPrefix[StructLen] = StructRec->ExtChar;
  RemStructLen = STRINGSIZE - 3 - StructLen;

  if ((RemVarLen > 1) && (RemStructLen > 1))
  {
    for (StructElem = StructRec->Elems; StructElem; StructElem = StructElem->Next)
    {
      strmaxcpy(pVarPrefix + VarLen + 1, StructElem->pElemName, RemVarLen);
      StrCompMkTemp(&TmpComp, pVarPrefix, 0);
      StructElem->ExpandFnc(&TmpComp, StructElem, Base);
      if (StructElem->IsStruct)
      {
        TStructRec *pSubStruct;
        Boolean Found;

        strmaxcpy(pStructPrefix + StructLen + 1, StructElem->pElemName, RemStructLen);
        Found = FoundStruct(&pSubStruct, pStructPrefix);
        if (Found)
          ExpandStruct_One(pSubStruct, pVarPrefix, pStructPrefix, Base + StructElem->Offset);
      }
    }
  }
  pVarPrefix[VarLen] = '\0';
  pStructPrefix[StructLen] = '\0';
}

/*!------------------------------------------------------------------------
 * \fn     ExpandStruct(PStructRec StructRec)
 * \brief  expand a defined structure
 * \param  StructRec structure to expand
 * ------------------------------------------------------------------------ */

#define DIMENSION_MAX 3

typedef struct
{
  LargeInt Dimension;
  LargeInt Index;
  int CompVarNameLen;
} dimension_t;

void ExpandStruct(PStructRec StructRec, const char *p_struct_name)
{
  String CompVarName, CompStructName;
  int z;
  unsigned DimensionCnt = 0, Dim;
  dimension_t *dimensions = NULL;
  tStrComp Arg;
  tEvalResult EvalResult;

  if (!LabPart.str.p_str[0])
  {
    WrError(ErrNum_StructNameMissing);
    goto func_exit;
  }

  /* currently, we only support array dimensions as arguments */

  for (z = 1; z <= ArgCnt; z++)
    if (IsIndirectGen(ArgStr[z].str.p_str, "[]"))
    {
      if (DimensionCnt >= DIMENSION_MAX)
      {
        WrStrErrorPos(ErrNum_TooManyArrayDimensions, &ArgStr[z]);
        goto func_exit;
      }
      StrCompRefRight(&Arg, &ArgStr[z], 1);
      StrCompShorten(&Arg, 1);
      dyn_array_rsv_end(dimensions, dimension_t, DimensionCnt);
      dimensions[DimensionCnt].CompVarNameLen = 0;
      dimensions[DimensionCnt].Index = 0;
      dimensions[DimensionCnt].Dimension = EvalStrIntExpressionWithResult(&Arg, UInt32, &EvalResult);
      if (!EvalResult.OK)
        goto func_exit;
      if (EvalResult.Flags & eSymbolFlag_FirstPassUnknown)
      {
        WrStrErrorPos(ErrNum_FirstPassCalc, &Arg);
        goto func_exit;
      }
      if (dimensions[DimensionCnt].Dimension <= 0)
      {
        WrStrErrorPos(ErrNum_UnderRange, &Arg);
        goto func_exit;
      }
      DimensionCnt++;
    }
    else
    {
      WrStrErrorPos(ErrNum_InvStructArgument, &ArgStr[z]);
      goto func_exit;
    }

  strmaxcpy(CompStructName, p_struct_name, sizeof(CompStructName));
  strmaxcpy(CompVarName, LabPart.str.p_str, sizeof(CompVarName));
  if (!DimensionCnt)
  {
    ExpandStruct_One(StructRec, CompVarName, CompStructName, EProgCounter());
    CodeLen = StructRec->TotLen;
  }
  else
  {
    tStrComp LabelComp;

    /* Start with element [0,...,0] and build associated names.
       Store length of names up to given dimension so we don't
       have to rebuild the name with all indices upon every elemnt: */

    for (Dim = 0; Dim < DimensionCnt; Dim++)
    {
      dimensions[Dim].Index = 0;
      dimensions[Dim].CompVarNameLen = strlen(CompVarName);
      as_snprcatf(CompVarName, sizeof(CompVarName), "_%lllu", (LargeWord)dimensions[Dim].Index);
    }
    while (dimensions[0].Index < dimensions[0].Dimension)
    {
      StrCompMkTemp(&LabelComp, CompVarName, sizeof(CompVarName));
      LabelHandle(&LabelComp, EProgCounter() + CodeLen, True);
      ExpandStruct_One(StructRec, CompVarName, CompStructName, EProgCounter() + CodeLen);
      CodeLen += StructRec->TotLen;

      /* increase indices, ripple through 'carry' from minor to major indices */

      dimensions[DimensionCnt - 1].Index++;
      for (Dim = DimensionCnt - 1; Dim > 0; Dim--)
        if (dimensions[Dim].Index >= dimensions[Dim].Dimension)
        {
          dimensions[Dim].Index = 0;
          dimensions[Dim - 1].Index++;
        }
        else
          break;

      /* Dim now holds the most major (leftmost) index that changed.  Build up new element
         name, starting from that: */

      CompVarName[dimensions[Dim].CompVarNameLen] = '\0';
      for (; Dim < DimensionCnt; Dim++)
      {
        as_snprcatf(CompVarName, sizeof(CompVarName), "_%lllu", (LargeWord)dimensions[Dim].Index);
        if (Dim + 1 < DimensionCnt)
          dimensions[Dim + 1].CompVarNameLen = strlen(CompVarName);
      }
    }
  }
  BookKeeping();
  DontPrint = True;
func_exit:
  if (dimensions)
    free(dimensions);
}

void asmstruct_init(void)
{
   StructRoot = NULL;
}
