{-# LANGUAGE TypeSynonymInstances, TemplateHaskell, QuasiQuotes, MultiParamTypeClasses, FlexibleInstances, DeriveDataTypeable, NamedFieldPuns, ScopedTypeVariables #-}
module Examples.Binary where
import Language.Pads.Padsc
import Language.Pads.Library.LittleEndian as LE
import qualified Language.Pads.Library.BigEndian as BE
import qualified Language.Pads.Library.Native as Native
import Language.Pads.Testing
import System.IO.Unsafe (unsafePerformIO)

-- the format specified in:
-- https://java.net/downloads/heap-snapshot/hprof-binary-format.html

[pads| 
       type U4 = BE.Word32
       type U2 = BE.Word16

       -- total header length: 31 = 19 + 4 + 4 + 4
       data Header = Header
           { version :: [Char] length <| 19 |> 
           , idsiz :: U4
           , time_hw :: U4
           , time_lw :: U4 }
                     -- TODO: can we convert this to the date and hard code it?
                     -- 00 00 01 2b 87 5b 95 3e
                     -- 299 2270926142
                     -- 1286466147646 = October 7th, 2010
                     -- http://www.epochconverter.com/
       data Hprof = Hprof
           { hdr :: Header
           -- , entry :: Entry
           , entries :: Entries -- Originally EntriesB
           , theRest :: [Word8] }
                  
       -- total entry length: 9 + len = 1 + 4 + 4 + len where len is determined by the tag field
       data Entry = Entry
           { tag :: Word8
           , time :: U4
           , len :: U4
             -- fromIntegral :: (Integral a, Num b) => a -> b
           , body :: Body <| (fromIntegral(tag) :: Int, fromIntegral(len) :: Int) |> 
           -- , body2 :: Body <| (fromIntegral(tag) :: Int, fromIntegral(len) :: Int) |>
}

           -- ???: Why does fromIntegral work here?

       -- !!: Want each Entry to take as many bytes as it needs, and the parser starts reading the next byte as a new Entry
       type Entries = [Entry] terminator EOF -- ?? [Entry | EOR] 
       -- type EntriesB = partition Entries using <| bytes 6 |>
       -- ?? Does the list type always have to have corresponding terminator and partition declaration?
           
       type ID = U4 -- !! should take idsiz from the Header

       data Body (tag :: Int, len :: Int) =
         case <| tag |> of
           1 -> One  -- ?? One and Two never declared...
                { id :: ID
                , str :: [Char] length <| len - 4 |> } -- !! should be len - idsiz.  len - (idsiz hdr)
         | 2 -> Two 
                { clsserial :: U4
                , objid :: ID 
                , stkserial :: U4
                , clsnamestrid :: ID }
         | 3 -> Three 
                { clsserial :: Word32 }
         | 4 -> Four 
                { stkframeid :: ID
                , method_nameid :: ID
                , method_sigid :: ID
                , srcfile_nameid :: ID
                , clsserial :: Word32
                , stkInfo :: [Word8] } -- !! should take an argument
         | 14 -> Fourteen
                 { bitmask :: U4 -- bizarre case: using 32 bits when you need 1
                 , stkdepth :: U2
                 --, next :: Word8 
                 -- , rest :: [Word8] 
                 }
         | otherwise -> Other [Word8]

-- Limitations:
-- One cannot access Header->idsiz from inside of an Entry type?
-- 

|]

hprof_input_file = "Examples/data/specjbb.hprof"
-- hprof_result :: (Header, Header_md) = unsafePerformIO $ parseFileWith header_parseM hprof_input_file
hprof_result :: (Hprof, Hprof_md) = unsafePerformIO $ parseFileWith hprof_parseM hprof_input_file

-- more readable result
result = fst hprof_result

-- returns first n entries
taken n = Prelude.take n $ entries result

-- gets the nth element. index starts at 1.
getnth n = Prelude.head $ Prelude.drop (n-1) $ entries result

hdrlen = 31
-- returns the number of bytes consumed to read n entries
lenupto n = hdrlen + (foldr (+) 0 $ map ((+9) . len) $ Prelude.take n $ entries result)
