//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "rl.h"

#include <objbase.h>
#include <XmlLite.h>

#define CHECKHR(x) {hr = x; if (FAILED(hr)) goto CleanUp;}
#define SAFERELEASE(p) {if (p) {(p)->Release(); p = NULL;}}

namespace Xml
{

Node * Node::TopNode;

//-----------------------------------------------------------------------------
//
// Description:
//
//    Constructor for Attribute class.
//
//-----------------------------------------------------------------------------

Attribute::Attribute
(
   Char * name,
   Char * value
)
   : Name(name)
   , Value(value)
   , Next(NULL)
{}

Char *
Attribute::GetValue
(
   const Char * name
)
{
   for (Attribute * p = this; p != NULL; p = p->Next)
   {
      if (strcmp(p->Name, name) == 0)
      {
         return p->Value;
      }
   }

   return NULL;
}

void
Attribute::Dump()
{
   for (Attribute * attr = this;
        attr != NULL;
        attr = attr->Next)
   {
      printf("%s=\"%s\"", attr->Name, attr->Value);
      if (attr->Next != NULL)
      {
         printf(" ");
      }
   }
   printf("\n");
}

//-----------------------------------------------------------------------------
//
// Description:
//
//    Constructor for Node class.
//
//
//-----------------------------------------------------------------------------

Node::Node
(
   Char * name,
   Attribute * attributeList
)
   : Name(name)
   , AttributeList(attributeList)
   , Next(NULL)
   , ChildList(NULL)
   , Data(NULL)
//   , LineNumber(Xml::LineNumber)
{}

Node *
Node::GetChild
(
   const Char * name
)
{
   for (Node * p = this->ChildList; p != NULL; p = p->Next)
   {
      if (strcmp(p->Name, name) == 0)
      {
         return p;
      }
   }

   return NULL;
}

Char *
Node::GetAttributeValue
(
   const Char * name
)
{
   return this->AttributeList->GetValue(name);
}

void
Node::Dump
(
   int indent
)
{
   for (int i = 0; i < indent; i++)
   {
      printf("   ");
   }

   printf("Node %s ", this->Name);
   this->AttributeList->Dump();
   if (this->Data != NULL)
   {
      for (int i = 0; i <= indent; i++)
      {
         printf("   ");
      }
      printf("Data: %s\n", this->Data);
   }
   else
   {
      for (Node * child = this->ChildList;
           child != NULL;
           child = child->Next)
      {
         child->Dump(indent + 1);
      }
   }
}

void
Node::Dump()
{
   this->Dump(0);
}

Char *
ConvertWCHAR
(
   const WCHAR * pWchar
)
{
   int len = ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pWchar, -1,
      NULL, 0, NULL, NULL);

   Char * newStr = new char[len + 1];

   ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pWchar, -1,
      newStr, len + 1, NULL, NULL);

   return newStr;
}

HRESULT
ParseAttributes
(
   IXmlReader * pReader,
   Attribute ** ppAttrList
)
{
   HRESULT hr;
   const WCHAR * tmpString;
   Attribute * attrLast = nullptr;

   while (S_OK == (hr = pReader->MoveToNextAttribute()))
   {
      pReader->GetLocalName(&tmpString, nullptr);
      Char * attrName = ConvertWCHAR(tmpString);

      pReader->GetValue(&tmpString, nullptr);
      Char * attrValue = ConvertWCHAR(tmpString);

      Attribute * attrItem = new Attribute(attrName, attrValue);
      if (attrLast != nullptr)
      {
         attrLast->Next = attrItem;
      }
      else
      {
         *ppAttrList = attrItem;
      }
      attrLast = attrItem;
   }

   return hr;
}

HRESULT
ParseNode
(
   IXmlReader * pReader,
   Node ** ppNode
)
{
   HRESULT hr;

   XmlNodeType nodeType;
   Char * nodeName = nullptr;

   Attribute * attrList = nullptr;

   Node * childList = nullptr;
   Node * childLast = nullptr;

   const WCHAR * tmpString;

#define APPEND_CHILD(childNode) \
   if (childLast == nullptr) \
   { \
       childList = childLast = childNode; \
   } \
   else \
   { \
       childLast->Next = childNode; \
       childLast = childNode; \
   }

   // This call won't fail we make sure the reader is positioned at a valid
   // node before ParseNode() is called.
   pReader->GetNodeType(&nodeType);

   do
   {
      switch (nodeType)
      {
      case XmlNodeType_Element:
      {
         bool inOpenElement = nodeName != nullptr;
         if (inOpenElement)
         {
            Node * childNode;
            hr = ParseNode(pReader, &childNode);
            if (hr == S_OK)
            {
               APPEND_CHILD(childNode);
            }
            else
            {
               return hr;
            }
         }
         else
         {
            pReader->GetLocalName(&tmpString, nullptr);
            nodeName = ConvertWCHAR(tmpString);

            hr = ParseAttributes(pReader, &attrList);
            if (FAILED(hr))
            {
               *ppNode = nullptr;
               return hr;
            }

            *ppNode = new Node(nodeName, attrList);

            if (pReader->IsEmptyElement())
            {
               return S_OK;
            }
         }

         break;
      }

      case XmlNodeType_EndElement:
      {
         Node * node = *ppNode;

         // If we have a single child with data called "#text", then pull the data up to this node.
         if (childList != nullptr
             && childList == childLast
             && (childList->Data != nullptr)
             && (_stricmp(childList->Name, "#text") == 0))
         {
            node->Data = childList->Data;
            node->ChildList = nullptr;
         }
         else
         {
            node->ChildList = childList;
         }

         return S_OK;
      }

      case XmlNodeType_Attribute:
         // Need to manually move to attributes when at an element node.
         break;

      case XmlNodeType_CDATA:
      case XmlNodeType_Text:
      {
         pReader->GetValue(&tmpString, nullptr);
         Node * node = new Node("#text", nullptr);
         node->Data = ConvertWCHAR(tmpString);
         APPEND_CHILD(node);

         break;
      }

      case XmlNodeType_Comment:
      case XmlNodeType_DocumentType:
      case XmlNodeType_None:
      case XmlNodeType_ProcessingInstruction:
      case XmlNodeType_Whitespace:
      case XmlNodeType_XmlDeclaration:
         // Ignored.
         break;
      }
   }
   while (S_OK == (hr = pReader->Read(&nodeType)));

   return hr;

#undef APPEND_CHILD
}

HRESULT
ParseXml
(
   IXmlReader * pReader,
   Node ** ppNode
)
{
   HRESULT hr;
   XmlNodeType nodeType;

   // ParseNode() ignores the XML declaration node, so there can be only one
   // top level node.
   if (SUCCEEDED(hr = pReader->Read(&nodeType)))
   {
      return ParseNode(pReader, ppNode);
   }

   return hr;
}

HRESULT
CreateStreamOnHandle
(
   HANDLE handle,
   IStream ** ppStream
)
{
   // Note that this function reads the whole file into memory.
   //
   // There is no API on ARM similar to SHCreateStreamOnFileEx which creates
   // an IStream object that reads a file lazily. Rather than writing our own
   // IStream implementation that does this, we just read the whole file here
   // given that XML files don't get quite large and it should be okay to keep
   // everything in memory.

   DWORD fileSize, fileSizeHigh, bytesRead;
   HGLOBAL buffer;

   fileSize = GetFileSize(handle, &fileSizeHigh);
   if (fileSize == INVALID_FILE_SIZE || fileSizeHigh != 0)
   {
      return E_FAIL;
   }

   buffer = GlobalAlloc(GPTR, fileSize + 1);
   if (buffer == nullptr)
   {
      return E_FAIL;
   }

   if (!::ReadFile(handle, buffer, fileSize, &bytesRead, nullptr)
       || FAILED(CreateStreamOnHGlobal(buffer, /* fDeleteOnRelease */ true, ppStream)))
   {
      GlobalFree(buffer);
      return E_FAIL;
   }

   return S_OK;
}

Node *
ReadFile
(
   const char * fileName
)
{
   IStream * pStream;
   IXmlReader * pReader;

   HANDLE fileHandle = CreateFile(fileName, FILE_GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
   if (fileHandle == INVALID_HANDLE_VALUE)
   {
      Fatal("Cannot open XML file %s", fileName);
   }

   if (FAILED(CreateStreamOnHandle(fileHandle, &pStream)))
   {
      Fatal("Cannot create stream from file");
   }

   if (FAILED(CreateXmlReader(__uuidof(IXmlReader), (void**) &pReader, nullptr)))
   {
      Fatal("Cannot create XML reader");
   }

   if (FAILED(pReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit)))
   {
      Fatal("Cannot prohibit DTD processing");
   }

   if (FAILED(pReader->SetInput(pStream)))
   {
      Fatal("Cannot set XML reader input");
   }

   Node * topNode;
   if (FAILED(ParseXml(pReader, &topNode)))
   {
      unsigned int line, linePos;
      pReader->GetLineNumber(&line);
      pReader->GetLinePosition(&linePos);
      fprintf(
         stderr,
         "Error on line %d, position %d in \"%s\".\n",
         line,
         linePos,
         fileName);
      Fatal("Error parsing XML");
   }

   SAFERELEASE(pReader);
   SAFERELEASE(pStream);
   CloseHandle(fileHandle);

   return topNode;
}

}  // namespace Xml
