{-# LANGUAGE FlexibleContexts, NoImplicitPrelude, DeriveTraversable, NamedFieldPuns, DerivingVia, DeriveGeneric, OverloadedRecordDot #-}

module Conflict
    ( Conflict(..), Sides(..), SrcContent(..)
    , setEachBody, setStrings
    , pretty, prettyLines
    , parse
    ) where

import Control.Monad.State (MonadState, state, evalStateT)
import Control.Monad.Writer (runWriter, tell)
import Data.Maybe (fromMaybe)
import Generic.Data (Generically1(..))
import GHC.Generics (Generic1)

import Prelude.Compat

data Sides a = Sides
    { sideA :: a
    , sideBase :: a
    , sideB :: a
    } deriving (Functor, Foldable, Traversable, Show, Eq, Ord, Generic1)
    deriving Applicative via Generically1 Sides

data SrcContent = SrcContent
    { lineNo :: Int
    , content :: String
    } deriving Show

data Conflict = Conflict
    { markers   :: Sides SrcContent -- The markers at the beginning of sections
    , markerEnd :: SrcContent       -- The ">>>>>>>...." marker at the end of the conflict
    , bodies    :: Sides [String]
    } deriving Show

setBodies :: (Sides [String] -> Sides [String]) -> Conflict -> Conflict
setBodies f c = c{bodies = f c.bodies}

setEachBody :: ([String] -> [String]) -> Conflict -> Conflict
setEachBody = setBodies . fmap

setStrings :: (String -> String) -> Conflict -> Conflict
setStrings = setEachBody . map

prettyLines :: Conflict -> [String]
prettyLines c =
    concat ((:) <$> ((.content) <$> c.markers) <*> c.bodies) <> [c.markerEnd.content]

pretty :: Conflict -> String
pretty = unlines . prettyLines

breakUpToMarker ::
    MonadState [SrcContent] m =>
    Char -> Maybe Int -> m [SrcContent]
breakUpToMarker c mCount =
    state (break cond)
    where
        count = fromMaybe 7 mCount
        prefix = replicate count c
        cond l =
            pre == prefix && rightCount
            where
                (pre, post) = splitAt count l.content
                rightCount =
                    case (mCount, post) of
                    (Just{}, x:_) -> c /= x
                    _ -> True

readHead :: MonadState [a] m => m (Maybe a)
readHead = state f
    where
        f [] = (Nothing, [])
        f (l:ls) = (Just l, ls)

tryReadUpToMarker ::
    MonadState [SrcContent] m =>
    Char -> Maybe Int -> m ([SrcContent], Maybe SrcContent)
tryReadUpToMarker c mCount =
    (,) <$> breakUpToMarker c mCount <*> readHead

readUpToMarker ::
    MonadState [SrcContent] m =>
    Char -> Maybe Int -> m ([SrcContent], SrcContent)
readUpToMarker c mCount = do
    res <- tryReadUpToMarker c mCount
    case res of
        (ls, Just h)  -> pure (ls, h)
        (ls, Nothing) ->
            error $ concat
            [ "Parse error: failed reading up to marker: "
            , show c, ", got:"
            , concatMap (\l -> "\n" ++ show l.lineNo ++ "\t" ++ l.content) $ take 5 ls
            ]

parseConflict :: MonadState [SrcContent] m => SrcContent -> m Conflict
parseConflict markerA =
    do  (linesA   , markerBase) <- readUpToMarker '|' markerCount
        (linesBase, markerB)    <- readUpToMarker '=' markerCount
        (linesB   , markerEnd)  <- readUpToMarker '>' markerCount
        pure Conflict
            { markers    = Sides markerA markerBase markerB
            , markerEnd
            , bodies     = fmap (.content) <$> Sides linesA linesBase linesB
            }
    where
        markerCount = Just (length (takeWhile (== '<') markerA.content))

parseFromNumberedLines :: [SrcContent] -> [Either String Conflict]
parseFromNumberedLines =
    snd . runWriter . evalStateT loop
    where
        loop =
            do  (ls, mMarkerA) <- tryReadUpToMarker '<' Nothing
                tell $ map (Left . (.content)) ls
                case mMarkerA of
                    Nothing -> pure ()
                    Just markerA ->
                        do  tell . pure . Right =<< parseConflict markerA
                            loop

parse :: String -> [Either String Conflict]
parse = parseFromNumberedLines . zipWith SrcContent [1..] . lines
