{-# LANGUAGE CPP              #-}
{-# OPTIONS -fno-warn-orphans #-}

module Text.Megaparsec.PosSpec (spec) where

import Data.Function (on)
import Data.List (isInfixOf)
import Data.Semigroup ((<>))
import Test.Hspec
import Test.Hspec.Megaparsec.AdHoc
import Test.QuickCheck
import Text.Megaparsec.Pos

#if !MIN_VERSION_base(4,8,0)
import Data.Word (Word)
#endif

spec :: Spec
spec = do

  describe "mkPos" $ do
    context "when the argument is 0" $
      it "throws InvalidPosException" $
        mkPos (0 :: Word) `shouldThrow` (== InvalidPosException)
    context "when the argument is not 0" $
      it "returns Pos with the given value" $
        property $ \n ->
          (n > 0) ==> (mkPos n >>= shouldBe n . unPos)

  describe "unsafePos" $
    context "when the argument is a positive integer" $
      it "returns Pos with the given value" $
        property $ \n ->
          (n > 0) ==> (unPos (unsafePos n) === n)

  describe "Read and Show instances of Pos" $
    it "printed representation of Pos is isomorphic to its value" $
      property $ \x ->
        read (show x) === (x :: Pos)

  describe "Ord instance of Pos" $
    it "works just like Ord instance of underlying Word" $
      property $ \x y ->
        compare x y === (compare `on` unPos) x y

  describe "Semigroup instance of Pos" $
    it "works like addition" $
      property $ \x y ->
        x <> y === unsafePos (unPos x + unPos y) .&&.
        unPos (x <> y) === unPos x + unPos y

  describe "initialPos" $
    it "consturcts initial position correctly" $
      property $ \path ->
        let x = initialPos path
        in sourceName   x === path        .&&.
           sourceLine   x === unsafePos 1 .&&.
           sourceColumn x === unsafePos 1

  describe "Read and Show instances of SourcePos" $
    it "printed representation of SourcePos in isomorphic to its value" $
      property $ \x ->
        read (show x) === (x :: SourcePos)

  describe "sourcePosPretty" $ do
    it "displays file name" $
      property $ \x ->
        sourceName x `isInfixOf` sourcePosPretty x
    it "displays line number" $
      property $ \x ->
        (show . unPos . sourceLine) x `isInfixOf` sourcePosPretty x
    it "displays column number" $
      property $ \x ->
        (show . unPos . sourceColumn) x `isInfixOf` sourcePosPretty x

  describe "defaultUpdatePos" $ do
    it "returns actual position unchanged" $
      property $ \w pos ch ->
        fst (defaultUpdatePos w pos ch) === pos
    it "does not change file name" $
      property $ \w pos ch ->
        (sourceName . snd) (defaultUpdatePos w pos ch) === sourceName pos
    context "when given newline character" $
      it "increments line number" $
        property $ \w pos ->
          (sourceLine . snd) (defaultUpdatePos w pos '\n')
            === (sourceLine pos <> pos1)
    context "when given tab character" $
      it "shits column number to next tab position" $
        property $ \w pos ->
          let c  = sourceColumn pos
              c' = (sourceColumn . snd) (defaultUpdatePos w pos '\t')
          in c' > c .&&. (((unPos c' - 1) `rem` unPos w) == 0)
    context "when given character other than newline or tab" $
      it "increments column number by one" $
        property $ \w pos ch ->
          (ch /= '\n' && ch /= '\t') ==>
          (sourceColumn . snd) (defaultUpdatePos w pos ch)
            === (sourceColumn pos <> pos1)
