module Opentype.Fileformat.Types where
import Data.Word
import Data.Int
import Data.Bits
import Data.Binary.Put
import Data.ByteString.Unsafe
import qualified Data.ByteString as Strict
import qualified Data.Map as M

-- | A ShortFrac is an 16 bit signed fixed number with a bias of
-- 14. This means it can represent numbers between 1.999 (0x7fff) and
-- -2.0 (0x8000). 1.0 is stored as 16384 (0x4000) and -1.0 is stored
-- as -16384 (0xc000).  Efficient numeric instances are provided.
newtype ShortFrac = ShortFrac Int16
  
-- | signed fixed-point number
type Fixed = Word32
-- | FWord describes a quantity in FUnits, the smallest
-- measurable distance in em space.
type FWord = Int16
-- | UFWord describes a quantity in FUnits, the smallest measurable
-- distance in em space.
type UFWord = Word16
-- | the glyph index in the glyph table
type GlyphID = Word16

data PlatformID =
  UnicodePlatform |
  -- | /DEPRECATED/
  MacintoshPlatform |
  MicrosoftPlatform
  deriving (Ord, Eq, Show)


type WordMap a = M.Map Word32 a
-- return larged power of 2 <= i 
iLog2 :: Integral a => a -> a
iLog2 = iLog2' 0 where
  iLog2' base i
    | i > 0 = iLog2' (base+1) (i `quot` 2) 
    | otherwise = base-1

byteAt :: (Bits a, Num a) => a -> Int -> Bool
byteAt flag i = flag .&. 1 `shift` i /= 0
{-# SPECIALIZE byteAt :: Word8 -> Int -> Bool #-}
{-# SPECIALIZE byteAt :: Word16 -> Int -> Bool #-}

makeFlag :: [Bool] -> Word16
makeFlag l =
  fromIntegral $ sum $ zipWith (*) (iterate (*2) 1) $
  map fromEnum l

instance Num ShortFrac where
  (ShortFrac a) + (ShortFrac b) = ShortFrac $ a + b
  (ShortFrac a) - (ShortFrac b) = ShortFrac $ a - b
  (ShortFrac a) * (ShortFrac b) =
    ShortFrac $ fromIntegral (((fromIntegral a :: Int32) * (fromIntegral b :: Int32)) `shift` (-14))
  abs (ShortFrac a) = ShortFrac $ abs a
  fromInteger i = ShortFrac $ fromIntegral i `shift` 14
  signum (ShortFrac a) = fromIntegral $ signum a

instance Eq ShortFrac where
  (ShortFrac a) == (ShortFrac b) = a == b

instance Ord ShortFrac where
  compare (ShortFrac a) (ShortFrac b) = compare a b

instance Fractional ShortFrac where
  fromRational r =
    ShortFrac $ fromIntegral $ 
    floor ((r+2) * 0x4000) - (0x8000::Word16)
  (ShortFrac a) / (ShortFrac b) =
    ShortFrac $ fromIntegral $
    ((fromIntegral a :: Int32) `shift` 14) `quot` fromIntegral b

instance Show ShortFrac where
  show a = show (realToFrac a :: Float)

instance Real ShortFrac where
  toRational (ShortFrac a) =
    fromIntegral ((fromIntegral a::Word16) + 0x8000) / 0x4000 - 2

instance RealFrac ShortFrac where
  properFraction (ShortFrac a)
    | a < 0 && f /= 0 = (i+1, ShortFrac (-f))
    | otherwise = (i, ShortFrac f)
    where i = fromIntegral (((fromIntegral a :: Word16) + 0x8000) `shift` (-14)) - 2
          f = a .&. 0x3fff

putShortFrac :: ShortFrac -> Put
putShortFrac (ShortFrac a) = putInt16be a

putPf :: PlatformID -> Put
putPf UnicodePlatform = putWord16be 0
putPf MacintoshPlatform = putWord16be 1
putPf MicrosoftPlatform = putWord16be 3

toPf :: Word16 -> Either String PlatformID
toPf i =
  case i of
    0 -> Right UnicodePlatform
    1 -> Right MacintoshPlatform
    3 -> Right MicrosoftPlatform
    j -> Left $ "unknown platformID " ++ show j

index16 :: Strict.ByteString -> Word16 -> Either String Word16
index16 bs i
  | Strict.length bs < fromIntegral ((i+1)*2) ||
    i < 0 = Left $ "Index " ++ show i ++ " out of Bounds"
  | otherwise = Right $ b1 * 256 + b2
  where
    b1, b2 :: Word16
    b1 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*2)
    b2 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*2 + 1)

index32 :: Strict.ByteString -> Word32 -> Either String Word32
index32 bs i
  | Strict.length bs < fromIntegral ((i+1)*4) ||
    i < 0 = Left $ "Index " ++ show i ++ " Out of Bounds"
  | otherwise = Right $ b1 `shift` 24 .|. b2 `shift` 16 .|. b3 `shift` 8 .|. b4
  where
    b1, b2, b3, b4 :: Word32
    b1 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4)
    b2 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4 + 1)
    b3 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4 + 2)
    b4 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4 + 3)