{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DeriveDataTypeable #-}

module Text.Reprinter
  ( reprint
  , splitBySpan
  , Position(..)
  , Source
  , Reprinting
  , initPosition
  , catchAll
  , genReprinting
  , Refactorable(..)
  , RefactorType(..)
  ) where

import Data.Generics.Zipper
import Debug.Trace
import qualified Data.ByteString.Char8 as B
import Data.Data
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Lazy

type Source    = B.ByteString

data Position = Position { posColumn :: Int, posLine :: Int }
  deriving (Data, Show)

initPosition = Position { posColumn = 1, posLine = 1 }

type Reprinting m =
 forall b . Typeable b
         => b -> m (Maybe (RefactorType, Source, (Position, Position)))

-- | The reprint algorithm takes a refactoring (parameteric in
-- | some monad m) and turns an arbitrary pretty-printable type 'p'
-- | into a monadic Source transformer.
reprint :: (Monad m, Data p)
        => Reprinting m -> p -> Source -> m Source
reprint reprinting tree input
  -- If the inupt is null then null is returned
  | B.null input = return B.empty

  -- Otherwise go with the normal algorithm
  | otherwise = do
   -- Initial state comprises start cursor and input source
   let state0 = (initPosition, input)
   -- Enter the top-node of a zipper for 'tree'
   (out, (_, remaining)) <- runStateT (enter reprinting (toZipper tree)) state0
   -- Add to the output source the reamining input source
   return $ out `B.append` remaining

-- The enter, enterDown, enterRight each take a refactoring and a
-- zipper producing a stateful Source transformer with Position
-- state.

enter, enterDown, enterRight
  :: Monad m
  => Reprinting m -> Zipper a -> StateT (Position, Source) m Source

-- `enter` applies the generic refactoring to the current context
-- of the zipper
enter reprinting z = do

  -- Step 1.
  -- Apply a refactoring
  refactoringInfo <- lift $ query reprinting z

  -- Step 2.
  output <-
    case refactoringInfo of
      -- No refactoring, so go into the children
      Nothing -> enterDown reprinting z

      -- A refactoring was applied
      Just (typ, output, (lb, ub)) -> do
        (cursor, inp) <- get
        case typ of
          Replace -> do
             -- Get the soure text up to the start of the refactored expr
             let (p0, inp') = splitBySpan (cursor, lb) inp
             -- Cut out the portion of source text consumed by the refactoring
             let (_, inp'') = splitBySpan (lb, ub) inp'
             put (ub, inp'')
             return $ B.concat [p0, output]
          After -> do
             -- Get the soure text up to the end of the refactored expr
             let (p0, inp') = splitBySpan (cursor, ub) inp
             put (ub, inp')
             return $ B.concat [p0, output]
          Before -> do
             -- Get the soure text up to the start of the refactored expr
             let (p0, inp')  = splitBySpan (cursor, lb) inp
             -- Cut out the portion of source text consumed by the refactoring
             let (p1, inp'') = splitBySpan (lb, ub) inp'
             put (ub, inp'')
             return $ B.concat [p0, output, p1]

  -- Part 3.
  -- Enter the right sibling of the current context
  output' <- enterRight reprinting z

  -- Concat the output for the current context, children, and right sibling
  return $ B.concat [output, output']

-- `enterDown` navigates to the children of the current context
enterDown reprinting z =
  case down' z of
    -- Go to children
    Just dz -> enter reprinting dz
    -- No children
    Nothing -> return B.empty

-- `enterRight` navigates to the right sibling of the current context
enterRight reprinting z =
  case right z of
    -- Go to right sibling
    Just rz -> enter reprinting rz
    -- No right sibling
    Nothing -> return B.empty

-- | Given a lower-bound and upper-bound pair of Positions, split the
-- | incoming Source based on the distanceF between the Position pairs
splitBySpan :: (Position, Position) -> Source -> (Source, Source)
splitBySpan (l, u) = subtext (ll, lc) (ll, lc) (ul, uc)
  where (Position lc ll) = l
        (Position uc ul) = u

{-
  Split a text.

  Returns a tuple containing:
    1. the bit of input text between upper and lower bounds
    2. the remaining input text

  Takes:
    1. current cursor position
    2. lower bound
    3. upper bound
    4. input text
-}
subtext :: (Int, Int) -> (Int, Int) -> (Int, Int) -> B.ByteString -> (B.ByteString, B.ByteString)
subtext cursor (lowerLn, lowerCol) (upperLn, upperCol) =
    subtext' B.empty cursor
  where
    subtext' acc (cursorLn, cursorCol) input

      | cursorLn <= lowerLn && (cursorCol >= lowerCol ==> cursorLn < lowerLn) =
        case B.uncons input of
          Nothing -> (B.reverse acc, input)
          Just ('\n', input') -> subtext' acc (cursorLn+1, 1) input'
          Just (_, input')    -> subtext' acc (cursorLn, cursorCol+1) input'

      | cursorLn <= upperLn && (cursorCol >= upperCol ==> cursorLn < upperLn) =
        case B.uncons input of
          Nothing -> (B.reverse acc, input)
          Just ('\n', input') -> subtext' (B.cons '\n' acc) (cursorLn+1, 1) input'
          Just (x, input')    -> subtext' (B.cons x acc) (cursorLn, cursorCol+1) input'

      | otherwise =
        (B.reverse acc, input)

-- Logical implication operator.
(==>) :: Bool -> Bool -> Bool; infix 2 ==>
a ==> b = a <= b

-- | Specify a refactoring type
data RefactorType = Before | After | Replace

-- | Infrastructure for building the reprinter "plugins"
class Refactorable t where
  isRefactored :: t -> Maybe RefactorType
  getSpan      :: t -> (Position, Position)

-- | Essentially wraps the refactorable interface
genReprinting :: (Monad m, Refactorable t, Typeable t)
    => (t -> m Source)
    -> t -> m (Maybe (RefactorType, Source, (Position, Position)))
genReprinting f z = do
  case isRefactored z of
    Nothing -> return Nothing
    Just refactorType -> do
      output <- f z
      return $ Just (refactorType, output, getSpan z)

-- | Catch all generic query
catchAll :: Monad m => a -> m (Maybe b)
catchAll _ = return Nothing
