{-# LANGUAGE ViewPatterns #-}

module Numeric.Rational.Positive (Qp, fromFraction, toFraction, fromRational, toRational) where

import Prelude hiding (Num (..), Fractional (..), Eq (..), Ord (..), Real (..))
import qualified Prelude

import Algebra
import Data.Bits
import Data.Bool
import Data.Function (on)
import Data.Ratio
import Data.Semigroup (Sum (..), Product (..))
import Data.Universe.Class
import Numeric.Natural
import Relation.Binary.Comparison

data Qp = Qp {-# UNPACK #-}!Word {-# UNPACK #-}!Natural
  deriving (Show)

fromBits :: [Bool] -> Qp
fromBits = go 0 0 where
    go :: Word -> Natural -> [Bool] -> Qp
    go k n = k `seq` n `seq` \ case
        [] -> Qp k n
        b:bs -> go (k+1) (bool id (flip setBit $ fromIntegral k) b n) bs

toBits :: Qp -> [Bool]
toBits (Qp l n) = testBit n <$> [0..fromIntegral l-1]

fromFraction :: (Natural, Natural) -> Qp
fromFraction = fromBits . uncurry go where
    go :: Natural -> Natural -> [Bool]
    go p q = case compare p q of
        EQ -> []
        GT -> False : go (pq) q
        LT -> True  : go p (qp)

toFraction :: Qp -> (Natural, Natural)
toFraction = go . toBits where
    go [] = (1, 1)
    go (b:bs) | (p, q) <- go bs = bool (p+q, q) (p, p+q) b

fromRational :: Ratio Natural -> Qp
fromRational a = fromFraction (numerator a, denominator a)

toRational :: Qp -> Ratio Natural
toRational = go . toBits where
    go [] = 1
    go (False:bs) = 1 + go bs
    go (True :bs) = Prelude.recip (1 + Prelude.recip (go bs))

instance Preord Qp where a  b = GT  compare a b
instance PartialEq Qp where Qp l₁ n₁  Qp l₂ n₂ = (l₁, n₁)  (l₂, n₂)
instance PartialOrd Qp where tryCompare a b = Just (compare a b)
instance Eq Qp
instance Ord Qp where
    compare = go `on` toBits where
        go [] [] = EQ
        go (a:_) [] = bool GT LT a
        go [] (b:_) = bool LT GT b
        go (a:as) (b:bs) = compare b a <> go as bs

instance {-# OVERLAPPING #-} Semigroup (Product Qp) where
    Product (toFraction -> (p₁, q₁)) <> Product (toFraction -> (p₂, q₂)) =
      (Product . fromFraction) (p₁ * p₂, q₁ * q₂)

instance {-# OVERLAPPING #-} Monoid (Product Qp) where
    mempty = Product (Qp 0 0)

instance Group (Product Qp) where
    invert (Product (Qp l n)) = Product (Qp l (xor n $ shiftL 1 (fromIntegral l)  1))

instance {-# OVERLAPPING #-} Semigroup (Sum Qp) where
    Sum (toFraction -> (p₁, q₁)) <> Sum (toFraction -> (p₂, q₂)) =
        (Sum . fromFraction) (p₁ * q₂ + p₂ * q₁, q₁ * q₂)

instance Universe Qp where
    universe = fromBits <$> go where go = [] : (go >>= traverse (:) [False, True])

instance Prelude.Eq Qp where (==) = ()
instance Prelude.Ord Qp where compare = compare
instance Prelude.Num Qp where
    fromInteger = fromRational . Prelude.fromInteger
    (+) = (+)
    a * b = fromRational $ ((*) `on` toRational) a b
    abs = id
    signum = pure 1
    (-) = undefined