packages feed

cobot-io-0.1.3.0: test/PDBParserSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module PDBParserSpec where

import           Bio.PDB.Reader  (fromTextPDB)
import           Bio.PDB.Type    (Atom (..), FieldType (..), PDB (..))
import qualified Data.Map.Strict (empty, fromList, singleton)
import           Data.Text       as T (Text, intercalate, length, lines, pack,
                                       replicate, take)
import qualified Data.Vector     as V (empty, fromList, singleton)
import           Test.Hspec

oneModelSpecP :: Spec
oneModelSpecP = describe "One model." $
        it "correctly parses pdb with only one model without strings \"MODEL\" & \"ENDMDL\"" $ do
        let mt  = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" ++
                                         "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                         "COMPND compnd\n" ++
                                         "SOURCE source\n" ++
                                         "KEYWDS keywds\n" ++
                                         "AUTHOR  Masha\n" ++
                                         "REVDAT revdat\n" ++
                                         "REMARK   1 REFERENCE 1\n" ++
                                         "SEQRES seqres\n" ++
                                         "CRYST1 cryst1\n" ++
                                         "ORIGX1 origx1 n=1\n" ++
                                         "SCALE2 sclaen n=2\n" ++
                                         "ATOM   2032  OXT CYS A 214      -4.546 -29.673  26.796  1.0 143.51           O  \n" ++
                                         "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" ++
                                         "TER    2034      CYS A 214                                                      \n" ++
                                         "ATOM   2035  N   GLU B   1      18.637 -61.583  66.852  1.0 118.48           N  \n" ++
                                         "TER   12534      ARG D 474                                                      \n" ++
                                         "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" ++
                                         "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" ++
                                         "ATOM   2036  CA  GLU B   1      19.722 -62.606  66.868  1.00 19.77           C  \n" ++
                                         "ATOM   2036  CA  GLU B   1A     19.722 -62.606  66.868  1.00 19.77           C  \n" ++
                                         "CONECT conect\n" ++
                                         "CONECT conect conect\n" ++
                                         "MASTER 1 2 3 4 5 6 7 8\n" ++
                                         "END"
                                        )
        mt `shouldBe` Right ([], oneModelPDB)

oneModelPDB :: PDB
oneModelPDB =  PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                   , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")
                   , models = V.singleton $ V.fromList [V.fromList [
                                                                Atom {atomSerial = 2032, atomName = " OXT", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ',
                                                                        atomX = -4.546, atomY = -29.673, atomZ = 26.796, atomOccupancy = 1.0, atomTempFactor = 143.51, atomElement = " O", atomCharge = "  "},
                                                                Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ', 
                                                                        atomX = -6.124, atomY = -27.225, atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}],
                                                        V.fromList [
                                                                Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',
                                                                        atomX = 18.637, atomY = -61.583, atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "},
                                                                Atom {atomSerial = 12535, atomName = " C1 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ',
                                                                        atomX = 5.791, atomY = -20.194, atomZ = -7.051, atomOccupancy = 1.0, atomTempFactor = 34.66, atomElement = " C", atomCharge = "  "},
                                                                Atom {atomSerial = 12538, atomName = " C4 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ', 
                                                                        atomX = 6.943, atomY = -19.507, atomZ = -9.597, atomOccupancy = 1.0, atomTempFactor = 25.87, atomElement = " C", atomCharge = "  "},
                                                                Atom {atomSerial = 2036, atomName = " CA ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',
                                                                        atomX = 19.722, atomY = -62.606, atomZ = 66.868, atomOccupancy = 1.0, atomTempFactor = 19.77, atomElement = " C", atomCharge = "  "},
                                                                Atom {atomSerial = 2036, atomName = " CA ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = 'A',
                                                                        atomX = 19.722, atomY = -62.606, atomZ = 66.868, atomOccupancy = 1.0, atomTempFactor = 19.77, atomElement = " C", atomCharge = "  "}]
                                                        ]
                  , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source"]),(KEYWDS,V.fromList[" keywds"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),
                                                            (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]
                   }

manyModelsSpecP :: Spec
manyModelsSpecP = describe "Some models." $
        it "correctly parses pdb with many models - they have strings \"MODEL\" & \"ENDMDL\" and text has disordered strings" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" ++
                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                        "COMPND compnd\n" ++
                                        "SOURCE source\n" ++
                                        "REMARK 1   REFERENCE 1\n" ++
                                        "REMARK 2   Reference 2_1/1\n" ++
                                        "CRYST1 cryst1\n" ++
                                        "ORIGX1 origx1 n=1\n" ++
                                        "SCALE2 sclaen n=2\n" ++
                                        "MODEL  4  \n" ++
                                        "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" ++
                                        "TER    2034      CYS A 214                                                      \n" ++
                                        "ANISOU anisou\n" ++
                                        "ENDMDL \n" ++
                                        "MODEL  5 \n" ++
                                        "ATOM   2035  N   GLU B   1      18.637-691.583  66.852  1.0 118.48           N  \n" ++
                                        "ATOM  12531 HH12 ARG D 474      45.558 -39.551 -49.936  1.00 15.00           H  \n" ++
                                        "ENDMDL \n" ++
                                        "MODEL  6 \n" ++
                                        "ATOM  12532 HH21 ARG D 474      47.457 -38.007 -47.445  1.00 15.00           H  \n" ++
                                        "TER   12534      ARG D 474                                                      \n" ++
                                        "ATOM  12533 HH22 ARG D 474      47.405 -39.268 -48.629  1.00 15.00           H  \n" ++
                                        "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" ++
                                        "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" ++
                                        "CONECT conect conect\n" ++
                                        "CONECT conect\n" ++
                                        "ENDMDL\n" ++
                                        "SEQRES seqres\n" ++
                                        "KEYWDS keywds\n" ++
                                        "EXPDTA expdta\n" ++
                                        "AUTHOR  Masha\n" ++
                                        "REVDAT revdat\n" ++
                                        "MASTER 1 2 3 4 5 6 7 8\n"
                                      )
        mt `shouldBe` Right ([], manyModelsPDB)

manyModelsPDB :: PDB
manyModelsPDB = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                    , remarks = Data.Map.Strict.fromList [(Just 1, V.singleton "REFERENCE 1"), (Just 2, V.singleton "Reference 2_1/1")]
                    , models = V.fromList [V.fromList  [V.fromList [
                                                                Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ', 
                                                                        atomX = -6.124, atomY = -27.225, atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]
                                                        ],
                                           V.fromList  [V.fromList [
                                                                Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ', 
                                                                        atomX = 18.637, atomY = -691.583, atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "}],
                                                        V.fromList [
                                                                Atom {atomSerial = 12531, atomName = "HH12", atomAltLoc = ' ', atomResName = "ARG", atomChainID = 'D', atomResSeq = 474, atomICode = ' ', 
                                                                        atomX = 45.558, atomY = -39.551, atomZ = -49.936, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]
                                                        ],
                                           V.fromList  [V.fromList [
                                                                Atom {atomSerial = 12532, atomName = "HH21", atomAltLoc = ' ', atomResName = "ARG", atomChainID = 'D', atomResSeq = 474, atomICode = ' ', 
                                                                        atomX = 47.457, atomY = -38.007, atomZ = -47.445, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "},
                                                                Atom {atomSerial = 12533, atomName = "HH22", atomAltLoc = ' ', atomResName = "ARG", atomChainID = 'D', atomResSeq = 474, atomICode = ' ', 
                                                                        atomX = 47.405, atomY = -39.268, atomZ = -48.629, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}],
                                                        V.fromList [
                                                                Atom {atomSerial = 12535, atomName = " C1 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ',
                                                                        atomX = 5.791, atomY = -20.194, atomZ = -7.051, atomOccupancy = 1.0, atomTempFactor = 34.66, atomElement = " C", atomCharge = "  "},
                                                                Atom {atomSerial = 12538, atomName = " C4 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ', 
                                                                        atomX = 6.943, atomY = -19.507, atomZ = -9.597, atomOccupancy = 1.0, atomTempFactor = 25.87, atomElement = " C", atomCharge = "  "}]
                                                        ]
                                           ]
                    , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source"]),(KEYWDS,V.fromList[" keywds"]),(EXPDTA,V.fromList[" expdta"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),
                                                              (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]
                    }

noModelsSpecP :: Spec
noModelsSpecP = describe "No models." $
        it "correctly parses pdb without models (no ATOM, TER, HETATM strings)" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" ++
                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                        "COMPND compnd\n" ++
                                        "SOURCE source\n" ++
                                        "KEYWDS keywds\n" ++
                                        "EXPDTA expdta\n" ++
                                        "AUTHOR  Masha\n" ++
                                        "REVDAT revdat\n" ++
                                        "REMARK 1   REFERENCE 1\n" ++
                                        "SEQRES seqres\n" ++
                                        "CRYST1 cryst1\n" ++
                                        "ORIGX1 origx1 n=1\n" ++
                                        "SCALE2 sclaen n=2\n" ++
                                        "MASTER 1 2 3 4 5 6 7 8\n"
                                      )
        mt `shouldBe` Right ([], noModelsPDB)

noModelsPDB :: PDB
noModelsPDB = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                  , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")
                  , models = V.empty
                  , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source"]),(KEYWDS,V.fromList[" keywds"]),(EXPDTA,V.fromList[" expdta"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),
                                                            (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]
                  }

allFieldsModelSpecP :: Spec
allFieldsModelSpecP = describe "PDB with all strings." $
        it "correctly parses pdb with all types of string" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" ++
                                        "OBSLTE obslte\n" ++
                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                        "SPLIT split\n" ++
                                        "CAVEAT caveat\n" ++
                                        "COMPND compnd\n" ++
                                        "SOURCE source\n" ++
                                        "KEYWDS keywds\n" ++
                                        "EXPDTA expdta1\n" ++
                                        "EXPDTA expdta2\n" ++
                                        "NUMMDL nummdl\n" ++
                                        "MDLTYP mdltyp mdltyp\n" ++
                                        "AUTHOR  Masha\n" ++
                                        "REVDAT revdat\n" ++
                                        "SPRSDE sprsde\n" ++
                                        "JRNL   jrnl\n" ++
                                        "REMARK 1   REFERENCE 1\n" ++
                                        "DBREF dbref\n" ++
                                        "DBREF1 dbref1\n" ++
                                        "DBREF2 dbref2_1\n" ++
                                        "DBREF2 dbref2_2\n" ++
                                        "SEQADV seqadv\n" ++
                                        "SEQRES seqres\n" ++
                                        "MODRES modres\n" ++
                                        "HET    het\n" ++
                                        "HETNAM hetnam\n" ++
                                        "HETSYN hetsyn\n" ++
                                        "FORMUL of love\n" ++
                                        "HELIX helix\n" ++
                                        "SHEET sheet\n" ++
                                        "SSBOND ssbond\n" ++
                                        "LINK   link\n" ++
                                        "CISPEP cispep\n" ++
                                        "SITE   site\n" ++
                                        "CRYST1 cryst1\n" ++
                                        "ORIGX1 origx1 n=1\n" ++
                                        "SCALE2 sclaen n=2\n" ++
                                        "MTRIX3 matrixn n=3\n" ++
                                        "MODEL 1\n" ++
                                        "ATOM  12532 HH21 ARG D 474      47.457 -38.007 -47.445  1.00 15.00           H  \n" ++
                                        "TER   12534      ARG D 474                                                      \n" ++
                                        "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" ++
                                        "CONECT conect\n" ++
                                        "CONECT conect conect\n" ++
                                        "ENDMDL\n" ++
                                        "MASTER 1 2 3 4 5 6 7 8\n" ++
                                        "END"
                                      )
        mt `shouldBe` Right ([], pdbWithAllFields)

pdbWithAllFields :: PDB
pdbWithAllFields = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                       , models = V.fromList   [V.fromList [
                                                        V.singleton Atom {atomSerial = 12532, atomName = "HH21", atomAltLoc = ' ', atomResName = "ARG", atomChainID = 'D', atomResSeq = 474, atomICode = ' ', 
                                                                                atomX = 47.457, atomY = -38.007, atomZ = -47.445, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "},
                                                        V.singleton Atom {atomSerial = 12535, atomName = " C1 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ',
                                                                                atomX = 5.791, atomY = -20.194, atomZ = -7.051, atomOccupancy = 1.0, atomTempFactor = 34.66, atomElement = " C", atomCharge = "  "}]
                                                ]
                       , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")
                       , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(OBSLTE, V.fromList [" obslte"]), (SPLIT, V.fromList ["split"]), (CAVEAT, V.fromList [" caveat"]),
                                                                 (COMPND, V.fromList [" compnd"]),(SOURCE, V.fromList [" source"]),(KEYWDS, V.fromList [" keywds"]),
                                                                 (EXPDTA, V.fromList [" expdta1"," expdta2"]),(NUMMDL, V.fromList [" nummdl"]),(MDLTYP, V.fromList [" mdltyp mdltyp"]),
                                                                 (AUTHOR, V.fromList ["  Masha"]),(REVDAT, V.fromList [" revdat"]),(SPRSDE, V.fromList [" sprsde"]),
                                                                 (JRNL, V.fromList [" jrnl"]),(DBREF, V.fromList ["dbref"]), (DBREF1, V.fromList [" dbref1"]), (DBREF2, V.fromList [" dbref2_1"," dbref2_2"]),
                                                                 (SEQADV, V.fromList [" seqadv"]),(SEQRES, V.fromList [" seqres"]),(MODRES, V.fromList [" modres"]),
                                                                 (HET, V.fromList [" het"]), (HETNAM, V.fromList [" hetnam"]), (HETSYN, V.fromList [" hetsyn"]),(FORMUL, V.fromList [" of love"]),(HELIX, V.fromList ["helix"]),
                                                                 (SHEET, V.fromList ["sheet"]),(SSBOND, V.fromList [" ssbond"]),(LINK, V.fromList [" link"]),
                                                                 (CISPEP, V.fromList [" cispep"]),(SITE,V.fromList [" site"]),(CRYST1, V.fromList [" cryst1"]),(MTRIX3, V.fromList [" matrixn n=3"]),
                                                                 (ORIGX1, V.fromList [" origx1 n=1"]),(SCALE2, V.fromList [" sclaen n=2"]),(MASTER, V.fromList [" 1 2 3 4 5 6 7 8"])]
                       }


emptySpecP :: Spec
emptySpecP = describe "empty PDB." $
        it "correctly parses empty pdb" $ do
        let mt = fromTextPDB ""
        mt `shouldBe` Right ([], emptyPdb)

emptyPdb :: PDB
emptyPdb = PDB { title = ""
               , models = V.empty
               , remarks = Data.Map.Strict.empty
               , otherFields = Data.Map.Strict.empty
               }

trashBetweenModelsSpecP :: Spec
trashBetweenModelsSpecP = describe "PDB has trash." $
        it "correctly parses pdb with trash string between models and other field strings" $ do
        let mt = fromTextPDB  . lenghtenLines $ T.pack ( "trash strings 1\n" ++
                                        "HEADER header\n" ++
                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                        "REMARK 1   REFERENCE 1\n" ++
                                        "trash strings 2\n" ++
                                        "REMARK 2   Reference 2_1/1\n" ++
                                        "MODEL  4  \n" ++
                                        "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" ++
                                        "TER    2034      CYS A 214                                                      \n" ++
                                        "ANISOU anisou\n" ++
                                        "ENDMDL \n" ++
                                        "CRYST1 cryst1\n" ++
                                        "trash strings 3\n" ++
                                        "SEQRES seqres\n" ++
                                        "MODEL  5 \n" ++
                                        "ATOM   2035  N   GLU B   1      18.637-691.583  66.852  1.0 118.48           N  \n" ++
                                        "ATOM  12531 HH12 ARG D 474      45.558 -39.551 -49.936  1.00 15.00           H  \n" ++
                                        "ENDMDL \n" ++
                                        "trash strings 4\n" ++
                                        "MODEL  6 \n" ++
                                        "ATOM  12532 HH21 ARG D 474      47.457 -38.007 -47.445  1.00 15.00           H  \n" ++
                                        "TER   12534      ARG D 474                                                      \n" ++
                                        "ATOM  12533 HH22 ARG D 474      47.405 -39.268 -48.629  1.00 15.00           H  \n" ++
                                        "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" ++
                                        "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" ++
                                        "CONECT conect conect\n" ++
                                        "ENDMDL\n" ++
                                        "MASTER 1 2 3 4 5 6 7 8\n" ++
                                        "trash strings 5\n"
                                       )
        mt `shouldBe` Left "There are trash strings between model strings"

pdbWithoutTrash :: PDB
pdbWithoutTrash = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                      , remarks = Data.Map.Strict.fromList [(Just 1, V.singleton "REFERENCE 1"), (Just 2, V.singleton "Reference 2_1/1")]
                      , models = V.fromList [V.fromList [V.fromList [Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS",
                                                                                                    atomChainID = 'A', atomResSeq = 214, atomICode = ' ', atomX = -6.124, atomY = -27.225,
                                                                                                    atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]
                                                                            ],
                                                      V.fromList [V.fromList [Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU",
                                                                                                   atomChainID = 'B', atomResSeq = 1, atomICode = ' ', atomX = 18.637, atomY = -691.583,
                                                                                                   atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "}],
                                                                            V.fromList [Atom {atomSerial = 12531, atomName = "HH12", atomAltLoc = ' ', atomResName = "ARG",
                                                                                                   atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 45.558, atomY = -39.551, atomZ = -49.936,
                                                                                                   atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]
                                                                           ],
                                                      V.fromList [V.fromList [Atom {atomSerial = 12532, atomName = "HH21", atomAltLoc = ' ', atomResName = "ARG",
                                                                                                   atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.457, atomY = -38.007, atomZ = -47.445,
                                                                                                   atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "},
                                                                                                  Atom {atomSerial = 12533, atomName = "HH22", atomAltLoc = ' ', atomResName = "ARG",
                                                                                                   atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.405, atomY = -39.268, atomZ = -48.629,
                                                                                                   atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]
                                                                            ]
                                                        ]
                      , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(SEQRES,V.fromList [" seqres"]),
                                                                (CRYST1,V.fromList [" cryst1"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]
                      }

onlyOneModelSpecP :: Spec
onlyOneModelSpecP = describe "Only One model." $
        it "correctly parses pdb with only one model without other field/title/trash/remarks strings" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "ATOM   2032  OXT CYS A 214      -4.546 -29.673  26.796  1.0 143.51           O  \n" ++
                                         "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" ++
                                         "TER    2034      CYS A 214                                                      \n" ++
                                         "ATOM   2035  N   GLU B   1      18.637 -61.583  66.852  1.0 118.48           N  \n" ++
                                         "TER   12534      ARG D 474                                                      \n" ++
                                         "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" ++
                                         "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" ++
                                         "ATOM   2036  CA  GLU B   1      19.722 -62.606  66.868  1.00 19.77           C  \n"
                                        )
        mt `shouldBe` Right ([], onlyOneModelPDB)

onlyOneModelPDB :: PDB
onlyOneModelPDB = PDB { title = ""
                      , remarks = Data.Map.Strict.empty
                      , models = V.singleton $ V.fromList [ 
                                V.fromList [
                                        Atom {atomSerial = 2032, atomName = " OXT", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ',
                                                atomX = -4.546, atomY = -29.673, atomZ = 26.796, atomOccupancy = 1.0, atomTempFactor = 143.51, atomElement = " O", atomCharge = "  "},
                                        Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ', 
                                                atomX = -6.124, atomY = -27.225, atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}],
                                V.fromList [
                                        Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',
                                                atomX = 18.637, atomY = -61.583, atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "},
                                        Atom {atomSerial = 12535, atomName = " C1 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ',
                                                atomX = 5.791, atomY = -20.194, atomZ = -7.051, atomOccupancy = 1.0, atomTempFactor = 34.66, atomElement = " C", atomCharge = "  "},
                                        Atom {atomSerial = 12538, atomName = " C4 ", atomAltLoc = ' ', atomResName = "NAG", atomChainID = 'B', atomResSeq = 475, atomICode = ' ', 
                                                atomX = 6.943, atomY = -19.507, atomZ = -9.597, atomOccupancy = 1.0, atomTempFactor = 25.87, atomElement = " C", atomCharge = "  "},
                                        Atom {atomSerial = 2036, atomName = " CA ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',
                                                atomX = 19.722, atomY = -62.606, atomZ = 66.868, atomOccupancy = 1.0, atomTempFactor = 19.77, atomElement = " C", atomCharge = "  "}]
                                                ]
                      , otherFields = Data.Map.Strict.empty
                      }

repeatedStringsSpecP :: Spec
repeatedStringsSpecP = describe "PDB with repeated other field strings." $
        it "correctly parses pdb with repeated other field strings (SOURCE)" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" ++
                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                        "COMPND compnd\n" ++
                                        "EXPDTA expdta\n" ++
                                        "AUTHOR  Masha\n" ++
                                        "SOURCE source1\n" ++
                                        "KEYWDS keywds\n" ++
                                        "REVDAT revdat\n" ++
                                        "REMARK 1   REFERENCE 1\n" ++
                                        "SEQRES seqres\n" ++
                                        "CRYST1 cryst1\n" ++
                                        "ORIGX1 origx1 n=1\n" ++
                                        "SOURCE source2\n" ++
                                        "SCALE2 sclaen n=2\n" ++
                                        "MASTER 1 2 3 4 5 6 7 8\n" ++
                                        "SOURCE source3\n" ++
                                        "END"
                                      )
        mt `shouldBe` Right ([], repeatedStringsPDB)

repeatedStringsPDB :: PDB
repeatedStringsPDB = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                         , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")
                         , models = V.empty
                         , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source1", " source2", " source3"]),(KEYWDS,V.fromList[" keywds"]),(EXPDTA,V.fromList[" expdta"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),
                                                                   (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]
                         }

emptyRemarkSpecP :: Spec
emptyRemarkSpecP = describe "PDB with repeated remark strings without code." $
        it "correctly parses pdb with repeated remark strings without code" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "REMARK 111 remark111_1/2\n" ++
                                        "COMPND compnd\n" ++
                                        "SOURCE source1\n" ++
                                        "KEYWDS keywds\n" ++
                                        "REMARK 111 remark111_2/2\n" ++
                                        "REVDAT revdat\n" ++
                                        "REMARK 2   remark2_1/1\n" ++
                                        "SOURCE source2\n" ++
                                        "REMARK     empty remark 1/2\n" ++
                                        "END    \n" ++
                                        "REMARK     empty remark 2/2\n" ++
                                        "SCALE2 sclaen n=2\n"
                                      )
        mt `shouldBe` Right ([], pdbWithEmptyRemarks)

pdbWithEmptyRemarks :: PDB
pdbWithEmptyRemarks = PDB { title = ""
                         , remarks = Data.Map.Strict.fromList [(Just 2, V.fromList ["remark2_1/1"]), (Just 111, V.fromList ["remark111_1/2", "remark111_2/2"]), (Nothing, V.fromList ["empty remark 1/2", "empty remark 2/2"])]
                         , models = V.empty
                         , otherFields = Data.Map.Strict.fromList [(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source1", " source2"]),
                                                                    (KEYWDS,V.fromList[" keywds"]), (REVDAT,V.fromList[" revdat"]), (SCALE2,V.fromList [" sclaen n=2"])]
                         }

emptyModelSpecP :: Spec
emptyModelSpecP = describe "PDB with one empty model." $
        it "correctly parses pdb with one model without strings inside" $ do
        let mt = fromTextPDB . lenghtenLines $ T.pack ( "trash strings 1\n" ++
                                        "HEADER header\n" ++
                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" ++
                                        "REMARK 1   REFERENCE 1\n" ++
                                        "trash strings 2\n" ++
                                        "REMARK 2   Reference 2_1/1\n" ++
                                        "MODEL  4  \n" ++
                                        "ENDMDL\n" ++
                                        "MASTER 1 2 3 4 5 6 7 8\n" ++
                                        "trash strings 5\n"
                                      )
        mt `shouldBe` Right ([], pdbEmptyModel)


pdbEmptyModel :: PDB
pdbEmptyModel = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"
                    , remarks = Data.Map.Strict.fromList [(Just 1, V.singleton "REFERENCE 1"), (Just 2, V.singleton "Reference 2_1/1")]
                    , models = V.empty
                    , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),
                                                              (MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]
                    }

lenghtenLines :: Text -> Text
lenghtenLines text = longLinedText
   where
       textLines = T.lines text
       longTextLines = changeLine <$> textLines
       desiredLength = 80  -- cause it is max length in standart pdb file
       longLinedText = T.intercalate "\n" longTextLines

       changeLine :: Text -> Text
       changeLine line | T.length line > desiredLength = T.take desiredLength line
                       | T.length line < desiredLength = line <> T.replicate spacesCount " "
                       | otherwise = line
            where
                    spacesCount = desiredLength - T.length line