{-# LANGUAGE OverloadedStrings #-}

module ArTimestampWiper where

import           Control.Applicative
import           Control.Monad
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import           Data.Char
import           System.IO


-- | Removes the time stamps of all files in the .a file.
arFileWipeTimeStamps :: FilePath -> IO ()
arFileWipeTimeStamps path = withBinaryFile path ReadWriteMode $ \h -> do

  -- We iterate through the archive stepping from one file header to the next,
  -- setting the time stamp field to zero.
  -- The size field tells us where the next header is.
  -- See: http://en.wikipedia.org/wiki/Ar_%28Unix%29.

  archiveSize <- hFileSize h

  let go entryOffset | entryOffset == archiveSize = return () -- done, at end
                     | entryOffset >  archiveSize = die "Archive truncated"
                     -- Headers are aligned to even bytes
                     | odd entryOffset            = go (entryOffset + 1)
                     | otherwise = do

        -- Sanity check
        magic <- goto 58 >> BS.hGet h 2
        when (magic /= "\x60\x0a") $ die "Bad ar magic"

        -- Get size (to find following file)
        size <- goto 48 >> parseSize . BS8.unpack <$> BS.hGet h 10

        -- Wipe time stamp
        goto 16 >> BS.hPut h "0           " -- 12 chars

        -- Seek to next file at header + file size
        go (entryOffset + 60 + size)

        where
          goto pos = hSeek h AbsoluteSeek (entryOffset + pos)

          parseSize x = case reads x of
            [(s, r)] | all isSpace r -> s
            _                        -> die "Malformed header"

          die msg = error $ "arFileWipeTimeStamps: " ++ path ++ ": "
                            ++ msg ++ " at offset " ++ show entryOffset

  go 8 -- 8 == size of global header, before first file header