using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Entities;
using Semmle.Extraction.Entities;
using Semmle.Util.Logging;
using System.IO;

namespace Semmle.Extraction.CSharp.Populators
{
    public class TypeContainerVisitor : CSharpSyntaxVisitor
    {
        protected readonly Context cx;
        protected readonly IEntity parent;
        protected readonly TextWriter trapFile;

        public TypeContainerVisitor(Context cx, TextWriter trapFile, IEntity parent)
        {
            this.cx = cx;
            this.parent = parent;
            this.trapFile = trapFile;
        }

        public override void DefaultVisit(SyntaxNode node)
        {
            throw new InternalError(node, "Unhandled top-level syntax node");
        }

        public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node)
        {
            Entities.NamedType.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
        }

        public override void VisitClassDeclaration(ClassDeclarationSyntax classDecl)
        {
            Entities.Type.Create(cx, cx.GetModel(classDecl).GetDeclaredSymbol(classDecl)).ExtractRecursive(trapFile, parent);
        }

        public override void VisitStructDeclaration(StructDeclarationSyntax node)
        {
            Entities.Type.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
        }

        public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
        {
            Entities.Type.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
        }

        public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
        {
            Entities.Type.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
        }

        public override void VisitAttributeList(AttributeListSyntax node)
        {
            if (cx.Extractor.Standalone) return;

            var outputAssembly = Assembly.CreateOutputAssembly(cx);
            foreach (var attribute in node.Attributes)
            {
                var ae = new Attribute(cx, attribute, outputAssembly);
                cx.BindComments(ae, attribute.GetLocation());
            }
        }
    }

    class TypeOrNamespaceVisitor : TypeContainerVisitor
    {
        public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
            : base(cx, trapFile, parent) { }

        public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
        {
            // Only deal with "using namespace" not "using X = Y"
            if (usingDirective.Alias == null)
                new UsingDirective(cx, usingDirective, (NamespaceDeclaration)parent);
        }

        public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
        {
            NamespaceDeclaration.Create(cx, node, (NamespaceDeclaration)parent);
        }
    }

    class CompilationUnitVisitor : TypeOrNamespaceVisitor
    {
        public CompilationUnitVisitor(Context cx)
            : base(cx, cx.TrapWriter.Writer, null) { }

        public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
        {
            // This information is not yet extracted.
            cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Extraction.Entities.Location.Create(cx, node.GetLocation()), "", Severity.Info);
        }

        public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
        {
            foreach (var m in compilationUnit.ChildNodes())
            {
                cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
            }

            // Gather comments:
            foreach (SyntaxTrivia trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
            {
                CommentLine.Extract(cx, trivia);
            }

            foreach (var trivia in compilationUnit.GetLeadingTrivia())
            {
                CommentLine.Extract(cx, trivia);
            }

            foreach (var trivia in compilationUnit.GetTrailingTrivia())
            {
                CommentLine.Extract(cx, trivia);
            }
        }
    }

    public class CompilationUnit
    {
        public static void Extract(Context cx, SyntaxNode unit)
        {
            // Ensure that the file itself is populated in case the source file is totally empty
            Semmle.Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);

            ((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
        }
    }
}
