\begin{comment}
\begin{code}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}
module LiveCoding.Migrate where

-- base
import Control.Arrow ((&&&))
import Control.Monad (guard)
import Data.Data
import Data.Function ((&))
import Data.Functor ((<&>))
import Data.Maybe
import Prelude hiding (GT)

-- transformers
import Control.Monad.Trans.Class
import Control.Monad.Trans.State

-- syb
import Data.Generics.Aliases
import Data.Generics.Twins

-- essence-of-live-coding
import LiveCoding.Migrate.Debugger
import LiveCoding.Migrate.Cell
import LiveCoding.Migrate.Migration
\end{code}
\end{comment}

\begin{code}
-- | The standard migration solution, recursing into the data structure and applying 'standardMigration'.
migrate :: (Data a, Data b) => a -> b -> a
migrate = migrateWith standardMigration

-- | Still recurse into the data structure, but apply your own given migration.
--   Often you will want to call @migrateWith (standardMigration <> yourMigration)@.
migrateWith :: (Data a, Data b) => Migration -> a -> b -> a
migrateWith specific = runSafeMigration $ treeMigration specific

-- | Covers standard cases such as matching types, to and from debuggers, to newtypes.
standardMigration :: Migration
standardMigration
  =  castMigration
  <> migrationDebugging
  <> migrationCell
  <> newtypeMigration

-- | The standard migration working horse.
--   Tries to apply the given migration,
--   and if this fails, tries to recurse into the data structure.
treeMigration :: Migration -> Migration
treeMigration specific
-- Maybe the specified user migration works?
  = specific
-- Maybe it's an algebraic datatype.
-- Let's try and match the structure as well as possible.
  <> sameConstructorMigration specific
  <> constructorMigration specific

matchingAlgebraicDataTypes :: (Data a, Data b) => a -> b -> Bool
matchingAlgebraicDataTypes a b
  = isAlgType typeA
  && isAlgType typeB
  && dataTypeName typeA == dataTypeName typeB
  where
    typeA = dataTypeOf a
    typeB = dataTypeOf b

-- | Assuming that both are algebraic data types, possibly the constructor names match.
--   In that case, we will try and recursively migrate as much data as possible onto the new constructor.
sameConstructorMigration :: Migration -> Migration
sameConstructorMigration specific = Migration $ \a b -> do
  guard $ matchingAlgebraicDataTypes a b
  let
    constrA = toConstr a
    constrB = toConstr b
  guard $ showConstr constrA == showConstr constrB
  let
    constrFieldsA = constrFields constrA
    constrFieldsB = constrFields constrB
    migrateSameConstr
      -- We have records, we can match on the field labels
      |  (not $ null constrFieldsA)
      && (not $ null constrFieldsB)
      = setChildren getFieldSetters a
      -- One of the two is not a record, just try to match 1-1 as far as possible
      | otherwise = setChildren (getChildrenSetters specific b) a
    settersB = zip constrFieldsB $ getChildrenSetters specific b
    getFieldSetters = constrFieldsA <&>
      \field -> fromMaybe (GT id)
        $ lookup field settersB
  return migrateSameConstr

-- | Still assuming that both are algebraic data types, but the constructor names don't match.
--   In that case, we will try and recursively fill all the fields new constructor.
--   If this doesn't work, fail.
constructorMigration :: Migration -> Migration
constructorMigration specific = Migration $ \a b -> do
  let
    constrB = toConstr b
    constrFieldsB = constrFields constrB
  guard $ matchingAlgebraicDataTypes a b
  matchingConstructor <- dataTypeOf a
    & dataTypeConstrs
    & map (show &&& id)
    & lookup (showConstr constrB)
  let matchingConstructorFields = constrFields matchingConstructor
  fieldSetters <- if null constrFieldsB || null matchingConstructorFields
    -- We don't have record. Try to cast each field.
    then
      return $ getChildrenMaybe b
    -- We have records. Sort by all field names and try to cast
    else
      getChildrenMaybe b
        & zip constrFieldsB
        & flip lookup
        & flip map matchingConstructorFields
        & sequence
  flip evalStateT fieldSetters $ fromConstrM tryOneField matchingConstructor

tryOneField :: Data a => StateT [GenericR' Maybe] Maybe a
tryOneField = do
  (field : fields) <- get
  put fields
  lift $ unGR field --lift field

getChildrenSetters :: Data a => Migration -> a -> [GenericT']
getChildrenSetters specific = gmapQ $ \child -> GT $ flip (runSafeMigration $ treeMigration specific) child

newtype GenericR' m = GR { unGR :: GenericR m }

getChildrenMaybe :: Data a => a -> [GenericR' Maybe]
getChildrenMaybe = gmapQ $ \child -> GR $ cast child

setChildren :: Data a => [GenericT'] -> a -> a
setChildren updates a = snd $ gmapAccumT f updates a
  where
    f [] e = ([], e)
    f (update : updates) e = (updates, unGT update $ e)
\end{code}