module Text.JSON.Combinators where

import Text.JSON
import Text.JSON.Types
import Text.JSON.Pretty

import Prelude hiding ((.),id)
import Control.Category
import Control.Applicative
import Data.Partial
import Control.Monad
import Control.Arrow

type JSObjTo a = Partial String (JSObject JSValue) a
type JSValTo a = Partial String JSValue a

abbrevObj o =
    let ls = lines $ show $ pp_js_object o
    in  unlines $ if length ls > 40
                  then (take 20 ls ++ ["..."] ++ drop (length ls - 20) ls)
                  else ls

tracePartial (Partial f) = Partial (\ o -> left (++ ("\n\nFrom:\n\n" ++ abbrevObj o)) (f o))

valJS :: JSON a => JSValTo a
valJS = Partial (resultToEither . readJSON)

strJS = Partial str
    where str (JSString s) = Right (fromJSString s)
          str v = Left ("Not a JSString:\n\n" ++ show v)

arrJS = Partial arr
    where arr (JSArray as) = Right as
          arr v = Left ("Not a JSArray:\n\n" ++ show v)

objJS = Partial obj
    where obj (JSObject o) = Right o
          obj v = Left ("Not a JSObject:\n\n" ++ show v)

lookupJS :: String -> JSObjTo JSValue
lookupJS s = Partial (\obj->
    case get_field obj s of
        Just v -> Right v
        Nothing -> Left ("Could not find field: " ++ s))

inObj :: String -> JSObjTo a -> JSObjTo a
inObj s pa = tracePartial (pa . objJS . lookupJS s)

objOf :: JSObjTo a -> JSValTo a
objOf pa = pa . objJS

-- | Lookup a field and apply a partial function to the result
-- eg 'll "artist" strJS' returns the string value of the field '"artist"'
ll :: String -> JSValTo a -> JSObjTo a
ll s pa = tracePartial (pa . lookupJS s)

-- | Accounts for all the weird ways that lastFM use json arrays.
-- If s is not found returns [].
-- If s is mapped to a single value returns a singleton list
-- If s is mapped to an array returns the array as a list
llArr :: String -> JSValTo a -> JSObjTo [a]
llArr s pa =
    tracePartial (mapPartial pa . someOrNothing)
    where mapPartial = Partial . mapM . apply
          someOrNothing = (lazyArray . lookupJS s) <|> return []
          lazyArray = arrJS  <|> arr (:[])