module Analysers

open System.IO
open VSSonarPlugins.Types
open VSSonarPlugins
open System.Text
open System
open System.Reflection
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Diagnostics
open Microsoft.CodeAnalysis.CSharp
open Microsoft.CodeAnalysis.VisualBasic
open Microsoft.CodeAnalysis.MSBuild
open Microsoft.CodeAnalysis.Text
open System.Collections.Immutable
open System.Threading
open System.Collections.Generic
open System.Threading
open System.Diagnostics
open System.Security
open System.Security.Permissions
open StyleCop
open Microsoft.Build.Execution
open Microsoft.Build.Evaluation
open MSBuildHelper
open FSharp.Data

open Types

let AdhocWorkSpaces = new System.Collections.Generic.Dictionary<Guid, Solution>()

let getLanguage(itemInView : VsFileItem) =
    let extension = Path.GetExtension(itemInView.FilePath.ToLower())
    if  extension.EndsWith(".cs") then
        "C#"
    elif extension.EndsWith(".vb") then
        "VB.NET"
    else
        ""

let issuesFromDiagnostics(result : ImmutableArray<Diagnostic>, itemInView : VsFileItem, externlProfileIn : Profile, repoid : string) =
        
    let mutable issues = Map.empty
    let issuesret = new System.Collections.Generic.List<Issue>()

    for issue in result do
        if issue.Location.SourceTree.FilePath.ToLower().Equals(itemInView.FilePath.ToLower()) then
            let issueNew = new Issue()
            issueNew.Component <- itemInView.SonarResource.Key
            issueNew.Rule <- repoid + ":" + issue.Id
            issueNew.Line <- issue.Location.GetLineSpan().StartLinePosition.Line + 1
            issueNew.Message <- issue.GetMessage()
            issueNew.LocalPath <- issue.Location.SourceTree.FilePath
            let rule = externlProfileIn.GetRule(issueNew.Rule)
            if rule <> null then
                issueNew.Severity <- rule.Severity
                issueNew.Debt <- rule.DebtRemFnCoeff
                let key = issueNew.Message + issueNew.Line.ToString()
                if not(issues.ContainsKey(key)) then
                    issues <- issues.Add(key,issueNew)
                    issuesret.Add(issueNew)

    issuesret

let mutable partialSyntaxTrees : System.Collections.Generic.Dictionary<string, SyntaxTree list> = new System.Collections.Generic.Dictionary<string, SyntaxTree list>()
let mutable filepartialSyntaxTrees : System.Collections.Generic.Dictionary<string, SyntaxTree list> = new System.Collections.Generic.Dictionary<string, SyntaxTree list>()

// gets from a compilation all trees that belong to item in view
let GetSyntaxTreeWithReferences(compilation : Compilation, itemInView : VsFileItem) =  
    let mutable syntaxTree : SyntaxTree list = List.Empty
    partialSyntaxTrees <- new System.Collections.Generic.Dictionary<string, SyntaxTree list>()
    filepartialSyntaxTrees <- new System.Collections.Generic.Dictionary<string, SyntaxTree list>()

    for tree in compilation.SyntaxTrees do
        let root = tree.GetRootAsync().Result
        root.DescendantNodes() |> Seq.iter (fun c -> 
                                                if (c :? Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax) then
                                                    let classdata = c :?> Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax
                                                    System.Diagnostics.Debug.WriteLine(classdata.Identifier)
                                                                    
                                                    let containsPartial = classdata.Modifiers |> Seq.tryFind (fun c -> c.Text.Equals("partial")) 
                                                    match containsPartial with
                                                    | Some v -> 
                                                        if partialSyntaxTrees.ContainsKey(classdata.Identifier.ToString()) then
                                                            partialSyntaxTrees.[classdata.Identifier.ToString()] <- partialSyntaxTrees.[classdata.Identifier.ToString()] @ [tree]
                                                        else                                                                        
                                                            partialSyntaxTrees.Add(classdata.Identifier.ToString(), [tree])

                                                        if tree.FilePath.ToLower().Equals(itemInView.FilePath.ToLower()) then
                                                            if filepartialSyntaxTrees.ContainsKey(classdata.Identifier.ToString()) then
                                                                filepartialSyntaxTrees.[classdata.Identifier.ToString()] <- filepartialSyntaxTrees.[classdata.Identifier.ToString()] @ [tree]
                                                            else                                                                        
                                                                filepartialSyntaxTrees.Add(classdata.Identifier.ToString(), [tree])
                                                    | _ -> ()
                                                    )

        if tree.FilePath.ToLower().Equals(itemInView.FilePath.ToLower()) then
            syntaxTree <- [tree]
                                                                                                                         
    if filepartialSyntaxTrees.Count <> 0 then
        syntaxTree <- List.Empty
        for partree in filepartialSyntaxTrees do
            syntaxTree <- syntaxTree @ partialSyntaxTrees.[partree.Key]

    syntaxTree


let runRoslynOnCompilationUnit(compilation : Compilation, ids, builder : DiagnosticAnalyzer list, sonarAdditionalDocument : string [], token : CancellationTokenSource) =
        
    let mutable docs = List.Empty
    for doc in sonarAdditionalDocument do
        docs <- docs @ [new AnalyzerAdditionalFile(doc) :> AdditionalText]

    let optionsWithAdditionalFiles = new AnalyzerOptions((List.toArray docs).ToImmutableArray())

    let options = 
        (if compilation.Language.Equals(LanguageNames.CSharp) then
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            else
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        ).WithSpecificDiagnosticOptions(ids)
        
    let compilationWithOptions = compilation.WithOptions(options)    
    let analyserMain = compilationWithOptions.WithAnalyzers(builder.ToImmutableArray(), optionsWithAdditionalFiles, token.Token)
    analyserMain.GetAnalyzerDiagnosticsAsync().Result


let runAnalyserWithMSBuild(itemInView : VsFileItem,
                           vsHelper : IConfigurationHelper,
                           solution : ProjectTypes.Solution,
                           additionalFiles : string [],
                           ids : KeyValuePair<string, ReportDiagnostic> list,
                           buildertypes : DiagnosticAnalyzerType list,
                           externlProfileIn : Profile,
                           synctype : bool,
                           token : CancellationTokenSource) =
        
    let fileLang = getLanguage(itemInView)

<<<<<<< HEAD
    let document = 
=======
    if itemInView.FilePath.ToLower().EndsWith(".xaml.cs") then
        new List<Issue>()
    else
        let document = 
            let msbuildProj = (solution.Projects |> Seq.tryFind (fun c -> c.Value.Path.ToLower().Equals(itemInView.Project.ProjectFilePath.ToLower()))).Value.Value

            if AdhocWorkSpaces.ContainsKey(msbuildProj.Guid) then
                let solution = AdhocWorkSpaces.[msbuildProj.Guid]
                let dummyProject = solution.Projects |> Seq.find(fun proj -> proj.Name.Equals("DummyProject"))
                let mutable solution = dummyProject.Solution

                let documentInWorkspace = dummyProject.Documents |> Seq.tryFind(fun doc -> doc.Name.ToLower().Equals(itemInView.FilePath.ToLower()))
                match documentInWorkspace with
                | Some doc ->                     
                    solution <- solution.RemoveDocument(doc.Id)
                | _ -> ()
>>>>>>> 9a54bf1... cancel analysis

            let workspace = MSBuildWorkspace.Create()
            let solution = workspace.OpenSolutionAsync(itemInView.Project.Solution.SolutionPath).Result;

            let language = 
                if Path.GetExtension(itemInView.FileName).Equals(".cs") then
                    LanguageNames.CSharp
                else
                    LanguageNames.VisualBasic

            let project = (solution.Projects |> Seq.tryFind (fun c -> c.FilePath.Equals(itemInView.Project.ProjectFilePath))).Value

            let isTheSame(doc : Document) =
                Path.GetFullPath(doc.FilePath).Equals(Path.GetFullPath(itemInView.FilePath))
                                
<<<<<<< HEAD
            let documentFound = (project.Documents |> Seq.tryFind (fun doc -> isTheSame(doc)))
            documentFound.Value

    try
        // compile code
        let compilation = document.Project.GetCompilationAsync().Result

        // this runs diagnostics in all project - slow
        //let result = runRoslynOnCompilationUnit(compilation, ids.ToImmutableDictionary(), builder)

        // faster
        //let syntaxTree = GetSyntaxTreeWithReferences(compilation, itemInView)                  
        // even faster
        // let syntaxTree = [document.GetSyntaxTreeAsync().Result]

        // create a smaller compilation unit to run the diagnostic analysers
        //let compilationfinal = 
        //    if Path.GetExtension(itemInView.FileName).Equals(".cs") then 
        //        CSharpCompilation.Create("singlefile", syntaxTree, List.Empty, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) :> Compilation
        //    else
        //        VisualBasicCompilation.Create("singlefile", syntaxTree, List.Empty, new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) :> Compilation

        let extension = Path.GetExtension(itemInView.FilePath).ToLower()
        let builder, langids =
=======
                let documentFound = (solution.GetProject(project.Id).Documents |> Seq.tryFind (fun doc -> doc.Name.Equals(itemInView.FilePath)))

                match documentFound with
                | Some doc -> ()
                | _ -> 
                    let documentId = DocumentId.CreateNewId(project.Id, itemInView.FilePath) 
                    solution <- solution.AddDocument(documentId, itemInView.FilePath, SourceText.From(File.ReadAllText(itemInView.FilePath)))                    
                    
                AdhocWorkSpaces.Add(msbuildProj.Guid, solution)
                (solution.GetProject(project.Id).Documents |> Seq.find (fun doc -> doc.Name.Equals(itemInView.FilePath)))
                                                      
        try
            // compile code
            let compilation = document.Project.GetCompilationAsync().Result

            // this runs diagnostics in all project - slow
            //let result = runRoslynOnCompilationUnit(compilation, ids.ToImmutableDictionary(), builder)

            // faster
            //let syntaxTree = GetSyntaxTreeWithReferences(compilation, itemInView)                  
            // even faster
            // let syntaxTree = [document.GetSyntaxTreeAsync().Result]

            // create a smaller compilation unit to run the diagnostic analysers
            //let compilationfinal = 
            //    if Path.GetExtension(itemInView.FileName).Equals(".cs") then 
            //        CSharpCompilation.Create("singlefile", syntaxTree, List.Empty, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) :> Compilation
            //    else
            //        VisualBasicCompilation.Create("singlefile", syntaxTree, List.Empty, new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) :> Compilation

            let extension = Path.GetExtension(itemInView.FilePath).ToLower()
            let builder, langids =
>>>>>>> 9a54bf1... cancel analysis
                
            let mutable count = 0
            let lang = List.Empty
            let mutable bd = List.Empty
            let mutable idsbd = List.Empty

            for datam in buildertypes do
                let isdefinedforlang = datam.Languages |> Seq.tryFind( fun c -> c.Equals(fileLang))
                match isdefinedforlang with
                | Some lang -> 
                        bd <- bd @ [datam.Diagnostic]
                        idsbd <- idsbd @ [ids.[count]]
                | _ -> ()

                count <- count + 1

            bd, idsbd

<<<<<<< HEAD
        let result = runRoslynOnCompilationUnit(compilation, ids.ToImmutableDictionary(), builder, additionalFiles)
=======
            let result = runRoslynOnCompilationUnit(compilation, ids.ToImmutableDictionary(), builder, additionalFiles, token)
>>>>>>> 9a54bf1... cancel analysis
                                               
        let issues = issuesFromDiagnostics(result, itemInView, externlProfileIn, "csharpsquid")
        issues.AddRange(issuesFromDiagnostics(result, itemInView, externlProfileIn, "roslyn-cs"))
        issues.AddRange(issuesFromDiagnostics(result, itemInView, externlProfileIn, "roslyn-vbnet"))
        issues
    with
    | ex -> 
        new List<Issue>()