{-# OPTIONS_GHC -fno-warn-orphans #-}
module Bio.MMTF
  ( module Bio.MMTF.Type
  , decode
  , fetch
  ) where

import           Bio.MMTF.MessagePack   ()
import           Bio.MMTF.Type   hiding ( IArray )
import           Bio.MMTF.Decode        ( l2a )
import           Bio.Structure

import           Data.Array             ( Array, (!), elems )
import           Data.Int               ( Int32 )
import           Data.Bifunctor         ( Bifunctor (..) )
import           Data.List              ( mapAccumL, zip3, zip4 )
import           Data.Text              ( Text )
import           Data.ByteString.Lazy   ( ByteString )
import           Data.MessagePack       ( unpack )
import           Data.Monoid            ( (<>) )
import           Data.String            ( IsString (..) )
import           Control.Monad.IO.Class ( MonadIO )
import           Network.HTTP.Simple    ( httpLBS, getResponseBody )
import           Linear.V3              ( V3 (..) )

-- | Decodes a 'ByteString' to 'MMTF'
--
decode :: Monad m => ByteString -> m MMTF
decode = unpack

-- | Fetches MMTF structure from RSCB
fetch :: MonadIO m => String -> m MMTF
fetch pdbid = do let url = fromString $ "https://mmtf.rcsb.org/v1.0/full/" <> pdbid
                 resp <- httpLBS url
                 decode (getResponseBody resp)

instance StructureModels MMTF where
    modelsOf m = l2a (Model . l2a <$> zipWith (zipWith Chain) chainNames chainResis)
      where
        chainsCnts = fromIntegral <$> elems (chainsPerModel (model m))
        groupsCnts = fromIntegral <$> elems (groupsPerChain (chain m))
        groupsRaws = snd $ mapAccumL getGroups (0, 0) groupsCnts
        groups     = cutter chainsCnts groupsRaws
        chainNames = cutter chainsCnts (elems $ chainNameList $ chain m)
        chainResis = fmap (fmap (l2a . fmap mkResidue)) groups

        getGroups :: (Int, Int) -> Int -> ((Int, Int), [(GroupType, SecondaryStructure, [Atom])])
        getGroups (chOffset, atOffset) sz = let chEnd        = chOffset + sz
                                                gtl          = groupTypeList (group m)
                                                gl           = groupList (group m)
                                                ssl          = secStructList (group m)
                                                chr          = [chOffset .. chEnd - 1]
                                                rgt          = (gl !) . fromIntegral . (gtl !) <$> chr
                                                rss          = (ssl !) <$> chr
                                                (atEnd, ats) = mapAccumL getAtoms atOffset rgt
                                            in  ((chEnd, atEnd), zip3 rgt rss ats)

        getAtoms :: Int -> GroupType -> (Int, [Atom])
        getAtoms offset gt = let cl  = fmap fromIntegral . elems . gtFormalChargeList $ gt
                                 nl  = elems . gtAtomNameList $ gt
                                 el  = elems . gtElementList $ gt
                                 ics = [offset .. end - 1]
                                 end = offset + length cl
                             in  (end, mkAtom <$> zip4 cl nl el ics)

        mkResidue :: (GroupType, SecondaryStructure, [Atom]) -> Residue
        mkResidue (gt, ss, atoms) = Residue (gtGroupName gt) (l2a atoms)
                                            (mkBonds (gtBondAtomList gt) (gtBondOrderList gt))
                                             ss (gtChemCompType gt)

        mkBonds :: Array Int (Int32, Int32) -> Array Int Int32 -> Array Int Bond
        mkBonds bal bol = let ball = bimap fromIntegral fromIntegral <$> elems bal
                              boll = fromIntegral <$> elems bol
                              res  = zipWith (\(f, t) o -> Bond f t o) ball boll
                          in  l2a res

        mkAtom :: (Int, Text, Text, Int) -> Atom
        mkAtom (fc, n, e, idx) = let x = xCoordList (atom m)
                                     y = yCoordList (atom m)
                                     z = zCoordList (atom m)
                                     o = occupancyList (atom m)
                                     b = bFactorList (atom m)
                                 in  Atom n e (V3 (x ! idx) (y ! idx) (z ! idx)) fc (b ! idx) (o ! idx)

        cutter :: [Int] -> [a] -> [[a]]
        cutter []     []    = []
        cutter (x:xs) ys    = take x ys : cutter xs (drop x ys)
        cutter []     (_:_) = error "Cutter: you cannot be here"