{-# LANGUAGE FlexibleInstances           #-}
{-# LANGUAGE ScopedTypeVariables         #-}
{-# LANGUAGE TypeOperators               #-}
{-# LANGUAGE PatternSynonyms             #-}
{-# LANGUAGE RankNTypes                  #-}
{-# LANGUAGE DataKinds                   #-}
{-# LANGUAGE PolyKinds                   #-}
{-# LANGUAGE GADTs                       #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
module Data.HDiff.Diff.Modes where

import qualified Data.Set as S
import           Data.Functor.Const
import           Data.Functor.Sum

import           Generics.MRSOP.Base
import           Generics.MRSOP.Holes
import           Generics.MRSOP.HDiff.Digest

import qualified Data.WordTrie as T
import           Data.HDiff.Diff.Preprocess
import           Data.HDiff.Diff.Types
import           Data.HDiff.MetaVar

-- |A predicate indicating whether a tree can be shared.
type CanShare ki codes phi = forall a ix . PrepFix a ki codes phi ix -> Bool

extractHoles :: DiffMode
             -> CanShare ki codes phi
             -> IsSharedMap
             -> Delta (PrepFix a ki codes phi) at
             -> Delta (Holes ki codes (Sum phi (MetaVarIK ki))) at
extractHoles DM_NoNested h tr sd
  = extractNoNested h tr sd
extractHoles DM_ProperShare h tr (src :*: dst)
  = (extractProperShare h tr src :*: extractProperShare h tr dst)
extractHoles DM_Patience h tr (src :*: dst)
  = (extractPatience h tr src :*: extractPatience h tr dst)

-- ** Proper Shares

extractProperShare :: CanShare ki codes phi
                   -> IsSharedMap
                   -> PrepFix a ki codes phi at
                   -> Holes ki codes (Sum phi (MetaVarIK ki)) at
extractProperShare h tr a = properShare h tr (tagProperShare tr a)

tagProperShare :: forall a ki codes phi at
                . IsSharedMap
               -> PrepFix a ki codes phi at
               -> PrepFix (Int , Bool) ki codes phi at
tagProperShare ism = holesSynthesize pHole pOpq pPeel
  where
    myar :: PrepData a -> Int
    myar = maybe 0 getArity . flip T.lookup ism . toW64s . treeDigest

    -- A leaf is always a proper share. Since it has no subtrees,
    -- none if its subtrees can appear outside the leaf under scrutiny
    -- by construction.
    pHole :: Const (PrepData a) ix -> phi ix
          -> Const (PrepData (Int , Bool)) ix
    pHole (Const pd) _ = Const $ pd { treeParm = (myar pd , True) }

    pOpq :: Const (PrepData a) ('K k) -> ki k
         -> Const (PrepData (Int , Bool)) ('K k)
    pOpq (Const pd) _ = Const $ pd { treeParm = (myar pd , True) }

    pPeel :: Const (PrepData a) ('I i)
          -> Constr sum n
          -> NP (Const (PrepData (Int, Bool))) (Lkup n sum)
          -> Const (PrepData (Int, Bool)) ('I i)
    pPeel (Const pd) _ p
      = let maxar = maximum (0 : elimNP (fst . treeParm . getConst) p)
            myar' = myar pd
         in Const $ pd { treeParm = (max maxar myar' , myar' >= maxar) }

properShare :: forall ki codes phi at
             . CanShare ki codes phi
            -> IsSharedMap
            -> PrepFix (Int , Bool) ki codes phi at
            -> Holes ki codes (Sum phi (MetaVarIK ki)) at
properShare h tr pr
  = let prep  = getConst $ holesAnn pr
        isPS  = snd $ treeParm prep
     in if not (isPS && h pr)
        then properShare' pr
        else case T.lookup (toW64s $ treeDigest prep) tr of
               Nothing -> properShare' pr
               Just i  -> mkHole (getMetavar i) pr
  where
    -- TODO: Abstract the properShare', noNested' and patience'
    -- into a single function, remove 'CanShare' from these specific functions
    -- and make the life of whoever is making an extraction strategy
    -- simpler.
    properShare' :: PrepFix (Int , Bool) ki codes phi at
                 -> Holes ki codes (Sum phi (MetaVarIK ki)) at
    properShare' (Hole _ d)    = Hole' (InL d)
    properShare' (HOpq _ k)    = HOpq' k
    properShare' (HPeel _ c d) = HPeel' c (mapNP (properShare h tr) d)

    mkHole :: Int
           -> PrepFix (Int , Bool) ki codes phi at
           -> Holes ki codes (Sum phi (MetaVarIK ki)) at
    mkHole _ (Hole _ d)    = Hole' (InL d)
    mkHole v (HPeel _ _ _) = Hole' (InR (NA_I (Const v)))
    mkHole v (HOpq _ k)    = Hole' (InR (NA_K (Annotate v k)))

-- ** Patience

extractPatience :: CanShare ki codes phi
                -> IsSharedMap
                -> PrepFix a ki codes phi at
                -> Holes ki codes (Sum phi (MetaVarIK ki)) at
extractPatience h tr a = patience h tr a

patience :: forall ki codes phi at a
          . CanShare ki codes phi
         -> IsSharedMap
         -> PrepFix a ki codes phi at
         -> Holes ki codes (Sum phi (MetaVarIK ki)) at
patience h tr pr
  = if not (h pr)
    then patience' pr
    else case T.lookup (toW64s $ treeDigest $ getConst $ holesAnn pr) tr of
           Nothing -> patience' pr
           Just i | getArity i == 2 -> mkHole (getMetavar i) pr
                  | otherwise       -> patience' pr
  where
    patience' :: PrepFix a ki codes phi at
              -> Holes ki codes (Sum phi (MetaVarIK ki)) at
    patience' (Hole _ d)    = Hole' (InL d)
    patience' (HOpq _ k)    = HOpq' k
    patience' (HPeel _ c d) = HPeel' c (mapNP (patience h tr) d)

    mkMetaVar :: PrepFix a ki codes phi at
              -> Int
              -> MetaVarIK ki at
    mkMetaVar (Hole _ _)    _ = error "This should be impossible"
    mkMetaVar (HPeel _ _ _) v = NA_I (Const v)
    mkMetaVar (HOpq _ k)    v = NA_K (Annotate v k)

    mkHole :: Int
           -> PrepFix a ki codes phi at
           -> Holes ki codes (Sum phi (MetaVarIK ki)) at
    mkHole _ (Hole _ d) = Hole' (InL d)
    mkHole v x          = Hole' (InR $ mkMetaVar x v)



-- ** No Nested

extractNoNested :: CanShare ki codes phi
                -> IsSharedMap
                -> Delta (PrepFix a ki codes phi) at
                -> Delta (Holes ki codes (Sum phi (MetaVarIK ki))) at
extractNoNested h tr (src :*: dst)
  = let del'  = noNested h tr src
        ins'  = noNested h tr dst
        holes = holesGetHolesAnnWith'' getHole del'
                  `S.intersection`
                holesGetHolesAnnWith'' getHole ins'
        del     = holesRefineAnn (const (refineHole holes)) HOpq del'
        ins     = holesRefineAnn (const (refineHole holes)) HOpq ins'
     in (del :*: ins)
  where
    getHole :: Sum phi (Const Int :*: f) at -> Maybe Int
    getHole (InL _)               = Nothing
    getHole (InR (Const v :*: _)) = Just v

    mkMetaVar :: PrepFix a ki codes phi at
              -> Int
              -> MetaVarIK ki at
    mkMetaVar (Hole _ _)    _ = error "This should be impossible"
    mkMetaVar (HPeel _ _ _) v = NA_I (Const v)
    mkMetaVar (HOpq _ k)    v = NA_K (Annotate v k)

    refineHole :: S.Set (Maybe Int)
               -> Sum phi (Const Int :*: PrepFix a ki codes phi) ix
               -> Holes ki codes (Sum phi (MetaVarIK ki)) ix
    refineHole _ (InL phi) = Hole' (InL phi)
    refineHole s (InR (Const i :*: f))
      | Just i `S.member` s = Hole' (InR $ mkMetaVar f i)
      | otherwise           = holesMapAnn InL (const $ Const ()) f

noNested :: forall ki codes phi at a
          . CanShare ki codes phi
         -> IsSharedMap
         -> PrepFix a ki codes phi at
         -> Holes ki codes (Sum phi (Const Int :*: PrepFix a ki codes phi)) at
noNested h tr pr
  = if not (h pr)
    then noNested' pr
    else case T.lookup (toW64s $ treeDigest $ getConst $ holesAnn pr) tr of
           Nothing -> noNested' pr
           Just i  -> mkHole (getMetavar i) pr
  where
    noNested' :: PrepFix a ki codes phi at
              -> Holes ki codes (Sum phi (Const Int :*: PrepFix a ki codes phi)) at
    noNested' (Hole _ d)    = Hole' (InL d)
    noNested' (HOpq _ k)    = HOpq' k
    noNested' (HPeel _ c d) = HPeel' c (mapNP (noNested h tr) d)

    mkHole :: Int
           -> PrepFix a ki codes phi at
           -> Holes ki codes (Sum phi (Const Int :*: PrepFix a ki codes phi)) at
    mkHole _ (Hole _ d) = Hole' (InL d)
    mkHole v x          = Hole' (InR (Const v :*: x))