    {-
    Copyright (C) 2009-2011 John Goerzen <jgoerzen@complete.org>

    All rights reserved.

    For license and copyright information, see the file LICENSE

    -}

    {- |
       Module     : Data.Convertible.Base
       Copyright  : Copyright (C) 2009-2011 John Goerzen
       License    : BSD3

       Maintainer : John Goerzen <jgoerzen@complete.org>
       Stability  : provisional
       Portability: portable

    -}

    module Data.Convertible.Base( -- * The conversion process
                                  convert,
                                  Convertible(..),
                                  -- * Handling the results
                                  ConvertResult,
                                  ConvertError(..),
                                  convError,
                                  prettyConvertError
                                 )
    where
    import Control.Monad.Error
    import Data.Typeable

    {- | The result of a safe conversion via 'safeConvert'. -}
    type ConvertResult a = Either ConvertError a

    ----------------------------------------------------------------------
    -- Conversions
    ----------------------------------------------------------------------

    {- | A typeclass that represents something that can be converted.
    A @Convertible a b@ instance represents an @a@ that can be converted to a @b@. -}
    class Convertible a b where
        {- | Convert @a@ to @b@, returning Right on success and Left on error.
           For a simpler interface, see 'convert'. -}
        safeConvert :: a -> ConvertResult b

    {-
    {- | Any type can be converted to itself. -}
    instance Convertible a a where
        safeConvert x = return x
    -}

    {-
    {- | Lists of any convertible type can be converted. -}
    instance Convertible a b => Convertible [a] [b] where
        safeConvert = mapM safeConvert
    -}

    {- | Convert from one type of data to another.  Raises an exception if there is
    an error with the conversion.  For a function that does not raise an exception
    in that case, see 'safeConvert'. -}
    convert :: Convertible a b => a -> b
    convert x =
        case safeConvert x of
          Left e -> error (prettyConvertError e)
          Right r -> r

    {-
    instance Convertible Int Double where
        safeConvert = return . fromIntegral
    instance Convertible Double Int where
        safeConvert = return . truncate         -- could do bounds checking here
    instance Convertible Integer Double where
        safeConvert = return . fromIntegral
    instance Convertible Double Integer where
        safeConvert = return . truncate
    -}

    ----------------------------------------------------------------------
    -- Error Handling
    ----------------------------------------------------------------------

    {- | How we indicate that there was an error. -}
    data ConvertError = ConvertError {
          convSourceValue :: String,
          convSourceType :: String,
          convDestType :: String,
          convErrorMessage :: String}
                        deriving (Eq, Read, Show)

    instance Error ConvertError where
        strMsg x = ConvertError "(unknown)" "(unknown)" "(unknown)" x

    convError' :: (Show a, Typeable a, Typeable b) =>
                   String -> a -> b -> ConvertResult b
    convError' msg inpval retval =
         Left $ ConvertError {
                 convSourceValue = show inpval,
                 convSourceType = show . typeOf $ inpval,
                 convDestType = show . typeOf $ retval,
                 convErrorMessage = msg}

    convError :: (Show a, Typeable a, Typeable b) =>
                 String -> a -> ConvertResult b
    convError msg inpval =
        convError' msg inpval undefined

    prettyConvertError :: ConvertError -> String
    prettyConvertError (ConvertError sv st dt em) =
        "Convertible: error converting source data " ++ sv ++ " of type " ++ st
        ++ " to type " ++ dt ++ ": " ++ em