module Data.Vhd.Types where

import Control.Exception
import Control.Monad
import qualified Data.ByteString as B
import Data.List
import qualified Data.Text as T
import Data.Text.Encoding
import Data.Word
import System.Random
import Text.Printf

data Header = Header
	{ headerCookie               :: Cookie
	, headerDataOffset           :: PhysicalByteAddress
	, headerTableOffset          :: PhysicalByteAddress
	, headerVersion              :: Version
	, headerMaxTableEntries      :: VirtualBlockCount
	, headerBlockSize            :: BlockByteCount
	, headerChecksum             :: Checksum
	, headerParentUniqueId       :: UniqueId
	, headerParentTimeStamp      :: TimeStamp
	, headerReserved1            :: B.ByteString
	, headerParentUnicodeName    :: ParentUnicodeName
	, headerParentLocatorEntries :: ParentLocatorEntries
	} deriving (Show, Eq)

data Footer = Footer
	{ footerCookie             :: Cookie
	, footerIsTemporaryDisk    :: Bool
	, footerFormatVersion      :: Version
	, footerDataOffset         :: PhysicalByteAddress
	, footerTimeStamp          :: TimeStamp
	, footerCreatorApplication :: CreatorApplication
	, footerCreatorVersion     :: Version
	, footerCreatorHostOs      :: CreatorHostOs
	, footerOriginalSize       :: VirtualByteCount
	, footerCurrentSize        :: VirtualByteCount
	, footerDiskGeometry       :: DiskGeometry
	, footerDiskType           :: DiskType
	, footerChecksum           :: Checksum
	, footerUniqueId           :: UniqueId
	, footerIsSavedState       :: Bool
	} deriving (Show, Eq)

data BatmapHeader = BatmapHeader
	{ batmapHeaderCookie       :: Cookie
	, batmapHeaderOffset       :: PhysicalByteAddress
	, batmapHeaderSize         :: Word32
	, batmapHeaderVersion      :: Version
	, batmapHeaderChecksum     :: Checksum
	} deriving (Show, Eq)

type BlockByteAddress            = Word32
type BlockByteCount              = Word32
type BlockSectorAddress          = Word32
type BlockSectorCount            = Word32
type DiskGeometryCylinders       = Word16
type DiskGeometryHeads           = Word8
type DiskGeometrySectorsPerTrack = Word8
type Checksum                    = Word32
type PhysicalByteAddress         = Word64
type PhysicalByteCount           = Word64
type PhysicalSectorAddress       = Word32
type PhysicalSectorCount         = Word32
type TimeStamp                   = Word32
type VirtualBlockAddress         = Word32
type VirtualBlockCount           = Word32
type VirtualByteAddress          = Word64
type VirtualByteCount            = Word64
type VirtualSectorAddress        = Word32
type VirtualSectorCount          = Word32

data Version      = Version VersionMajor VersionMinor deriving (Show, Eq)
type VersionMajor = Word16
type VersionMinor = Word16

data CreatorHostOs
	= CreatorHostOsUnknown
	| CreatorHostOsWindows
	| CreatorHostOsMacintosh deriving (Show, Eq)

data DiskGeometry = DiskGeometry
	DiskGeometryCylinders
	DiskGeometryHeads
	DiskGeometrySectorsPerTrack deriving (Show, Eq)

data DiskType
	= DiskTypeFixed
	| DiskTypeDynamic
	| DiskTypeDifferencing deriving (Show, Eq)

newtype Cookie               = Cookie             B.ByteString deriving (Show, Eq)
newtype CreatorApplication   = CreatorApplication B.ByteString deriving (Show, Eq)
newtype ParentLocatorEntry   = ParentLocatorEntry B.ByteString deriving (Show, Eq)
newtype ParentUnicodeName    = ParentUnicodeName  String       deriving (Show, Eq)
newtype UniqueId             = UniqueId           B.ByteString deriving (Eq)

instance Show UniqueId where
	show (UniqueId b) = intercalate "-" $ map disp [[0 .. 3], [4, 5], [6, 7], [8, 9], [10 .. 15]]
		where disp = concatMap (printf "%02x" . B.index b)

newtype ParentLocatorEntries = ParentLocatorEntries [ParentLocatorEntry] deriving (Show, Eq)

cookie               c = assert (B.length c ==   8) $ Cookie               c
creatorApplication   a = assert (B.length a ==   4) $ CreatorApplication   a
parentLocatorEntries e = assert (  length e ==   8) $ ParentLocatorEntries e
parentLocatorEntry   e = assert (B.length e ==  24) $ ParentLocatorEntry   e
uniqueId             i = assert (B.length i ==  16) $ UniqueId             i

parentUnicodeName n
	| encodedLength > 512 = error "parent unicode name length must be <= 512 bytes"
	| otherwise           = ParentUnicodeName n
	where
		encodedLength = B.length $ encodeUtf16BE $ T.pack n

randomUniqueId :: IO UniqueId
randomUniqueId
	= liftM (uniqueId . B.pack)
	$ replicateM 16
	$ liftM fromIntegral
	$ randomRIO (0 :: Int, 255)