<Query Kind="FSharpProgram" />

// -------------------------

open System
open System.Collections.Concurrent
open System.Threading
open System.Threading.Tasks
open System.Diagnostics

// -------------------------

let tid () = Thread.CurrentThread.ManagedThreadId

let duration f = 
    let timer = new Stopwatch()
    timer.Start()
    let res = f()
    timer.Stop()
    printfn "... [%d] duration: %i ms" (tid()) timer.ElapsedMilliseconds
    res

// ------------------------

let naive (z1:string) (z2:string) (s1:int[]) (s2:int[]): int =
    let n1, n2 = z1.Length, z2.Length
    let a = Array2D.create n1 n2 0
        
    for i1 in 0..n1-1 do
        a.[i1,0] <- s1.[i1]
        
    for i2 in 0..n2-1 do
        a.[0,i2] <- s2.[i2]
        
    for i1 in 1..n1-1 do
        for i2 in 1..n2-1 do
            a.[i1,i2] <- if z1.[i1] = z2.[i2] then a.[i1-1,i2-1] + 1 else max a.[i1,i2-1] a.[i1-1,i2]
    

    for i1 in 0..n1-1 do
        s1.[i1] <- a.[i1,n2-1] 
        
    for i2 in 0..n2-1 do
        s2.[i2] <- a.[n1-1,i2] 
    
    //a.Dump ()
    a.[n1-1,n2-1]

// -------------------------

type Message =
     | FromWest of int
     | FromNorth of int

let result = TaskCompletionSource<int*int> ()

// not really needed, just to collect some threading stats
let threads = new ConcurrentDictionary<int, int> ()

// -------------------------

let agent (string1:string) (string2:string) (sentinel1:int[]) (sentinel2:int[]) (agents:MailboxProcessor<Message>[,]) (i1:int) (i2:int) = 
    MailboxProcessor<Message>.Start <| fun inbox ->
        let b1 = Array2D.length1 agents
        let b2 = Array2D.length1 agents
        
        async {
            let mutable n = 0
            let mutable w = 0
            
            let! m = inbox.Receive () 
            match m with
            | FromNorth t -> n <- t
            | FromWest t -> w <- t

            let! m' = inbox.Receive () 
            match m' with
            | FromNorth t -> n <- t
            | FromWest t -> w <- t
            
            threads.AddOrUpdate (tid (), 1, fun k v -> v + 1) |> ignore // for threading stats only
            // Thread.SpinWait 100000 // simulate intensive computation
            let log = sprintf "naive[%d,%d] %s %s %A %A" i1 i2 string1 string2 sentinel1 sentinel2
            let r = naive string1 string2 sentinel1 sentinel2
            printfn "%s -> r=%d %A %A" log r sentinel1 sentinel2
            
            if i1 = b1-1 && i2 = b2-1 then 
                result.SetResult (n+1, w+1)
            else
                if i1 < b1-1 then agents.[i1+1, i2].Post (FromNorth (n+1)) // Post to South
                if i2 < b2-1 then agents.[i1, i2+1].Post (FromWest (w+1))  // Post to East            
        }

// -------------------------

let run (strings1:string[]) (strings2:string[]) (sentinels1:int[][]) (sentinels2:int[][]) = 
    let b1, b2 = strings1.Length, strings2.Length
    let agents = Array2D.zeroCreate<MailboxProcessor<Message>> b1 b2 // typed nulls

    for i1 = 0 to b1-1 do
        for i2 = 0 to b2-1 do
            agents.[i1, i2] <- agent strings1.[i1] strings2.[i2] sentinels1.[i1] sentinels2.[i2] agents i1 i2
    
    // none of these agents can yet start
    for i2 = 1 to b2-1 do 
        agents.[0, i2].Post (FromNorth 0)
        
    for i1 = 1 to b1-1 do 
        agents.[i1, 0].Post (FromWest 0)
    
    // start 
    let n, w = 
        duration <| fun () -> 
            // actual start from the North-West corner
            agents.[0, 0].Post (FromNorth 0)
            agents.[0, 0].Post (FromWest 0)
            result.Task.Result
            
    printfn "... [%d] n:%d, w:%d" (tid()) n w

    // optional stats
    threads |> Seq.iter (fun kv -> printfn "... [%d] %d times" kv.Key kv.Value)
    let tcount = threads |> Seq.length
    let acount = threads |> Seq.sumBy (fun kv -> kv.Value)
    printfn "... [%d] total threads:%d, agents:%d" (tid()) tcount acount

// -------------------------

let main () =  // Excel example
    let b1, b2 = 2, 2
    
    let strings1 = [|  "_xazby"; "yxazby" |]
    let strings2 = [|  "_axcyb"; "baxcyb" |]
    
    let sentinels1 = [| [| 0; 0; 0; 0; 0; 0; |]; [| 0; 0; 0; 0; 0; 0; |] |]
    let sentinels2 = [| [| 0; 0; 0; 0; 0; 0; |]; [| 0; 0; 0; 0; 0; 0; |] |]

    run strings1 strings2 sentinels1 sentinels2
    ()
   
let main2 () =  // Excel example, variant
    let b1, b2 = 2, 2
    
    let strings1 = [|  "_xazbyx"; "xazby" |]
    let strings2 = [|  "_axcyba"; "bxcyb" |]
    
    let sentinels1 = [| [| 0; 0; 0; 0; 0; 0; 0; |]; [| 0; 0; 0; 0; 0; |] |]
    let sentinels2 = [| [| 0; 0; 0; 0; 0; 0; 0; |]; [| 0; 0; 0; 0; 0; |] |]

    run strings1 strings2 sentinels1 sentinels2
    ()
   
let main1 () =  // Excel example, variant
    let b1, b2 = 1, 1
    
    let strings1 = [|  "_xazby" |]
    let strings2 = [|  "_axcyb" |]
    
    let sentinels1 = [| [| 0; 0; 0; 0; 0; 0; |] |]
    let sentinels2 = [| [| 0; 0; 0; 0; 0; 0; |] |]

    run strings1 strings2 sentinels1 sentinels2
    ()
   
let main3 () =  // Excel example, variant
    let b1, b2 = 1, 1
    
    let strings1 = [|  "_x"; "xa"; "az"; "zb"; "by" |]
    let strings2 = [|  "_a"; "ax"; "xc"; "cy"; "yb" |]
    
    let sentinels1 = [| [| 0; 0; |]; [| 0; 0; |]; [| 0; 0; |]; [| 0; 0; |]; [| 0; 0; |]; |]
    let sentinels2 = [| [| 0; 0; |]; [| 0; 0; |]; [| 0; 0; |]; [| 0; 0; |]; [| 0; 0; |]; |]

    run strings1 strings2 sentinels1 sentinels2
    ()
   
main ()
//main2 ()
//main1 ()
//main3 ()
// -------------------------
