{-# LANGUAGE OverloadedStrings #-}
module Hpack.Render.DslSpec where

import           Test.Hspec
import           Test.QuickCheck
import           Data.List
import           Data.Maybe

import           Hpack.Render.Dsl

spec :: Spec
spec = do
  describe "render" $ do
    let render_ = render defaultRenderSettings 0
    context "when rendering a Stanza" $ do
      it "renders stanza" $ do
        let stanza = Stanza "foo" [
                Field "bar" "23"
              , Field "baz" "42"
              ]
        render_ stanza `shouldBe` [
            "foo"
          , "  bar: 23"
          , "  baz: 42"
          ]

      it "omits empty fields" $ do
        let stanza = Stanza "foo" [
                Field "bar" "23"
              , Field "baz" (WordList [])
              ]
        render_ stanza `shouldBe` [
            "foo"
          , "  bar: 23"
          ]

      it "allows to customize indentation" $ do
        let stanza = Stanza "foo" [
                Field "bar" "23"
              , Field "baz" "42"
              ]
        render defaultRenderSettings{renderSettingsIndentation = 4} 0 stanza `shouldBe` [
            "foo"
          , "    bar: 23"
          , "    baz: 42"
          ]

      it "renders nested stanzas" $ do
        let input = Stanza "foo" [Field "bar" "23", Stanza "baz" [Field "qux" "42"]]
        render_ input `shouldBe` [
            "foo"
          , "  bar: 23"
          , "  baz"
          , "    qux: 42"
          ]

    context "when rendering a Field" $ do
      context "when rendering a MultipleLines value" $ do
        it "takes nesting into account" $ do
          let field = Field "foo" (CommaSeparatedList ["bar", "baz"])
          render defaultRenderSettings 1 field `shouldBe` [
              "  foo:"
            , "      bar"
            , "    , baz"
            ]

        context "when value is empty" $ do
          it "returns an empty list" $ do
            let field = Field "foo" (CommaSeparatedList [])
            render_ field `shouldBe` []

      context "when rendering a SingleLine value" $ do
        it "returns a single line" $ do
          let field = Field "foo" (Literal "bar")
          render_ field `shouldBe` ["foo: bar"]

        it "takes nesting into account" $ do
          let field = Field "foo" (Literal "bar")
          render defaultRenderSettings 2 field `shouldBe` ["    foo: bar"]

        it "takes alignment into account" $ do
          let field = Field "foo" (Literal "bar")
          render defaultRenderSettings {renderSettingsFieldAlignment = 10} 0 field `shouldBe` ["foo:      bar"]

        context "when value is empty" $ do
          it "returns an empty list" $ do
            let field = Field "foo" (Literal "")
            render_ field `shouldBe` []

  describe "renderValue" $ do
    it "renders WordList" $ do
      renderValue defaultRenderSettings (WordList ["foo", "bar", "baz"]) `shouldBe` SingleLine "foo bar baz"

    it "renders CommaSeparatedList" $ do
      renderValue defaultRenderSettings (CommaSeparatedList ["foo", "bar", "baz"]) `shouldBe` MultipleLines [
          "  foo"
        , ", bar"
        , ", baz"
        ]

    it "renders LineSeparatedList" $ do
      renderValue defaultRenderSettings (LineSeparatedList ["foo", "bar", "baz"]) `shouldBe` MultipleLines [
          "  foo"
        , "  bar"
        , "  baz"
        ]

    context "when renderSettingsCommaStyle is TrailingCommas" $ do
      let settings = defaultRenderSettings{renderSettingsCommaStyle = TrailingCommas}

      it "renders CommaSeparatedList with trailing commas" $ do
        renderValue settings (CommaSeparatedList ["foo", "bar", "baz"]) `shouldBe` MultipleLines [
            "foo,"
          , "bar,"
          , "baz"
          ]

      it "renders LineSeparatedList without padding" $ do
        renderValue settings (LineSeparatedList ["foo", "bar", "baz"]) `shouldBe` MultipleLines [
            "foo"
          , "bar"
          , "baz"
          ]

  describe "sortFieldsBy" $ do
    let
      field name = Field name (Literal $ name ++ " value")
      arbitraryFieldNames = sublistOf ["foo", "bar", "baz", "qux", "foobar", "foobaz"] >>= shuffle

    it "sorts fields" $ do
      let fields = map field ["baz", "bar", "foo"]
      sortFieldsBy ["foo", "bar", "baz"] fields `shouldBe` map field ["foo", "bar", "baz"]

    it "keeps existing field order" $ do
      forAll (map field <$> arbitraryFieldNames) $ \fields -> do
        forAll arbitraryFieldNames $ \existingFieldOrder -> do
          let
            existingIndex :: Element -> Maybe Int
            existingIndex (Field name _) = name `elemIndex` existingFieldOrder
            existingIndex _ = Nothing

            indexes :: [Int]
            indexes = mapMaybe existingIndex (sortFieldsBy existingFieldOrder fields)

          sort indexes `shouldBe` indexes

    it "is stable" $ do
      forAll arbitraryFieldNames $ \fieldNames -> do
        forAll (elements $ subsequences fieldNames) $ \existingFieldOrder -> do
          let fields = map field fieldNames
          sortFieldsBy existingFieldOrder fields `shouldBe` fields

  describe "addSortKey" $ do
    it "adds sort key"  $ do
      addSortKey [(Nothing, "foo"), (Just 3, "bar"), (Nothing, "baz")] `shouldBe` [((-1, 0), "foo"), ((3, 1), "bar"), ((3, 2), "baz" :: String)]
