using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;

namespace Rokojori
{  
  public class XMLSerializer
  {    
    StringBuilder sb = new StringBuilder();
    XMLWalker walker = new XMLWalker();
    string indent = "  ";
    Dictionary<XMLNode, int> depthMap = new Dictionary<XMLNode, int>();
    Dictionary<int, string> indentMap = new Dictionary<int, string>();
    List<XMLElementNode> stack = new List<XMLElementNode>();

    
    public static string Escape( string rawText )
    {
      rawText = RegexUtility.Replace( rawText, "&", "&amp;" );
      rawText = RegexUtility.Replace( rawText, "<", "&lt;" );
      rawText = RegexUtility.Replace( rawText, ">", "&gt;" );
      rawText = RegexUtility.Replace( rawText, "\'", "&apos;" );
      rawText = RegexUtility.Replace( rawText, "\"", "&quot;" );

      return rawText;
    }

    public static string Unescape( string escapedText )
    {
      escapedText = RegexUtility.Replace( escapedText, "&amp;", "&"  );
      escapedText = RegexUtility.Replace( escapedText, "&lt;", "<"  );
      escapedText = RegexUtility.Replace( escapedText,  "&gt;,", ">" );
      escapedText = RegexUtility.Replace( escapedText, "&apos;", "\'" );
      escapedText = RegexUtility.Replace( escapedText, "&quot;","\"" );

      return escapedText;
    }
    
    public string SerializeXMLNode( XMLNode node )
    {
      walker.DepthIterate( node,
      
        ( n, d ) => 
        {
          var depth = GetDepth( n );
          ClosePreviousElements( depth );

          var element = n as XMLElementNode;

          // RJLog.Log( "Processing:", n );

          if ( element != null )
          {
            sb.Append( "\n" );
            sb.Append( GetIndent( depth ) );
            
            sb.Append( "<" + element.fullNodeName );

            for ( int i = 0; i < element.numAttributes; i++ )
            {
              var attribute = element.GetAttributeAt( i );

              sb.Append( " " );
              sb.Append( attribute.fullName );
              sb.Append( "=\"" );
              sb.Append( Escape( attribute.value ) );
              sb.Append( "\"");
            }
            
            sb.Append( ">" );           

            stack.Add( element );

          }
          else
          {            
            if ( n.parentElement != null && (
              HTMLElementName.style.Selects( n.parentElement ) ||
              HTMLElementName.script.Selects( n.parentElement ) )
            )
            {
              sb.Append( n.nodeValue );
            }
            else
            {
              var nodeValue = n.nodeValue;

              if ( nodeValue != null )
              {
                if ( XMLNode.NodeType.Text == n.nodeType )
                {
                  sb.Append( Escape( n.nodeValue ) );
                  
                }
                else
                {
                  sb.Append( n.nodeValue );
                }
                
              }
              
            }
          }


        },

        false, 
        depthMap 
      );

      ClosePreviousElements( -1 );

      return sb.ToString();
    }

    public string SerializeHtml( XMLNode node )
    {      
      sb.Append( "<!DOCTYPE html>" );
      return SerializeXMLNode( node );
    }

    string GetIndent( int depth )
    { 

      if ( indentMap.ContainsKey( depth ) )
      {
        return indentMap[ depth ];
      }

      if ( depth == 0 )
      {
        indentMap[ 0 ] = "";
        return "";
      }

      if ( indentMap.ContainsKey( depth - 1 ) )
      {
        var smallerIndent = indentMap[ depth -1 ];
        indentMap[ depth ] = smallerIndent + indent;
        return indentMap[ depth ];
      }

      var sb = new StringBuilder();

      for ( int i = 0; i < depth; i++ )
      {
        sb.Append( indent );
      }

      indentMap[ depth ] = sb.ToString(); 
      return indentMap[ depth ];     

    }

    int GetDepth( XMLNode n )
    {
      return walker.GetDepth( n, depthMap );
    }

    void ClosePreviousElements( int currentDepth )
    {
      for ( int i = stack.Count - 1; i >= 0; i-- )
      {
        var stackDepth = GetDepth( stack[ i ] );

        if ( stackDepth >= currentDepth )
        {
          var element = stack[ i ];
          stack.RemoveAt( i );    

          if ( NeedsLineBreak( element ) )
          {
            sb.Append( "\n" );
            sb.Append( GetIndent( stackDepth ) );
          }
          
          sb.Append( "</" + element.fullNodeName + ">" );
        }
        else
        {
          i = -1;
        }
      }
    }

    bool NeedsLineBreak( XMLElementNode elementNode )
    {
      if ( elementNode.numChildren == 0 || 

      elementNode.HasOnlyTextNodes() && 
      ! ( HTMLElementName.script.Selects( elementNode ) || 
          HTMLElementName.style.Selects( elementNode )
        )
      )
      {
        return false;
      }

      return true;    
    }
  }
}