{-# LANGUAGE CPP #-}
-- | Tree diffing working on @containers@ 'Tree'.
module Data.TreeDiff.Tree (treeDiff, EditTree (..), Edit (..)) where

import Data.Tree          (Tree (..))
import Data.TreeDiff.List

-- $setup
-- >>> import Data.Tree (Tree (..))
-- >>> import qualified Text.PrettyPrint as PP
-- >>> :{
-- ppTree :: (a -> PP.Doc) -> Tree a -> PP.Doc
-- ppTree pp = ppT
--   where
--     ppT (Node x []) = pp x
--     ppT (Node x xs) = PP.parens $ PP.hang (pp x) 2 $
--         PP.sep $ map ppT xs
-- ppEditTree :: (a -> PP.Doc) -> Edit (EditTree a) -> PP.Doc
-- ppEditTree pp = PP.sep . ppEdit
--   where
--     ppEdit (Cpy tree) = [ ppTree tree ]
--     ppEdit (Ins tree) = [ PP.char '+' PP.<> ppTree tree ]
--     ppEdit (Del tree) = [ PP.char '-' PP.<> ppTree tree ]
--     ppEdit (Swp a b) =
--         [ PP.char '-' PP.<> ppTree a
--         , PP.char '+' PP.<> ppTree b
--         ]
--     ppTree (EditNode x []) = pp x
--     ppTree (EditNode x xs) = PP.parens $ PP.hang (pp x) 2 $
--        PP.sep $ concatMap ppEdit xs
-- :}

-- | A breadth-traversal diff.
--
-- It's different from @gdiff@, as it doesn't produce a flat edit script,
-- but edit script iself is a tree. This makes visualising the diff much
-- simpler.
--
-- ==== Examples
--
-- Let's start from simple tree. We pretty print them as s-expressions.
--
-- >>> let x = Node 'a' [Node 'b' [], Node 'c' [return 'd', return 'e'], Node 'f' []]
-- >>> ppTree PP.char x
-- (a b (c d e) f)
--
-- If we modify an argument in a tree, we'll notice it's changed:
--
-- >>> let y = Node 'a' [Node 'b' [], Node 'c' [return 'x', return 'e'], Node 'f' []]
-- >>> ppTree PP.char y
-- (a b (c x e) f)
--
-- >>> ppEditTree PP.char (treeDiff x y)
-- (a b (c -d +x e) f)
--
-- If we modify a constructor, the whole sub-trees is replaced, though there
-- might be common subtrees.
--
-- >>> let z = Node 'a' [Node 'b' [], Node 'd' [], Node 'f' []]
-- >>> ppTree PP.char z
-- (a b d f)
--
-- >>> ppEditTree PP.char (treeDiff x z)
-- (a b -(c d e) +d f)
--
-- If we add arguments, they are spotted too:
--
-- >>> let w = Node 'a' [Node 'b' [], Node 'c' [return 'd', return 'x', return 'e'], Node 'f' []]
-- >>> ppTree PP.char w
-- (a b (c d x e) f)
--
-- >>> ppEditTree PP.char (treeDiff x w)
-- (a b (c d +x e) f)
--
treeDiff :: (Show a, Eq a) => Tree a -> Tree a -> Edit (EditTree a)
treeDiff ta@(Node a as) tb@(Node b bs)
    | a == b = Cpy $ EditNode a (map rec (diffBy (==) as bs))
    | otherwise = Swp (treeToEdit ta) (treeToEdit tb)
  where
    rec (Ins x)   = Ins (treeToEdit x)
    rec (Del y)   = Del (treeToEdit y)
    rec (Cpy z)   = Cpy (treeToEdit z)
    rec (Swp x y) = treeDiff x y

-- | Type used in the result of 'treeDiff'.
--
-- It's essentially a 'Tree', but the forest list is changed from
-- @[tree a]@ to @['Edit' (tree a)]@. This highlights that
-- 'treeDiff' performs a list diff on each tree level.
data EditTree a
    = EditNode a [Edit (EditTree a)]
  deriving Show

treeToEdit :: Tree a -> EditTree a
treeToEdit = go where go (Node x xs) = EditNode x (map (Cpy . go) xs)
