{- EnsemblConvert
Gregory W. Schwartz

Collections the functions pertaining to converting certain annotations into
ensembl annotations.
-}

{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE OverloadedStrings #-}

module EnsemblConvert
    ( toEnsemblAnn
    , toEnsemblDesc
    ) where

-- Standard
import Data.Maybe
import Data.List
import qualified Data.Map.Strict as Map
import Data.Monoid

-- Cabal
import qualified Data.ByteString.Lazy.Char8 as CL
import qualified Data.Text as T
import Data.Aeson
import Data.Aeson.Types
import Network.HTTP
import Safe

-- Local
import Types

-- | Null the null value of an object. If the object is Null, return "".
nullNull :: Value -> Parser T.Text
nullNull (String x)  = return $ x
nullNull _           = return $ ""

-- | Decode a Ensembl id query as an Ensembl identifier.
decodeEnsemblAnn :: CL.ByteString -> Maybe Ann
decodeEnsemblAnn ""    = Nothing
decodeEnsemblAnn query = getENSG ( eitherDecode query
                                :: Either String ([Map.Map T.Text T.Text])
                                 )
  where
    getENSG = fmap Ann
            . headMay
            . filter (T.isPrefixOf "ENSG")
            . concatMap Map.elems
            . either error id

-- | Decode a Ensembl id query as a description of the id.
decodeEnsemblDesc :: DescFields -> CL.ByteString -> Desc
decodeEnsemblDesc _ ""        = Desc ""
decodeEnsemblDesc field query =
    Desc
        . T.intercalate "/"
        . filter (not . T.null)
        . fmap (parse field)
        . either (error . ("In initial decoding: " <>)) id
        $ (eitherDecode query :: Either String [Object])
  where
    parse :: DescFields -> Object -> T.Text
    parse Synonyms    = T.intercalate "/"
                      . filter (not . T.null)
                      . either (error . ("In synonym decoding: " <>)) id
                      . parseEither (flip (.:) "synonyms")
    parse Description = either (error . ("In description decoding: " <>)) id
                      . parseEither ((=<<) nullNull . flip (.:) "description")
    getDesc = Desc
            . T.intercalate "/"
            . filter (not . T.null)
            . fmap (parse field)

-- | Get Ensembl annotation.
toEnsemblAnn :: UnknownAnn -> IO (Maybe Ann)
toEnsemblAnn (UnknownAnn "")    = return Nothing
toEnsemblAnn (UnknownAnn query) = do
    let base     = "http://rest.ensembl.org/"
        xrefs    = "xrefs/symbol/homo_sapiens/"
        xrefOpts = "?content-type=application/json;all_levels=1"
        xrefReq  = base <> xrefs <> T.unpack query <> xrefOpts

    xrefRsp   <- simpleHTTP (getRequest xrefReq) >>= getResponseBody

    let ensemblAnn = case xrefRsp of
                        "[]"                                      -> Nothing
                        (stripPrefix "{\"error\":" -> Just _)     -> Nothing
                        x    -> decodeEnsemblAnn . CL.pack $ x

    return ensemblAnn

-- | Get Ensembl description.
toEnsemblDesc :: DescFields -> UnknownAnn -> IO (Maybe Desc)
toEnsemblDesc _ (UnknownAnn "")        = return Nothing
toEnsemblDesc field (UnknownAnn query) = do
    let base     = "http://rest.ensembl.org/"
        xrefs    = "xrefs/id/"
        xrefOpts = "?content-type=application/json;all_levels=1"
        xrefReq  = base <> xrefs <> T.unpack query <> xrefOpts

    xrefRsp   <- simpleHTTP (getRequest xrefReq) >>= getResponseBody

    let ensemblDesc = case xrefRsp of
                        "[]"                                      -> Nothing
                        (stripPrefix "{\"error\":" -> Just _)     -> Nothing
                        x    -> Just . decodeEnsemblDesc field . CL.pack $ x

    return ensemblDesc