content: module Language.Haskell.Format.Utilities
    ( defaultFormatter
    , hunitTest
    , showDiff
    , wasReformatted
    ) where
  
  import System.IO.Unsafe
  
  import Language.Haskell.Format
  import Language.Haskell.Source.Enumerator
  
  import Conduit
  import Control.Monad
  import Data.Algorithm.DiffContext
  import Data.List
  import Data.Maybe
  import Test.HUnit
  import Text.PrettyPrint
  
  type ErrorString = String
  
  data CheckResult
    = InvalidCheckResult HaskellSource ErrorString
    | CheckResult HaskellSource Reformatted
  
  checkResultPath :: CheckResult -> FilePath
  checkResultPath (InvalidCheckResult (HaskellSource filepath _) _) = filepath
  checkResultPath (CheckResult (HaskellSource filepath _) _)        = filepath
  
  hunitTest :: FilePath -> Test
  hunitTest filepath = TestLabel filepath . unsafePerformIO . testPath $ filepath
  
  testPath :: FilePath -> IO Test
  testPath filepath = do
    formatter <- defaultFormatter
    TestList <$>
      runConduit (check formatter filepath .| mapC makeTestCase .| sinkList)
  
  makeTestCase :: CheckResult -> Test
  makeTestCase result =
    TestLabel (checkResultPath result) . TestCase $ assertCheckResult result
  
  assertCheckResult :: CheckResult -> IO ()
  assertCheckResult result =
    case result of
      (InvalidCheckResult _ errorString) ->
        assertFailure ("Error: " ++ errorString)
      (CheckResult source reformatted) ->
        when (wasReformatted source reformatted) $
        assertFailure (showReformatted source reformatted)
    where
      showReformatted :: HaskellSource -> Reformatted -> String
      showReformatted source reformatted =
        intercalate "\n" $
        catMaybes
          [ showSourceChanges source reformatted
          , showSuggestions source reformatted
          ]
      showSourceChanges source reformatted =
        whenMaybe
          (sourceChanged source reformatted)
          (showDiff source (reformattedSource reformatted))
      showSuggestions _ reformatted =
        whenMaybe
          (hasSuggestions reformatted)
          (concatMap show (suggestions reformatted))
      whenMaybe :: Bool -> a -> Maybe a
      whenMaybe cond val = val <$ guard cond
  
  showDiff :: HaskellSource -> HaskellSource -> String
  showDiff (HaskellSource _ a) (HaskellSource _ b) = render (toDoc diff)
    where
      toDoc = prettyContextDiff (text "Original") (text "Reformatted") text
      diff = getContextDiff linesOfContext (lines a) (lines b)
      linesOfContext = 1
  
  check :: Formatter -> FilePath -> ConduitT () CheckResult IO ()
  check formatter filepath =
    enumeratePath filepath .| mapMC readSourceFile .|
    mapC (checkFormatting formatter)
  
  readSourceFile :: FilePath -> IO HaskellSource
  readSourceFile filepath = HaskellSource filepath <$> readFile filepath
  
  checkFormatting :: Formatter -> HaskellSource -> CheckResult
  checkFormatting (Formatter doFormat) source =
    case doFormat source of
      Left err          -> InvalidCheckResult source err
      Right reformatted -> CheckResult source reformatted
  
  defaultFormatter :: IO Formatter
  defaultFormatter = mconcat <$> (autoSettings >>= formatters)
  
  wasReformatted :: HaskellSource -> Reformatted -> Bool
  wasReformatted source reformatted =
    hasSuggestions reformatted || sourceChanged source reformatted
  
  sourceChanged :: HaskellSource -> Reformatted -> Bool
  sourceChanged source reformatted = source /= reformattedSource reformatted
  
  hasSuggestions :: Reformatted -> Bool
  hasSuggestions reformatted = not (null (suggestions reformatted))
  