    {- |
        Copyright  : Copyright (C) 2006-2014 Bjorn Buckwalter
        License    : BSD3

        Maintainer : bjorn@buckwalter.se
        Stability  : Stable
        Portability: GHC only?

    Defines types for manipulation of units and quantities without phantom types for their dimensions.
    -}

    {-# LANGUAGE DataKinds #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE NoImplicitPrelude #-}
    {-# LANGUAGE ScopedTypeVariables #-}

    module Numeric.Units.Dimensional.Dynamic
    (
      -- * Dynamic Quantities
      AnyQuantity
    , demoteQuantity, promoteQuantity
      -- * Dynamic Units
    , AnyUnit
    , demoteUnit, promoteUnit
    ) where

    import Numeric.Units.Dimensional.Prelude hiding (lookup)
    import Numeric.Units.Dimensional.Coercion
    import Numeric.Units.Dimensional.UnitNames (UnitName, baseUnitName)
    import Data.ExactPi
    import Data.Proxy

    -- | A 'Quantity' whose 'Dimension' is only known dynamically.
    data AnyQuantity v = AnyQuantity Dimension' v
      deriving (Eq)

    instance (Show v) => Show (AnyQuantity v) where
      show (AnyQuantity d v) = (show v) ++ " " ++ (show . baseUnitName $ d)

    instance HasDimension (AnyQuantity v) where
      dimension (AnyQuantity d _) = d

    -- | Converts a 'Quantity' of statically known 'Dimension' into an 'AnyQuantity'.
    demoteQuantity :: forall d v.(KnownDimension d) => Quantity d v -> AnyQuantity v
    demoteQuantity (Quantity val) = AnyQuantity dim val
      where dim = dimension (Proxy :: Proxy d)

    -- | Converts an 'AnyQuantity' into a 'Quantity' of statically known 'Dimension', or 'Nothing' if the dimension does not match.
    promoteQuantity :: forall d v.(KnownDimension d) => AnyQuantity v -> Maybe (Quantity d v)
    promoteQuantity (AnyQuantity dim val) | dim == dim' = Just . Quantity $ val
                                          | otherwise   = Nothing
      where
        dim' = dimension (Proxy :: Proxy d)

    -- | A 'Unit' whose 'Dimension' is only known dynamically.
    data AnyUnit = AnyUnit Dimension' (UnitName 'NonMetric) ExactPi

    instance Show AnyUnit where
      show (AnyUnit _ n e) = "1 " ++ (show n) ++ " =def= " ++ (show e) ++ " of the SI base unit"

    instance HasDimension AnyUnit where
      dimension (AnyUnit d _ _) = d

    -- | Converts a 'Unit' of statically known 'Dimension' into an 'AnyUnit'.
    demoteUnit :: forall a d v.(KnownDimension d) => Unit a d v -> AnyUnit
    demoteUnit u = AnyUnit dim (name $ weaken u) (exactValue u)
      where
        dim = dimension (Proxy :: Proxy d)

    -- | Converts an 'AnyUnit' into a 'Unit' of statically known 'Dimension', or 'Nothing' if the dimension does not match.
    --
    -- The result is represented in 'ExactPi', conversion to other representations is possible using 'changeRepApproximate'.
    promoteUnit :: forall d.(KnownDimension d) => AnyUnit -> Maybe (Unit 'NonMetric d ExactPi)
    promoteUnit (AnyUnit dim n e) | dim == dim' = Just $ mkUnitR n e siUnit
                                  | otherwise   = Nothing
      where
        dim' = dimension (Proxy :: Proxy d)