content: -- Copyright 2017 Google Inc.
  --
  -- Licensed under the Apache License, Version 2.0 (the "License");
  -- you may not use this file except in compliance with the License.
  -- You may obtain a copy of the License at
  --
  --     http://www.apache.org/licenses/LICENSE-2.0
  --
  -- Unless required by applicable law or agreed to in writing, software
  -- distributed under the License is distributed on an "AS IS" BASIS,
  -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  -- See the License for the specific language governing permissions and
  -- limitations under the License.
  
  {-# LANGUAGE OverloadedStrings #-}
  module Main where
  
  import qualified Data.ByteString.Lazy as BL
  import qualified Data.Text.Lazy as TL
  import qualified Data.Text.Lazy.Encoding as TL
  
  import Test.Framework (defaultMain)
  import Test.Framework.Providers.HUnit (testCase)
  import Test.Framework.Providers.QuickCheck2 (testProperty)
  import Test.HUnit
  import Test.QuickCheck
  
  import Data.Char (chr)
  import Data.Maybe (fromJust)
  import Data.Text.Offset
  
  main :: IO ()
  main = defaultMain
      [ testProperty "bytecount matches final offset"
          propBytecountMatchesFinalOffset
      , testCase "column over end of line" testColOverEndOfLine
      , testCase "column over end of file" testColOverEndOfFile
      , testCase "line over end of file" testLineOverEndOfFile
      , testCase "multibytes" testMultiBytes
      ]
  
  -- | String with more multibytes (Arbitrary for String gives you at most
  -- 2-bytes).
  newtype MultiString = MultiString { unMultiString :: String }
      deriving (Eq, Show)
  
  instance Arbitrary MultiString where
      arbitrary = MultiString `fmap` listOf genChar
        where
          genChar :: Gen Char
          genChar = fmap chr . frequency $
            [ (8, choose(0, 0x7f))
            , (1, choose(0x80, 0x7ff))
            , (1, choose(0x800, 0xffff))
            , (1, choose(0x10000, 0x10fff))  -- subset of remaining range.
            ]
  
  propBytecountMatchesFinalOffset :: [MultiString] -> Property
  propBytecountMatchesFinalOffset mss =
      let ts = map (TL.pack . unMultiString) mss
          -- | Getting back the last valid source offset is not straightforward,
          -- since we don't consider newlines valid source positions. So rather
          -- just make user we don't have a terminal newline, and cover that case
          -- in other tests.
          t = TL.intercalate "\n" ts `TL.append` "guard"
          table = createOffsetTable t
          tlines = TL.splitOn "\n" t
          queryLine = length tlines - 1
          queryCol = (fromIntegral . TL.length . last $ tlines) - 1
          afterLastByteOffset =
              fromJust $ (+1) `fmap` lineColToByteOffset table queryLine queryCol
          encodedLength = fromIntegral . BL.length . TL.encodeUtf8 $ t
          tl = fromIntegral . TL.length $ t
      in classify (length tlines == 1) "single-line" $
         classify (length tlines > 1) "multi-line" $
         classify (encodedLength > tl) "multibyte" $
         classify (encodedLength == tl) "singlebyte" $
         afterLastByteOffset === encodedLength
  
  testColOverEndOfLine :: Assertion
  testColOverEndOfLine = do
      let t = createOffsetTable "012\n345"
      assertEqual "valid offset" (Just 2) (lineColToByteOffset t 0 2)
      assertEqual "offset at end" (Left (OverLineEnd 3 JustAtLineEnd))
                                  (lineColToByteOffsetDetail t 0 3)
      assertEqual "offset after end" (Left (OverLineEnd 4 AfterLineEnd))
                                     (lineColToByteOffsetDetail t 0 4)
  
  testColOverEndOfFile :: Assertion
  testColOverEndOfFile = do
      let t = createOffsetTable "012\n345"
      assertEqual "valid offset before EOF" (Just 6)
                                            (lineColToByteOffset t 1 2)
      assertEqual "invalid offset over EOF" (Left (OverLineEnd 7 JustAtLineEnd))
                                            (lineColToByteOffsetDetail t 1 3)
  
  testLineOverEndOfFile :: Assertion
  testLineOverEndOfFile = do
      assertEqual "single line, no newline" (Left NoSuchLine)
          (lineColToByteOffsetDetail (createOffsetTable "012") 1 0)
      assertEqual "single line, with newline" (Left EmptyLine)
          (lineColToByteOffsetDetail (createOffsetTable "012\n") 1 0)
      assertEqual "negative test: non-empty newline" (Just 4)
          (lineColToByteOffset (createOffsetTable "012\n0") 1 0)
  
  testMultiBytes :: Assertion
  testMultiBytes = do
      let t = createOffsetTable "0む4\nΣΣ8"
      assertEqual "before 3-byte" (Just 1) (lineColToByteOffset t 0 1)
      assertEqual "after 3-byte" (Just 4) (lineColToByteOffset t 0 2)
      assertEqual "after 2x2-byte last line" (Just 10)
                                             (lineColToByteOffset t 1 2)
  