namespace TweetTest.Utils.Static.StringUtils

open System
open TweetLib.Utils.Static
open Xunit


module NullIfEmpty =

    [<Fact>]
    let ``null string returns null`` () =
        Assert.Null(StringUtils.NullIfEmpty(null))

    [<Fact>]
    let ``empty string returns null`` () =
        Assert.Null(StringUtils.NullIfEmpty(String.Empty))

    [<Fact>]
    let ``blank string returns a blank string`` () =
        Assert.Equal(" ", StringUtils.NullIfEmpty(" "))

    [<Fact>]
    let ``non-empty string returns a non-empty string`` () =
        Assert.Equal("abc", StringUtils.NullIfEmpty("abc"))


module SplitInTwo =

    [<Fact>]
    let ``empty string returns null`` () =
        Assert.Null(StringUtils.SplitInTwo("", '|'))

    [<Fact>]
    let ``non-empty string without search character returns null`` () =
        Assert.Null(StringUtils.SplitInTwo("abcdef", '|'))

    [<Fact>]
    let ``splitting into two non-empty parts works`` () =
        let result = StringUtils.SplitInTwo("abc|def", '|')
        Assert.NotNull(result)

        let struct (before, after) = result.Value
        Assert.Equal("abc", before)
        Assert.Equal("def", after)

    [<Fact>]
    let ``splitting into two empty parts works`` () =
        let result = StringUtils.SplitInTwo("|", '|')
        Assert.NotNull(result)

        let struct (before, after) = result.Value
        Assert.Equal("", before)
        Assert.Equal("", after)

    [<Fact>]
    let ``first found search character is used if multiple are present`` () =
        let result = StringUtils.SplitInTwo("abc|de|f", '|')
        Assert.NotNull(result)

        let struct (before, after) = result.Value
        Assert.Equal("abc", before)
        Assert.Equal("de|f", after)

    [<Fact>]
    let ``start index skips search characters preceding it`` () =
        let result = StringUtils.SplitInTwo("|bc|de|f", '|', 4)
        Assert.NotNull(result)

        let struct (before, after) = result.Value
        Assert.Equal("|bc|de", before)
        Assert.Equal("f", after)

    [<Fact>]
    let ``start index matching position of search character does not skip it`` () =
        let result = StringUtils.SplitInTwo("|bc|de|f", '|', 3)
        Assert.NotNull(result)

        let struct (before, after) = result.Value
        Assert.Equal("|bc", before)
        Assert.Equal("de|f", after)

    [<Fact>]
    let ``start index before start of string throws`` () =
        Assert.Throws<ArgumentOutOfRangeException>(fun () -> StringUtils.SplitInTwo("abc|def", '|', -1) |> ignore)

    [<Fact>]
    let ``start index after end of string throws`` () =
        Assert.Throws<ArgumentOutOfRangeException>(fun () -> StringUtils.SplitInTwo("abc|def", '|', 8) |> ignore)


module ExtractBefore =

    [<Fact>]
    let ``empty string returns empty string`` () =
        Assert.Equal("", StringUtils.ExtractBefore("", ' '))

    [<Fact>]
    let ``non-empty string without search character returns input string`` () =
        Assert.Equal("abc", StringUtils.ExtractBefore("abc", ' '))

    [<Fact>]
    let ``finding searched char returns everything before it`` () =
        Assert.Equal("abc", StringUtils.ExtractBefore("abc def ghi", ' '))

    [<Theory>]
    [<InlineData(0, "abc")>]
    [<InlineData(3, "abc")>]
    [<InlineData(4, "abc def")>]
    [<InlineData(7, "abc def")>]
    [<InlineData(8, "abc def ghi")>]
    let ``start index works`` (startIndex: int, expectedValue: string) =
        Assert.Equal(expectedValue, StringUtils.ExtractBefore("abc def ghi", ' ', startIndex))

    [<Fact>]
    let ``start index before start of string throws`` () =
        Assert.Throws<ArgumentOutOfRangeException>(fun () -> StringUtils.ExtractBefore("abc def", ' ', -1) |> ignore)

    [<Fact>]
    let ``start index after end of string throws`` () =
        Assert.Throws<ArgumentOutOfRangeException>(fun () -> StringUtils.ExtractBefore("abc def", ' ', 8) |> ignore)


module ParseInts =

    [<Fact>]
    let ``empty string returns empty collection`` () =
        Assert.Empty(StringUtils.ParseInts("", ','))

    [<Fact>]
    let ``single integer is parsed correctly`` () =
        Assert.Equal([ 1 ], StringUtils.ParseInts("1", ','))

    [<Fact>]
    let ``multiple integers are parsed correctly`` () =
        Assert.Equal([ -3 .. 3 ], StringUtils.ParseInts("-3,-2,-1,0,1,2,3", ','))

    [<Fact>]
    let ``neighbouring delimiters are discarded`` () =
        Assert.Equal([ 1 .. 4 ], StringUtils.ParseInts(",,1,,,2,3,,4,,,", ','))

    [<Fact>]
    let ``invalid format throws`` () =
        Assert.Throws<FormatException>(fun () -> StringUtils.ParseInts("1,2,a", ',') |> ignore)


module ConvertPascalCaseToScreamingSnakeCase =

    [<Fact>]
    let ``converts one word`` () =
        Assert.Equal("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help"))

    [<Fact>]
    let ``converts two words`` () =
        Assert.Equal("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"))

    [<Fact>]
    let ``converts three words`` () =
        Assert.Equal("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"))

    [<Fact>]
    let ``converts one uppercase abbreviation`` () =
        Assert.Equal("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"))

    [<Fact>]
    let ``converts many uppercase abbreviations`` () =
        Assert.Equal("I_LIKE_HTML_AND_CSS", StringUtils.ConvertPascalCaseToScreamingSnakeCase("ILikeHTMLAndCSS"))


module ConvertRot13 =

    [<Fact>]
    let ``ignores digits and special characters`` () =
        Assert.Equal("<123'456.789>", StringUtils.ConvertRot13("<123'456.789>"))

    [<Fact>]
    let ``converts lowercase letters correctly`` () =
        Assert.Equal("nopqrstuvwxyzabcdefghijklm", StringUtils.ConvertRot13("abcdefghijklmnopqrstuvwxyz"))

    [<Fact>]
    let ``converts uppercase letters correctly`` () =
        Assert.Equal("NOPQRSTUVWXYZABCDEFGHIJKLM", StringUtils.ConvertRot13("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))

    [<Fact>]
    let ``converts mixed character types correctly`` () =
        Assert.Equal("Uryyb, jbeyq! :)", StringUtils.ConvertRot13("Hello, world! :)"))