%  Copyright (C) 2002-2003 David Roundy
%
%  This program is free software; you can redistribute it and/or modify
%  it under the terms of the GNU General Public License as published by
%  the Free Software Foundation; either version 2, or (at your option)
%  any later version.
%
%  This program is distributed in the hope that it will be useful,
%  but WITHOUT ANY WARRANTY; without even the implied warranty of
%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%  GNU General Public License for more details.
%
%  You should have received a copy of the GNU General Public License
%  along with this program; see the file COPYING.  If not, write to
%  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
%  Boston, MA 02110-1301, USA.


\begin{code}
{-# OPTIONS -fno-warn-orphans #-}
module Darcs.Patch.Read ( readPrim, readPatch )
             where

import Prelude hiding ( pi )
import Control.Monad ( liftM )

#include "gadts.h"
import FastPackedString ( PackedString )
import FileName ( FileName, fn2fp, fp2fn, ps2fn, decode_white )
import Darcs.Patch.Core ( Patch(..), Named(..) )
import Darcs.Patch.Prim ( Prim(..), FileNameFormat(..),
                          DirPatchType(..), FilePatchType(..),
                          hunk, binary )
#ifndef GADT_WITNESSES
import Darcs.Patch.Commute ( merger )
import Darcs.Patch.Patchy ( invert )
#endif
import Darcs.Patch.Info ( PatchInfo, readPatchInfo )
import Darcs.Patch.ReadMonads (Stringalike(..), ParserM, work, maybe_work, alter_input,
                               parse_strictly, peek_input, lex_string, lex_eof, my_lex)
#include "impossible.h"
import Darcs.Patch.Patchy ( ReadPatch, readPatch', bracketedFL )
import Darcs.Patch.Ordered ( FL(..) )
import Darcs.Sealed ( Sealed(..), seal, mapSeal )
\end{code}

\begin{code}
readPatch :: ReadPatch p => PackedString -> Maybe (Sealed (p C(x )), PackedString)
readPatch ps = case parse_strictly (readPatch' False) ps of
                   Just (Just p, ps') -> Just (p, ps')
                   _ -> Nothing

instance ReadPatch p => ReadPatch (Named p) where
 readPatch' want_eof
   = do s <- peek_input
        case liftM (sal_to_string . fst) $ my_lex s of
          Just ('[':_) ->      liftM Just $ readNamed want_eof -- ]
          _ -> return Nothing

instance ReadPatch Prim where
 readPatch' w = readPrim OldFormat w

readPrim :: (ParserM m, Monad (m PackedString))
            => FileNameFormat -> Bool -> m PackedString (Maybe (Sealed (Prim C(x ))))
readPrim x _
   = do s <- peek_input
        case liftM (sal_to_string . fst) $ my_lex s of
          Just "{}" ->         do work my_lex
                                  return $ Just $ seal Identity
          Just "(" ->          liftM Just $ readSplit x -- )
          Just "hunk" ->       liftM (Just . seal) $ readHunk x
          Just "replace" ->    liftM (Just . seal) $ readTok x
          Just "binary" ->     liftM (Just . seal) $ readBinary x
          Just "addfile" ->    liftM (Just . seal) $ readAddFile x
          Just "adddir" ->     liftM (Just . seal) $ readAddDir x
          Just "rmfile" ->     liftM (Just . seal) $ readRmFile x
          Just "rmdir" ->      liftM (Just . seal) $ readRmDir x
          Just "move" ->       liftM (Just . seal) $ readMove x
          Just "changepref" -> liftM (Just . seal) $ readChangePref
          _ -> return Nothing

instance ReadPatch Patch where
 readPatch' want_eof
   = do mps <- bracketedFL (fromIntegral $ fromEnum '{') (fromIntegral $ fromEnum '}')
        case mps of
          Just (Sealed ps) -> return $ Just $ Sealed $ ComP ps
          Nothing -> do s <- peek_input
                        case liftM (sal_to_string . fst) $ my_lex s of
#ifndef GADT_WITNESSES
                          Just "merger" ->     liftM (Just . seal) $ readMerger True
                          Just "regrem" ->     liftM (Just . seal) $ readMerger False
#endif
                          _ -> liftM (fmap (mapSeal PP)) $ readPatch' want_eof
\end{code}

\begin{code}
read_patches :: (ParserM m, Monad (m PackedString))
             => FileNameFormat -> String -> Bool -> m PackedString (Sealed (FL Prim C(x )))
read_patches x str want_eof
 = do mp <- readPrim x False
      case mp of
          Nothing -> do unit <- lex_string str
                        case unit of
                            () -> if want_eof then do unit' <- lex_eof
                                                      case unit' of
                                                          () -> return $ seal NilFL
                                              else return $ seal NilFL
          Just (Sealed p) -> do Sealed ps <- read_patches x str want_eof
                                return $ seal (p:>:ps)
\end{code}

\begin{code}
readSplit :: (ParserM m, Monad (m PackedString)) =>
             FileNameFormat -> m PackedString (Sealed (Prim C(x )))
readSplit x = do
  work my_lex
  ps <- read_patches x ")" False
  return $ Split `mapSeal` ps
\end{code}

\begin{code}
readFileName :: Stringalike s => FileNameFormat -> s -> FileName
readFileName OldFormat = ps2fn . sal_to_PS
readFileName NewFormat = fp2fn . decode_white . sal_to_string

readHunk :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readHunk x = do
  work my_lex
  fi <- work my_lex
  l <- work sal_readInt
  have_nl <- skip_newline
  if have_nl
     then do work $ lines_starting_with ' ' -- skipping context
             old <- work $ lines_starting_with '-'
             new <- work $ lines_starting_with '+'
             work $ lines_starting_with ' ' -- skipping context
             return $ hunk (fn2fp $ readFileName x fi) l old new
     else return $ hunk (fn2fp $ readFileName x fi) l [] []

skip_newline :: (ParserM m, Monad (m PackedString)) => m PackedString Bool
skip_newline = do s <- peek_input
                  if sal_null s
                    then return False
                    else if sal_head s /= '\n'
                         then return False
                         else alter_input sal_tail >> return True
\end{code}

\begin{code}
readTok :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readTok x = do
  work my_lex
  f <- work my_lex
  regstr <- work my_lex
  o <- work my_lex
  n <- work my_lex
  return $ FP (readFileName x f) $ TokReplace (drop_brackets $ sal_to_string regstr)
                          (sal_to_string o) (sal_to_string n)
    where drop_brackets = init . tail
\end{code}

\paragraph{Binary file modification}

Modify a binary file
\begin{verbatim}
binary FILENAME
oldhex
*HEXHEXHEX
...
newhex
*HEXHEXHEX
...
\end{verbatim}
\begin{code}
readBinary :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readBinary x = do
  work my_lex
  fi <- work my_lex
  work my_lex
  alter_input sal_dropWhite
  old <- work $ lines_starting_with '*'
  work my_lex
  alter_input sal_dropWhite
  new <- work $ lines_starting_with '*'
  return $ binary (fn2fp $ readFileName x fi)
                  (sal_to_PS $ sal_fromHex $ sal_concat old)
                  (sal_to_PS $ sal_fromHex $ sal_concat new)
\end{code}

\begin{code}
readAddFile :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readAddFile x = do work my_lex
                   f <- work my_lex
                   return $ FP (readFileName x f) AddFile
\end{code}

\begin{code}
readRmFile :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readRmFile x = do work my_lex
                  f <- work my_lex
                  return $ FP (readFileName x f) RmFile
\end{code}

\begin{code}
readMove :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readMove x = do work my_lex
                d <- work my_lex
                d' <- work my_lex
                return $ Move (readFileName x d) (readFileName x d')
\end{code}

\begin{code}
readChangePref :: (ParserM m, Monad (m PackedString)) => m PackedString (Prim C(x y))
readChangePref
 = do work my_lex
      p <- work my_lex
      f <- work (Just . sal_breakOn '\n' . sal_tail . sal_dropWhile (== ' '))
      t <- work (Just . sal_breakOn '\n' . sal_tail)
      return $ ChangePref (sal_to_string p) (sal_to_string f) (sal_to_string t)
\end{code}

\begin{code}
readAddDir :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readAddDir x = do work my_lex
                  f <- work my_lex
                  return $ DP (readFileName x f) AddDir
\end{code}

\begin{code}
readRmDir :: (ParserM m, Monad (m PackedString)) => FileNameFormat -> m PackedString (Prim C(x y))
readRmDir x = do work my_lex
                 f <- work my_lex
                 return $ DP (readFileName x f) RmDir
\end{code}

\begin{code}
#ifndef GADT_WITNESSES
readMerger :: (ParserM m, Monad (m PackedString)) => Bool -> m PackedString (Patch C(x y))
readMerger b = do work my_lex
                  g <- work my_lex
                  lex_string "("
                  Just (Sealed p1) <- readPatch' False
                  Just (Sealed p2) <- readPatch' False
                  lex_string ")"
                  let m = merger (sal_to_string g) p1 p2
                  return $ if b then m else invert m
#endif
\end{code}

\begin{code}
readNamed :: (ReadPatch p, ParserM m, Monad (m PackedString)) =>
             Bool -> m PackedString (Sealed (Named p C(x )))
readNamed want_eof
          = do mn <- maybe_work readPatchInfo
               case mn of
                   Nothing -> bug "readNamed 1"
                   Just n ->
                       do d <- read_depends
                          Just p <- readPatch' want_eof
                          return $ (NamedP n d) `mapSeal` p
read_depends :: (ParserM m, Monad (m PackedString)) => m PackedString [PatchInfo]
read_depends = do s <- peek_input
                  case my_lex s of
                      Just (xs, _) | sal_to_string xs == "<" ->
                          do work my_lex
                             read_pis
                      _ -> return []
read_pis :: (ParserM m, Monad (m PackedString)) => m PackedString [PatchInfo]
read_pis = do mpi <- maybe_work readPatchInfo
              case mpi of
                  Just pi -> do pis <- read_pis
                                return (pi:pis)
                  Nothing -> do alter_input (sal_tail . sal_dropWhile (/= '>'))
                                return []
\end{code}

\begin{code}
lines_starting_with :: Stringalike s => Char -> s -> Maybe ([PackedString], s)
lines_starting_with c thes =
    Just (lsw [] thes)
    where lsw acc s | sal_null s || sal_head s /= c = (reverse acc, s)
          lsw acc s = let s' = sal_tail s
                  in case sal_breakFirst '\n' s' of
                     Just (l, r) -> lsw (sal_to_PS l:acc) r
                     Nothing -> (reverse (sal_to_PS s':acc), sal_empty)
\end{code}
