{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings, TypeSynonymInstances,
             FlexibleInstances, MagicHash, GeneralizedNewtypeDeriving, CPP #-}
-- | Dealing with JavaScript objects on a low, low level.
module Haste.Object (
    JSObj, Type (..),
    (#), asString, asBool, asNumber, typeOf, lookupPath, toObject
  ) where
import Haste.Prim
import Haste.Foreign

-- | A JS object: either null/undefined or 'Just' an actual value.
type JSObj = Maybe JSAny

-- | Possible types of JS objects.
data Type = TUndefined | TNumber | TBoolean | TString | TFunction | TObject
  deriving (Show, Eq, Enum)

instance FromAny Type where
  fromAny = fmap toEnum . fromAny

-- | Any type on which we can look up a JS property.
class JSLookup a where
  infixl 4 #
  -- | Look up a property on an object-like value.
  (#) :: a -> JSString -> IO JSObj

instance JSLookup JSObj where
  Just o # prop = look o prop
  _      # _    = return Nothing

instance JSLookup a => JSLookup (IO a) where
  o # prop = o >>= (# prop)

-- | Lookup a whole path at once. More efficient for long paths.
--   @x `lookupPath` ["a", "b"]@ is equivalent to @x.a.b@.
lookupPath :: JSObj -> [JSString] -> IO JSObj
lookupPath = ffi "(function(o,as){\
                   for(var i in as){\
                     o = o[as[i]];\
                     if(typeof o==='undefined'){return null;}\
                   }\
                   return o;})"

look :: JSAny -> JSString -> IO (Maybe JSAny)
look = ffi "(function(o,s){return o[s] === undefined ? null : o[s];})"

-- | Convert the object to a 'JSString'.
asString :: JSObj -> IO (Maybe JSString)
asString = maybe (return Nothing) go
  where
    go = ffi "(function(o){return String(o);})"

-- | Convert the object to a 'Bool'.
asBool :: JSObj -> IO (Maybe Bool)
asBool = maybe (return Nothing) go
  where
    go = ffi "(function(o){return Boolean(o);})"

-- | Convert the object to a 'Double'.
asNumber :: JSObj -> IO (Maybe Double)
asNumber = maybe (return Nothing) go
  where
    go = ffi "(function(o){return Number(o);})"

-- | Get the type of a JS object.
typeOf :: JSObj -> IO Type
typeOf = maybe (return TUndefined) go
  where
    go = ffi "(function(o){\
               switch(typeof o){\
                 case 'undefined': return 0;\
                 case 'number':    return 1;\
                 case 'boolean':   return 2;\
                 case 'string':    return 3;\
                 case 'function':  return 4;\
                 default:          return 5;\
               }\
             })"