using Godot;

using System;
using System.Collections.Generic;

namespace Rokojori
{  
  public class SceneFileHTMLExporter
  {
    public static readonly XMLElementNodeName GD_ExternalResource = XMLElementNodeName.Create( "gd-external-resource" );
    public static readonly XMLElementNodeName GD_SubResource = XMLElementNodeName.Create( "gd-sub-resource" );

    public static readonly XMLElementNodeName GD_Node = XMLElementNodeName.Create( "gd-node" );
    public static readonly XMLElementNodeName GD_Name = XMLElementNodeName.Create( "gd-name" );
    public static readonly XMLElementNodeName GD_Type = XMLElementNodeName.Create( "gd-type" );
    public static readonly XMLElementNodeName GD_Script = XMLElementNodeName.Create( "gd-script" );
    public static readonly XMLElementNodeName GD_Members = XMLElementNodeName.Create( "gd-members" ); 
    public static readonly XMLElementNodeName GD_Member = XMLElementNodeName.Create( "gd-member" ); 
    public static readonly XMLElementNodeName GD_Children = XMLElementNodeName.Create( "gd-children" );
    public static readonly XMLElementNodeName GD_Reference = XMLElementNodeName.Create( "gd-reference" );

    SceneFileParser _parser;
    XMLDocument _htmlDoc;
    XMLElementNode body;
    XMLElementNode head;

    public static void Convert( string inputPath, string outputPath )
    {
      var text = FilesSync.LoadUTF8( inputPath );

      var parser = new SceneFileParser();

      parser.Parse( text );

      Export( parser, outputPath );
    }

    public static void Export( SceneFileParser parser, string path )
    {
      new SceneFileHTMLExporter().ParseAndExport( parser, path );
    }

    public void ParseAndExport( SceneFileParser parser, string path )
    {
      _parser = parser;
      CreateHTMLDocument();

      AddExternalResources();
      AddNodes();         

      var exportPath = path.EndsWith( ".html" ) ? path : ( path + ".html" );

      FilesSync.SaveUTF8( exportPath, new XMLSerializer().SerializeHtml( _htmlDoc.documentElement ) );
    } 

    
    void CreateHTMLDocument()
    {
      var doc  = XMLDocument.HTML();
      var html = doc.documentElement;
      
      body = html.querySelector( HTMLElementName.body );
      head = html.querySelector( HTMLElementName.head );

      head.AddHTMLScript( SceneFileHTMLScriptJS.script );
      head.AddHTMLStyle( SceneFileHTMLStyle.style );
     

      _htmlDoc = doc;
    }

    void AddExternalResources()
    {
      var extResources = _parser.sceneFile.extResources;
      RJLog.Log( "External Resources:", extResources.Count );

      for ( int i = 0; i < extResources.Count; i++ )
      { 
        var resource = extResources[ i ];
        var attachmentElement = body;

        var resourceElement = _htmlDoc.Create( GD_ExternalResource );

        resourceElement.SetAttribute( "class", "resource" );

        attachmentElement.AppendChild( resourceElement );

        var nameElement = resourceElement.AddElement( GD_Name, resource.path.value );

        resourceElement.AddElement( GD_Type,  resource.id.value + " " + resource.uid.value + " " + resource.type.value );

        resourceElement.SetAttribute( "id", resource.id.value );
        attachmentElement.AddElement( HTMLElementName.br );
      }

      var subResources = _parser.sceneFile.subResources;
      RJLog.Log( "Sub Resources:", subResources.Count );

      for ( int i = 0; i < subResources.Count; i++ )
      { 
        var resource = subResources[ i ];
        var attachmentElement = body;

        var resourceElement = _htmlDoc.Create( GD_SubResource );

        resourceElement.SetAttribute( "class", "resource" );

        attachmentElement.AppendChild( resourceElement );


        var nameElement = resourceElement.AddElement( GD_Name, resource.id.value + " " + resource.type.value );

        var scriptOrShaderMember = resource.GetMember( "script" );

        if ( scriptOrShaderMember == null )
        {
          scriptOrShaderMember = resource.GetMember( "shader" );
        }

        if ( scriptOrShaderMember != null )
        {
          var scriptEntry = _parser.sceneFile.extResources.Find( e => e.id.value == scriptOrShaderMember.valueData.reference );
          var scriptType = scriptEntry == null ? "Null" : scriptEntry.path.value;
          resourceElement.AddElement( GD_Type, scriptType + "@" + scriptOrShaderMember.valueData.reference );
        }

        resourceElement.SetAttribute( "id", resource.id.value );
        attachmentElement.AddElement( HTMLElementName.br );
      }
    }

    void AddNodes()
    {
      var nodes = _parser.sceneFile.nodes;
      RJLog.Log( "Nodes:", nodes.Count );      

      var elementMap = new Dictionary<string,XMLElementNode>();     

      
      for ( int i = 0; i < nodes.Count; i++ )
      {          
        var node = nodes[ i ];

        //RJLog.Log( i, node.name.value );

        var attachmentElement = body;

        var elementPath = node.elementPath;

        var parent = node.parent.value;

        if ( parent != null && elementMap.ContainsKey( parent ) )
        { 
          attachmentElement = elementMap[ parent ].querySelector( GD_Children );         
        }
        
        var nodeElement = _htmlDoc.Create( GD_Node );

        nodeElement.SetAttribute( "class", "node" );

        var nameElement = nodeElement.AddElement( GD_Name, node.name.value );
        nameElement.SetAttribute( "class", "closeable" );
        nameElement.SetAttribute( "data-close-target", ".. " + GD_Children.selector );

        var scriptMember = node.GetMember( "script" );

        if ( scriptMember != null )
        {
          var scriptEntry = _parser.sceneFile.extResources.Find( e => e.id.value == scriptMember.valueData.reference );
          var scriptType = scriptEntry == null ? "Null" : scriptEntry.path.value;
          nodeElement.AddElement( GD_Type, scriptType + "@" + scriptMember.valueData.reference + "(" + node.type.value + ")" );
        }
        else
        {
          nodeElement.AddElement( GD_Type, node.type.value );
        }
        

        string scriptValue = null ;
        
        if ( scriptValue != null )
        {
          nodeElement.AddElement( GD_Script, scriptValue );
        }

        var membersElement = nodeElement.AddElement( GD_Members, "" );

        var memberData = node.data;

        memberData.ForEach( 
          m => 
          {
            if ( m.member != null )
            {
              var mem = m.member;

              if ( mem.valuesData != null )
              {

              } 
              else if ( mem.valueData != null ) 
              {

                if ( SceneFileValueType.Variant == mem.valueData.type )
                {
                  membersElement.AddElement( GD_Member, mem.name + ": " + mem.valueData );
                }
                else 
                {
                  var member = membersElement.AddElement( GD_Member, mem.name );
                  var reference = member.AddElement( GD_Reference, mem.valueData.reference );
                  reference.SetAttribute( "reference", mem.valueData.reference );
                }
                
              }
              
            }
            else
            {
              // membersElement.AddElement( GD_Member, "Type: " + m.type + " {" + m.line + "}");
            }
            
          }
        );

        nodeElement.AddElement( GD_Children );

        

        elementMap[ elementPath ] = nodeElement;

        // RJLog.Log( "'" + elementPath + "'", node.name.value );

        attachmentElement.AppendChild( nodeElement );

          
      }
    }



  } 
}