namespace Domain

open System.IO
open DMLib.String

/// Path of the dir the user wants to process
type DirToProcess = string

/// *.bat file generated by this program
type BatFileName = string

/// modinfo.ini
type ModInfoFileName = string

/// String surrounded by quotes. Used to send command line instructions to compress files.
type QuotedStr = private QuotedStr of string

type ErrorMsg = string
type FileName = string

/// Executable file name.
type ExeName = private ExeName of QuotedStr

type RarExeName = private RarExeName of QuotedStr

/// Full parameters needed to start processing files.
type FullParams =
  { InputDir: DirToProcess
    OutFile: BatFileName
    ZipExe: ExeName
    RarExe: RarExeName option }


module QuotedStr =
  let private transformIfNot transform condition x =
    if not (condition x) then
      transform x
    else
      x

  let private ensureFirstQuote s =
    transformIfNot (fun s -> "\"" + s) (fun (s: string) -> s.StartsWith('"')) s

  let private ensureTrailQuote s =
    transformIfNot (fun s -> s + "\"") (fun (s: string) -> s.EndsWith('"')) s

  let create (fileName: string) =
    QuotedStr(fileName |> ensureFirstQuote |> ensureTrailQuote)

  let value (QuotedStr fileName) = fileName

  let unquote (QuotedStr fileName) = fileName[.. fileName.Length - 2][1..]

  let modify fn (fileName: QuotedStr) = fileName |> unquote |> fn |> create


module ErrorMsg =
  let map errorExtractor x =
    match x with
    | Ok v -> Ok v
    | Error e -> e |> errorExtractor |> Error


module private ExeFileName =
  let checkFileExists (errorMsg: ErrorMsg) (fileName: FileName) =
    if not (File.Exists(fileName)) then
      Error errorMsg
    else
      Ok fileName

  let checkExe (errorMsg: ErrorMsg) (exeName: FileName) (fileName: FileName) =
    if not (Path.GetFileName(fileName) |> toLower = toLower exeName) then
      Error errorMsg
    else
      Ok fileName

  let createQuotedStr exeName notExist wrongExe (fileName: FileName) =
    result {
      let! existing = fileName |> checkFileExists notExist
      let! x = existing |> (checkExe wrongExe exeName)
      return x |> QuotedStr.create
    }

  let fileNonExistingE appName jsonPath fileName =
    ErrorMsg
      $"{appName} executable {encloseQuotes fileName} does not exist. If you have installed it somewhere else, make sure to modify {encloseQuotes jsonPath}."

  let wrongExeE appName appVersion exeName =
    ErrorMsg
      $"Your {appName} executable must be named {encloseQuotes exeName} (last tested with {appName} v{appVersion}, which is guaranteed to have a file named like that)."

module ExeName =
  open ExeFileName

  let create jsonPath fileName =
    let exeName = "7z.exe"
    let appName = "7zip"
    let notExist = fileNonExistingE appName jsonPath fileName
    let wrongExe = wrongExeE appName "22.01" exeName

    createQuotedStr exeName notExist wrongExe fileName
    |> Result.map ExeName

  let value (ExeName fileName) = fileName |> QuotedStr.value

  let createForDebugging fileName = fileName |> QuotedStr.create |> ExeName


module RarExeName =
  open ExeFileName

  let create jsonPath fileName =
    let exeName = "Rar.exe"
    let appName = "WinRar"
    let notExist = fileNonExistingE appName jsonPath fileName
    let wrongExe = wrongExeE appName "5.91" exeName

    createQuotedStr exeName notExist wrongExe fileName
    |> Result.map RarExeName

  let value (RarExeName fileName) = fileName |> QuotedStr.value
