open System
open System.IO
open System.Diagnostics
open System.Text.RegularExpressions

// This script is used to generate the .fsx files that are used to load the .NET SDK.
// It will generate a .fsx file for each version of the .NET SDK that is installed.
// It will also generate a .fsx file for the latest major version of the .NET SDK to make referencing less brittle.

#r "nuget: semver"
open Semver

type Runtime = {
    Name: string
    Version: SemVersion
    Path: DirectoryInfo
}

let getRuntimeList () =
    // You can see which versions of the .NET runtime are currently installed with the following command.
    let psi =
        ProcessStartInfo("dotnet", "--list-runtimes", RedirectStandardOutput = true)

    let proc = Process.Start(psi)
    proc.WaitForExit()

    let output =
        seq {
            while not proc.StandardOutput.EndOfStream do
                proc.StandardOutput.ReadLine()
        }

    /// Regex for output like: Microsoft.AspNetCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
    let listRuntimesRegex = Regex("([^\s]+) ([^\s]+) \[(.*?)\\]")

    let runtimes =
        output
        |> Seq.map (fun x ->
            let matches = listRuntimesRegex.Match(x)
            let (version: string) = matches.Groups.[2].Value

            {
                Name = matches.Groups.[1].Value
                Version = SemVersion.Parse(version, SemVersionStyles.Any)
                Path = DirectoryInfo(Path.Join(matches.Groups[3].Value, version))
            }
        )

    runtimes
    |> Seq.toArray

module Seq =
    let filterOut predicate =
        Seq.filter (
            predicate
            >> not
        )

    let filterOutAny predicates xs =
        xs
        |> filterOut (fun x ->
            predicates
            |> Seq.exists (fun f -> f x)
        )


let createRuntimeLoadScript blockedDlls (r: Runtime) =
    let dir = r.Path

    let isDLL (f: FileInfo) = f.Extension = ".dll"

    let tripleQuoted (s: string) = $"\"\"\"{s}\"\"\""

    let packageSource (source: string) = $"#I {tripleQuoted source}"

    let reference (ref: string) = $"#r \"{ref}\""

    let fileReferences =
        dir.GetFiles()
        |> Seq.filter isDLL
        |> Seq.filterOutAny blockedDlls
        |> Seq.map (fun f -> reference f.Name)

    let referenceContents = [|
        packageSource dir.FullName
        yield! fileReferences
    |]

    referenceContents

let writeReferencesToFile outputPath outputFileName referenceContents =
    Directory.CreateDirectory(outputPath)
    |> ignore

    File.WriteAllLines(Path.Join(outputPath, outputFileName), referenceContents)

let runtimeOuputNameByVersion r = $"{r.Name}-{r.Version.ToString()}.fsx"

let runtimeOuputNameByMajorVersion r =
    $"{r.Name}-latest-{r.Version.Major}.fsx"

let contains (x: string) (y: FileInfo) = y.Name.Contains x

// List of DLLs that FSI can't load
let blockedDlls = [
    contains "aspnetcorev2_inprocess"
    contains "api-ms-win"
    contains "clrjit"
    contains "clrgc"
    contains "clretwrc"
    contains "coreclr"
    contains "hostpolicy"
    contains "Microsoft.DiaSymReader.Native.amd64"
    contains "mscordaccore_amd64_amd64_7"
    contains "mscordaccore"
    contains "msquic"
    contains "mscordbi"
    contains "mscorrc"
    contains "System.IO.Compression.Native"
]

let runTimeLoadScripts =
    getRuntimeList ()
    |> Array.map (fun runtime -> runtime, createRuntimeLoadScript blockedDlls runtime)

let outputFolder = "runtime-scripts"

// print all by version
runTimeLoadScripts
|> Seq.iter (fun (r, referenceContents) ->
    writeReferencesToFile outputFolder (runtimeOuputNameByVersion r) referenceContents
)

// print all by major version
runTimeLoadScripts
|> Array.groupBy (fun (r, _) -> r.Name, r.Version.Major)
|> Array.map (fun (_, values) ->
    values
    |> Array.maxBy (fun (r, _) -> r.Version)
)
|> Array.iter (fun (r, referenceContents) ->
    writeReferencesToFile outputFolder (runtimeOuputNameByMajorVersion r) referenceContents
)