module GridMethod.Algorithm

open TaskModel

type GridMethodResult =
    { Solution: float array
      Error: float
      Step_h: float
      StepsCount: int }

let get_H a b n = (/) ((b - a) |> float) (n |> float)

let getGrid a b n =
    let h = get_H a b n
    Array.init (n + 1) (fun i -> a + float (i) * h)

let getError (solution: float array) (secondDetailedSolution: float array) =
    Array.init (solution |> Array.length) (fun i ->
        (/) ((-) secondDetailedSolution[i * 2] solution[i]) (2.0 ** 1.0 - 1.0))

let getl2ErrorNorm (solution: float array) (secondDetailedSolution: float array) =
    getError solution secondDetailedSolution
    |> Array.map (fun element -> element ** 2.0)
    |> Array.sum
    |> (*) ((/) 1.0 (solution |> Array.length |> float))
    |> sqrt

let clarifySolution (solution: float array) (secondDetailedSolution: float array) =
    let clarification = getError solution secondDetailedSolution
    Array.init (solution |> Array.length) (fun i -> secondDetailedSolution[i * 2] + clarification[i])

let run (task: Task) initial_N targetError =
    let initialGrid =
        getGrid task.InitialConditions.a task.InitialConditions.b initial_N

    let initial_h = get_H task.InitialConditions.a task.InitialConditions.b initial_N
    let firstSolution = ThomasAlgorithm.run task initialGrid initial_h

    let rec loop previousSolution current_N i =
        let new_N = current_N * 2
        let newGrid = getGrid task.InitialConditions.a task.InitialConditions.b new_N
        let new_h = get_H task.InitialConditions.a task.InitialConditions.b new_N
        let newSolution = ThomasAlgorithm.run task newGrid new_h

        let error = getl2ErrorNorm previousSolution newSolution

        if error.CompareTo(targetError) < 0 then
            { Solution = clarifySolution previousSolution newSolution
              Error = error
              Step_h = new_h
              StepsCount = i }
        else
            loop newSolution new_N (i + 1)

    loop firstSolution initial_N 1
