diff --git a/cobot-io.cabal b/cobot-io.cabal
--- a/cobot-io.cabal
+++ b/cobot-io.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: b952542571739fbdf02508e11db35e6059993b82d37e4f7e27825e5c83fedfb1
+-- hash: 5649e9d10f32f2f4349434935511b7ca5b206829754168dd6c74817d422c0e91
 
 name:           cobot-io
-version:        0.1.2.0
+version:        0.1.2.1
 synopsis:       Biological data file formats and IO
 description:    Please see the README on GitHub at <https://github.com/less-wrong/cobot-io#readme>
 category:       Bio
@@ -40,6 +40,9 @@
       Bio.GB.Parser
       Bio.GB.Type
       Bio.GB.Writer
+      Bio.MAE
+      Bio.MAE.Parser
+      Bio.MAE.Type
       Bio.MMTF
       Bio.MMTF.Decode
       Bio.MMTF.Decode.Codec
@@ -94,6 +97,7 @@
       FastaWriterSpec
       GBParserSpec
       GBWriterSpec
+      MAEParserSpec
       MMTFSpec
       SequenceSpec
       UniprotSpec
@@ -103,7 +107,7 @@
   default-extensions: OverloadedStrings TypeFamilies
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
   build-depends:
-      QuickCheck >=2.9.2 && <2.13
+      QuickCheck >=2.9.2 && <2.14
     , array >=0.5 && <0.6
     , attoparsec >=0.10 && <0.14
     , base >=4.7 && <5
@@ -113,7 +117,8 @@
     , containers >=0.5.7.1 && <0.7
     , data-msgpack >=0.0.9 && <0.1
     , deepseq >=1.4 && <1.5
-    , hspec >=2.4.1 && <2.7
+    , directory
+    , hspec >=2.4.1 && <2.8
     , http-conduit >=2.3 && <2.4
     , hyraxAbif >=0.2.3.15 && <0.2.4.0
     , lens >=4.16 && <5.0
diff --git a/src/Bio/GB.hs b/src/Bio/GB.hs
--- a/src/Bio/GB.hs
+++ b/src/Bio/GB.hs
@@ -13,8 +13,7 @@
 import           Control.Monad.IO.Class (MonadIO, liftIO)
 import           Data.Attoparsec.Text   (parseOnly)
 import           Data.Bifunctor         (first)
-import           Data.Text              (Text)
-import           Data.Text              (pack)
+import           Data.Text              (Text, pack)
 import qualified Data.Text.IO           as TIO (readFile, writeFile)
 
 -- | Reads 'GenBankSequence' from givem file.
diff --git a/src/Bio/MAE.hs b/src/Bio/MAE.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MAE.hs
@@ -0,0 +1,24 @@
+module Bio.MAE
+  ( module T
+  , fromFile
+  , fromText
+  , maeP
+  ) where
+
+import           Bio.MAE.Parser
+import           Bio.MAE.Type           as T
+import           Control.Monad.IO.Class (MonadIO, liftIO)
+import           Data.Attoparsec.Text   (parseOnly)
+import           Data.Bifunctor         (first)
+import           Data.Text              (Text, pack)
+import qualified Data.Text.IO           as TIO (readFile)
+
+-- | Reads 'Mae' from givem file.
+--
+fromFile :: MonadIO m => FilePath -> m Mae
+fromFile f = liftIO (TIO.readFile f) >>= either fail pure . parseOnly maeP
+
+-- | Reads 'Mae' from 'Text'.
+--
+fromText :: Text -> Either Text Mae
+fromText = first pack . parseOnly maeP
diff --git a/src/Bio/MAE/Parser.hs b/src/Bio/MAE/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MAE/Parser.hs
@@ -0,0 +1,156 @@
+{-# LANGUAGE TupleSections #-}
+
+module Bio.MAE.Parser
+  ( maeP
+  , versionP
+  , blockP
+  , tableP
+  ) where
+
+import           Bio.MAE.Type         (Block (..), Mae (..), MaeValue (..),
+                                       Table (..))
+import           Control.Applicative  ((<|>))
+import           Control.Monad        (replicateM, when, zipWithM)
+import           Data.Attoparsec.Text (Parser, char, decimal, endOfInput,
+                                       endOfLine, many', many1', string,
+                                       takeWhile, takeWhile1)
+import           Data.Char            (isSpace)
+import           Data.List            (transpose)
+import           Data.Map.Strict      (Map)
+import qualified Data.Map.Strict      as M (fromList)
+import           Data.Text            (Text)
+import qualified Data.Text            as T (pack, uncons)
+import qualified Data.Text.Read       as TR (decimal, rational, signed)
+import           Prelude              hiding (takeWhile)
+
+maeP :: Parser Mae
+maeP = Mae <$> versionP
+           <*> many' blockP
+           <*  endOfInput
+
+versionP :: Parser Text
+versionP = inBrackets $  lineP
+                      *> delimiterP
+                      *> lineP
+
+blockP :: Parser Block
+blockP = uncurry <$> (Block <$> anyStringP <* many' oneSpaceP)
+                 <*> inBrackets ((,) <$> fieldsP <*> many' tableP)
+  where
+    fieldsP :: Parser (Map Text MaeValue)
+    fieldsP = do
+        fieldNames  <- upToDelimiterP lineP
+        fieldMaeValues <- replicateM (length fieldNames) lineP
+
+        M.fromList <$> zipWithM (\k v -> (k,) <$> textToMaeValue k v) fieldNames fieldMaeValues
+
+textToMaeValue :: Text -> Text -> Parser MaeValue
+textToMaeValue k v = if v == absentMaeValue then pure Absent else
+    case T.uncons k of
+        Just (c, _) -> getMaeValueReader c v
+        _           -> fail "Absent field name."
+  where
+    absentMaeValue :: Text
+    absentMaeValue = "<>"
+
+    getMaeValueReader :: Char -> Text -> Parser MaeValue
+    getMaeValueReader 'i' = textToIntMaeValueReader
+    getMaeValueReader 'r' = textToRealMaeValueReader
+    getMaeValueReader 'b' = textToBoolMaeValueReader
+    getMaeValueReader 's' = textToStringMaeValueReader
+    getMaeValueReader _   = const $ fail "Unknown value type."
+
+    textToIntMaeValueReader :: Text -> Parser MaeValue
+    textToIntMaeValueReader = either fail (pure . IntMaeValue . fst) . TR.signed TR.decimal
+
+    textToRealMaeValueReader :: Text -> Parser MaeValue
+    textToRealMaeValueReader = either fail (pure . RealMaeValue . fst) . TR.signed TR.rational
+
+    textToBoolMaeValueReader :: Text -> Parser MaeValue
+    textToBoolMaeValueReader t =
+        case t of
+            "0" -> pure $ BoolMaeValue False
+            "1" -> pure $ BoolMaeValue True
+            _   -> fail "Can't parse bool value."
+
+    textToStringMaeValueReader :: Text -> Parser MaeValue
+    textToStringMaeValueReader = pure . StringMaeValue
+
+tableP :: Parser Table
+tableP = do
+    name            <- many' oneSpaceP *> takeWhile1 (/= leftSquareBracket)
+    numberOfEntries <- char leftSquareBracket *> decimal <* char rightSquareBracket
+
+    _ <- many' oneSpaceP
+
+    contents <- inBrackets $ do
+        fieldNames  <- upToDelimiterP lineP
+        let readers = fmap textToMaeValue fieldNames
+        entries     <- replicateM numberOfEntries $ entryP readers
+
+        delimiterP
+
+        pure $ M.fromList $ zip fieldNames $ transpose entries
+
+    pure $ Table name contents
+  where
+    leftSquareBracket :: Char
+    leftSquareBracket = '['
+
+    rightSquareBracket :: Char
+    rightSquareBracket = ']'
+
+    entryP :: [Text -> Parser MaeValue] -> Parser [MaeValue]
+    entryP readers = do
+        valuesT <- many1' (many' oneSpaceP *> valueTP <* many' oneSpaceP) <* tillEndOfLine
+        when (length readers /= length valuesT - 1) $ fail "Wrong number of values in an entry."
+        zipWithM ($) readers $ drop 1 valuesT
+
+--------------------------------------------------------------------------------
+-- Utility functions.
+--------------------------------------------------------------------------------
+
+inBrackets :: Parser a -> Parser a
+inBrackets p =  char leftBracket *> many1' tillEndOfLine
+             *> p
+             <* many' oneSpaceP <* char rightBracket <* many1' tillEndOfLine
+  where
+    leftBracket :: Char
+    leftBracket = '{'
+
+    rightBracket :: Char
+    rightBracket = '}'
+
+delimiterP :: Parser ()
+delimiterP = many' oneSpaceP *> string delimiter *> tillEndOfLine
+  where
+    delimiter :: Text
+    delimiter = ":::"
+
+upToDelimiterP :: Parser a -> Parser [a]
+upToDelimiterP p = ([] <$ delimiterP) <|> ((:) <$> p <*> upToDelimiterP p)
+
+oneSpaceP :: Parser Char
+oneSpaceP = char ' '
+
+anyStringP :: Parser Text
+anyStringP = takeWhile1 (not . isSpace)
+
+valueTP :: Parser Text
+valueTP  =  ((<>) <$> string quoteT <*> ((<>) <$> takeWhile (/= quote) <*> string quoteT))
+        <|> anyStringP
+  where
+    quote :: Char
+    quote = '\"'
+
+    quoteT :: Text
+    quoteT = T.pack $ pure quote
+
+commentaryP :: Parser ()
+commentaryP = () <$ many' (many' oneSpaceP *> char '#' *> takeWhile (`notElem` ['\n', '\r']) *> endOfLine)
+
+lineP :: Parser Text
+lineP = commentaryP *> many' oneSpaceP *> valueTP <* tillEndOfLine <* commentaryP
+
+tillEndOfLine :: Parser ()
+tillEndOfLine = () <$ many' oneSpaceP <* endOfLine
diff --git a/src/Bio/MAE/Type.hs b/src/Bio/MAE/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MAE/Type.hs
@@ -0,0 +1,60 @@
+module Bio.MAE.Type
+  ( Mae (..)
+  , Block (..)
+  , Table (..)
+  , MaeValue (..)
+  , FromMaeValue (..)
+  ) where
+
+import           Data.Map.Strict (Map)
+import           Data.Maybe      (fromJust)
+import           Data.Text       (Text)
+
+data Mae = Mae { version :: Text
+               , blocks  :: [Block]
+               }
+  deriving (Eq, Show)
+
+data Block = Block { blockName :: Text
+                   , fields    :: Map Text MaeValue
+                   , tables    :: [Table]
+                   }
+  deriving (Eq, Show)
+
+data Table = Table { tableName :: Text
+                   , contents  :: Map Text [MaeValue]
+                   }
+  deriving (Eq, Show)                   
+
+data MaeValue = IntMaeValue Int
+              | RealMaeValue Float
+              | StringMaeValue Text
+              | BoolMaeValue Bool
+              | Absent
+  deriving (Eq, Show)
+
+class FromMaeValue a where
+    fromMaeValue :: MaeValue -> Maybe a
+
+    unsafeFromMaeValue :: MaeValue -> a
+    unsafeFromMaeValue = fromJust . fromMaeValue
+
+instance FromMaeValue Int where
+    fromMaeValue :: MaeValue -> Maybe Int
+    fromMaeValue (IntMaeValue i) = Just i
+    fromMaeValue _               = Nothing
+
+instance FromMaeValue Float where
+    fromMaeValue :: MaeValue -> Maybe Float
+    fromMaeValue (RealMaeValue f) = Just f
+    fromMaeValue _                = Nothing
+
+instance FromMaeValue Bool where
+    fromMaeValue :: MaeValue -> Maybe Bool
+    fromMaeValue (BoolMaeValue b) = Just b
+    fromMaeValue _                = Nothing
+
+instance FromMaeValue Text where
+    fromMaeValue :: MaeValue -> Maybe Text
+    fromMaeValue (StringMaeValue t) = Just t
+    fromMaeValue _                  = Nothing
diff --git a/src/Bio/MMTF.hs b/src/Bio/MMTF.hs
--- a/src/Bio/MMTF.hs
+++ b/src/Bio/MMTF.hs
@@ -5,23 +5,23 @@
   , fetch
   ) where
 
+import           Bio.MMTF.Decode        (l2v)
 import           Bio.MMTF.MessagePack   ()
-import           Bio.MMTF.Type   hiding ( IArray )
-import           Bio.MMTF.Decode        ( l2a )
+import           Bio.MMTF.Type
 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 (..) )
+import           Control.Monad.IO.Class (MonadIO)
+import           Data.Bifunctor         (Bifunctor (..))
+import           Data.ByteString.Lazy   (ByteString)
+import           Data.Int               (Int32)
+import           Data.List              (mapAccumL, zip3, zip4)
+import           Data.MessagePack       (unpack)
+import           Data.Monoid            ((<>))
+import           Data.String            (IsString (..))
+import           Data.Text              (Text)
+import           Data.Vector            (Vector, empty, toList, (!))
+import           Linear.V3              (V3 (..))
+import           Network.HTTP.Simple    (getResponseBody, httpLBS)
 
 -- | Decodes a 'ByteString' to 'MMTF'
 --
@@ -32,17 +32,17 @@
 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) 
+                 decode (getResponseBody resp)
 
 instance StructureModels MMTF where
-    modelsOf m = l2a (Model . l2a <$> zipWith (zipWith Chain) chainNames chainResis)
+    modelsOf m = l2v (flip Model empty . l2v <$> zipWith (zipWith Chain) chainNames chainResis)
       where
-        chainsCnts = fromIntegral <$> elems (chainsPerModel (model m))
-        groupsCnts = fromIntegral <$> elems (groupsPerChain (chain m))
+        chainsCnts = fromIntegral <$> toList (chainsPerModel (model m))
+        groupsCnts = fromIntegral <$> toList (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
+        chainNames = cutter chainsCnts (toList $ chainNameList $ chain m)
+        chainResis = fmap (fmap (l2v . fmap mkResidue)) groups
 
         getGroups :: (Int, Int) -> Int -> ((Int, Int), [(GroupType, SecondaryStructure, [Atom])])
         getGroups (chOffset, atOffset) sz = let chEnd        = chOffset + sz
@@ -56,31 +56,38 @@
                                             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
+        getAtoms offset gt = let cl  = fmap fromIntegral . toList . gtFormalChargeList $ gt
+                                 nl  = toList . gtAtomNameList $ gt
+                                 el  = toList . 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)
+        mkResidue (gt, ss, atoms) = Residue (gtGroupName gt) (l2v 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
+        mkBonds :: Vector (Int32, Int32) -> Vector Int32 -> Vector (Bond LocalID)
+        mkBonds bal bol = let ball = bimap (LocalID . fromIntegral) (LocalID . fromIntegral) <$> toList bal
+                              boll = fromIntegral <$> toList bol
                               res  = zipWith (\(f, t) o -> Bond f t o) ball boll
-                          in  l2a res
+                          in  l2v res
 
         mkAtom :: (Int, Text, Text, Int) -> Atom
-        mkAtom (fc, n, e, idx) = let x = xCoordList (atom m)
+        mkAtom (fc, n, e, idx) = let i = atomIdList (atom m)
+                                     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)
+                                 in  Atom (GlobalID $ fromIntegral (i ! idx))
+                                           n
+                                           e
+                                           (V3 (x ! idx) (y ! idx) (z ! idx))
+                                           fc
+                                           (b ! idx)
+                                           (o ! idx)
 
         cutter :: [Int] -> [a] -> [[a]]
         cutter []     []    = []
diff --git a/src/Bio/MMTF/Decode.hs b/src/Bio/MMTF/Decode.hs
--- a/src/Bio/MMTF/Decode.hs
+++ b/src/Bio/MMTF/Decode.hs
@@ -6,11 +6,11 @@
 
 import           Control.Monad               ((>=>))
 import           Data.ByteString.Lazy        (empty)
+import           Data.Char                   (ord)
 import           Data.Map.Strict             (Map)
 import           Data.MessagePack            (Object)
 import           Data.Text                   (Text, pack)
-import           Data.Char                   (ord)
-import           Data.Array                  (listArray)
+import           Data.Vector                 (Vector, fromList)
 
 -- | Parses format data from ObjectMap
 --
@@ -22,7 +22,7 @@
 -- | Parses model data from ObjectMap
 --
 modelData :: Monad m => Map Text Object -> m ModelData
-modelData mp = ModelData . l2a <$> atP mp "chainsPerModel" asIntList
+modelData mp = ModelData . l2v <$> atP mp "chainsPerModel" asIntList
 
 -- | Parses chain data from ObjectMap
 --
@@ -30,7 +30,7 @@
 chainData mp = do gpc <- atP mp "groupsPerChain" asIntList
                   cil <- codec5 . parseBinary <$> atP   mp "chainIdList"   asBinary
                   cnl <- codec5 . parseBinary <$> atPMD mp "chainNameList" asBinary empty
-                  pure $ ChainData (l2a gpc) (l2a cil) (l2a cnl)
+                  pure $ ChainData (l2v gpc) (l2v cil) (l2v cnl)
 
 -- | Parses atom data from ObjectMap
 --
@@ -42,7 +42,7 @@
                  ycl' <-      codec10 . parseBinary <$> atP   mp "yCoordList"    asBinary
                  zcl' <-      codec10 . parseBinary <$> atP   mp "zCoordList"    asBinary
                  ol' <-        codec9 . parseBinary <$> atPMD mp "occupancyList" asBinary empty
-                 pure $ AtomData (l2a ail') (l2a all') (l2a bfl') (l2a xcl') (l2a ycl') (l2a zcl') (l2a ol')
+                 pure $ AtomData (l2v ail') (l2v all') (l2v bfl') (l2v xcl') (l2v ycl') (l2v zcl') (l2v ol')
 
 -- | Parses group data from ObjectMap
 --
@@ -53,7 +53,7 @@
                   ssl' <- fmap ssDec . codec2 . parseBinary <$> atPMD mp "secStructList"     asBinary empty
                   icl' <-        c2s . codec6 . parseBinary <$> atPMD mp "insCodeList"       asBinary empty
                   sil' <-              codec8 . parseBinary <$> atPMD mp "sequenceIndexList" asBinary empty
-                  pure $ GroupData (l2a gl') (l2a gtl') (l2a gil') (l2a ssl') (l2a icl') (l2a sil')
+                  pure $ GroupData (l2v gl') (l2v gtl') (l2v gil') (l2v ssl') (l2v icl') (l2v sil')
 
 -- | Parses group type from ObjectMap
 --
@@ -66,7 +66,7 @@
                   gn'  <-          atP mp "groupName"        asStr
                   slc' <-          atP mp "singleLetterCode" asChar
                   cct' <-          atP mp "chemCompType"     asStr
-                  pure $ GroupType (l2a fcl') (l2a anl') (l2a el') (l2a bal') (l2a bol') gn' slc' cct'
+                  pure $ GroupType (l2v fcl') (l2v anl') (l2v el') (l2v bal') (l2v bol') gn' slc' cct'
 
 -- | Parses structure data from ObjectMap
 --
@@ -92,23 +92,23 @@
                       btl' <-  l2pl . codec4 . parseBinary <$> atPMD mp "bondAtomList"        asBinary empty
                       bol' <-         codec2 . parseBinary <$> atPMD mp "bondOrderList"       asBinary empty
                       pure $ StructureData ttl' sid' dd' rd' nb' na'
-                                          ng' nc' nm' sg' uc' (l2a nol')
-                                           (l2a bal') (l2a el') res' rf'
-                                           rw' (l2a em') (l2a btl') (l2a bol')
+                                          ng' nc' nm' sg' uc' (l2v nol')
+                                           (l2v bal') (l2v el') res' rf'
+                                           rw' (l2v em') (l2v btl') (l2v bol')
 
 -- | Parses bio assembly data from ObjectMap
 --
 bioAssembly :: Monad m => Map Text Object -> m Assembly
 bioAssembly mp = do nme' <- atP mp "name"          asStr
                     tlt' <- atP mp "transformList" asObjectList >>= traverse (transformObjectMap >=> transform)
-                    pure $ Assembly (l2a tlt') nme'
+                    pure $ Assembly (l2v tlt') nme'
 
 -- | Parses transform data from ObjectMap
 --
 transform :: Monad m => Map Text Object -> m Transform
 transform mp = do cil' <- atP mp "chainIndexList" asIntList
                   mtx' <- atP mp "matrix"         asFloatList >>= m44Dec
-                  pure $ Transform (l2a cil') mtx'
+                  pure $ Transform (l2v cil') mtx'
 
 -- | Parses entity data from ObjectMap
 --
@@ -117,7 +117,7 @@
                dsc' <- atP mp "description"    asStr
                tpe' <- atP mp "type"           asStr
                sqc' <- atP mp "sequence"       asStr
-               pure $ Entity (l2a cil') dsc' tpe' sqc'
+               pure $ Entity (l2v cil') dsc' tpe' sqc'
 
 -- Helper functions
 
@@ -130,8 +130,8 @@
 
 -- | Converst list to an array
 --
-l2a :: [a] -> IArray a
-l2a lst = listArray (0, length lst - 1) lst
+l2v :: [a] -> Vector a
+l2v = fromList
 
 -- | List to list of pairs
 --
diff --git a/src/Bio/MMTF/Type.hs b/src/Bio/MMTF/Type.hs
--- a/src/Bio/MMTF/Type.hs
+++ b/src/Bio/MMTF/Type.hs
@@ -1,16 +1,12 @@
 module Bio.MMTF.Type where
 
-import           Data.Int        ( Int32, Int8 )
-import           Data.Text       ( Text )
-import           Data.Array      ( Array )
-import           GHC.Generics    ( Generic )
-import           Control.DeepSeq ( NFData (..) )
-
-import           Bio.Structure   ( SecondaryStructure )
+import           Control.DeepSeq (NFData (..))
+import           Data.Int        (Int32, Int8)
+import           Data.Text       (Text)
+import           Data.Vector     (Vector)
+import           GHC.Generics    (Generic)
 
--- | All arrays are int-indexed
---
-type IArray a = Array Int a
+import           Bio.Structure   (SecondaryStructure)
 
 -- | Transformation matrix
 --
@@ -33,21 +29,21 @@
 
 -- | Transform data
 --
-data Transform = Transform { chainIndexList :: !(IArray Int32) -- ^ indices into the 'chainIdList' and 'chainNameList' fields
+data Transform = Transform { chainIndexList :: !(Vector Int32) -- ^ indices into the 'chainIdList' and 'chainNameList' fields
                            , matrix         :: !M44            -- ^ 4x4 transformation matrix
                            }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Assembly data
 --
-data Assembly = Assembly { transformList :: !(IArray Transform) -- ^ List of transform objects
+data Assembly = Assembly { transformList :: !(Vector Transform) -- ^ List of transform objects
                          , assemblyName  :: !Text               -- ^ Name of the biological assembly
                          }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Entity data
 --
-data Entity = Entity { entityChainIndexList :: !(IArray Int32) -- ^ indices into the 'chainIdList' and 'chainNameList' fields
+data Entity = Entity { entityChainIndexList :: !(Vector Int32) -- ^ indices into the 'chainIdList' and 'chainNameList' fields
                      , entityDescription    :: !Text           -- ^ Description of the entity
                      , entityType           :: !Text           -- ^ Name of the entity type
                      , entitySequence       :: !Text           -- ^ Sequence of the full construct in one-letter-code
@@ -56,11 +52,11 @@
 
 -- | Group type data
 --
-data GroupType = GroupType { gtFormalChargeList :: !(IArray Int32)          -- ^ List of formal charges
-                           , gtAtomNameList     :: !(IArray Text)           -- ^ List of atom names
-                           , gtElementList      :: !(IArray Text)           -- ^ List of elements
-                           , gtBondAtomList     :: !(IArray (Int32, Int32)) -- ^ List of bonded atom indices
-                           , gtBondOrderList    :: !(IArray Int32)          -- ^ List of bond orders
+data GroupType = GroupType { gtFormalChargeList :: !(Vector Int32)          -- ^ List of formal charges
+                           , gtAtomNameList     :: !(Vector Text)           -- ^ List of atom names
+                           , gtElementList      :: !(Vector Text)           -- ^ List of elements
+                           , gtBondAtomList     :: !(Vector (Int32, Int32)) -- ^ List of bonded atom indices
+                           , gtBondOrderList    :: !(Vector Int32)          -- ^ List of bond orders
                            , gtGroupName        :: !Text                    -- ^ The name of the group
                            , gtSingleLetterCode :: !Char                    -- ^ The single letter code
                            , gtChemCompType     :: !Text                    -- ^ The chemical component type
@@ -87,52 +83,52 @@
                                    , numModels           :: !Int32                   -- ^ The overall number of models in the structure
                                    , spaceGroup          :: !Text                    -- ^ The Hermann-Mauguin space-group symbol
                                    , unitCell            :: !(Maybe UnitCell)        -- ^ Array of six values defining the unit cell
-                                   , ncsOperatorList     :: !(IArray M44)            -- ^ List of 4x4 transformation matrices (transformation matrices describe noncrystallographic symmetry operations needed to create all molecules in the unit cell)
-                                   , bioAssemblyList     :: !(IArray Assembly)       -- ^ List of instructions on how to transform coordinates for an array of chains to create (biological) assemblies
-                                   , entityList          :: !(IArray Entity)         -- ^ List of unique molecular entities within the structure
+                                   , ncsOperatorList     :: !(Vector M44)            -- ^ List of 4x4 transformation matrices (transformation matrices describe noncrystallographic symmetry operations needed to create all molecules in the unit cell)
+                                   , bioAssemblyList     :: !(Vector Assembly)       -- ^ List of instructions on how to transform coordinates for an array of chains to create (biological) assemblies
+                                   , entityList          :: !(Vector Entity)         -- ^ List of unique molecular entities within the structure
                                    , resolution          :: !(Maybe Float)           -- ^ The experimental resolution in Angstrom
                                    , rFree               :: !(Maybe Float)           -- ^ The R-free value
                                    , rWork               :: !(Maybe Float)           -- ^ The R-work value
-                                   , experimentalMethods :: !(IArray Text)           -- ^ List of experimental methods employed for structure determination
-                                   , bondAtomList        :: !(IArray (Int32, Int32)) -- ^ Pairs of values represent indices of covalently bonded atoms [binary (type 4)]
-                                   , bondOrderList       :: !(IArray Int8)           -- ^ List of bond orders for bonds in 'bondAtomList' [binary (type 2)]
+                                   , experimentalMethods :: !(Vector Text)           -- ^ List of experimental methods employed for structure determination
+                                   , bondAtomList        :: !(Vector (Int32, Int32)) -- ^ Pairs of values represent indices of covalently bonded atoms [binary (type 4)]
+                                   , bondOrderList       :: !(Vector Int8)           -- ^ List of bond orders for bonds in 'bondAtomList' [binary (type 2)]
                                    }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Models data
 --
-data ModelData = ModelData { chainsPerModel :: !(IArray Int32) -- ^ List of the number of chains in each model
+data ModelData = ModelData { chainsPerModel :: !(Vector Int32) -- ^ List of the number of chains in each model
                            }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Chains data
 --
-data ChainData = ChainData { groupsPerChain :: !(IArray Int32)       -- ^ List of the number of groups (aka residues) in each chain
-                           , chainIdList    :: !(IArray Text)        -- ^ List of chain IDs [binary (type 5)]
-                           , chainNameList  :: !(IArray Text)        -- ^ List of chain names [binary (type 5)]
+data ChainData = ChainData { groupsPerChain :: !(Vector Int32)       -- ^ List of the number of groups (aka residues) in each chain
+                           , chainIdList    :: !(Vector Text)        -- ^ List of chain IDs [binary (type 5)]
+                           , chainNameList  :: !(Vector Text)        -- ^ List of chain names [binary (type 5)]
                            }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Groups data
 --
-data GroupData = GroupData { groupList         :: !(IArray GroupType)              -- ^ List of groupType objects
-                           , groupTypeList     :: !(IArray Int32)                  -- ^ List of pointers to 'groupType' entries in 'groupList' by their keys [binary (type 4)]
-                           , groupIdList       :: !(IArray Int32)                  -- ^ List of group (residue) numbers [binary (type 8)]
-                           , secStructList     :: !(IArray SecondaryStructure)     -- ^ List of secondary structure assignments [binary (type 2)]
-                           , insCodeList       :: !(IArray Text)                   -- ^ List of insertion codes, one for each group (residue) [binary (type 6)]
-                           , sequenceIndexList :: !(IArray Int32)                  -- ^ List of indices that point into the sequence property of an entity object in the 'entityList' field that is associated with the chain the group belongs to [binary (type 8)]
+data GroupData = GroupData { groupList         :: !(Vector GroupType)              -- ^ List of groupType objects
+                           , groupTypeList     :: !(Vector Int32)                  -- ^ List of pointers to 'groupType' entries in 'groupList' by their keys [binary (type 4)]
+                           , groupIdList       :: !(Vector Int32)                  -- ^ List of group (residue) numbers [binary (type 8)]
+                           , secStructList     :: !(Vector SecondaryStructure)     -- ^ List of secondary structure assignments [binary (type 2)]
+                           , insCodeList       :: !(Vector Text)                   -- ^ List of insertion codes, one for each group (residue) [binary (type 6)]
+                           , sequenceIndexList :: !(Vector Int32)                  -- ^ List of indices that point into the sequence property of an entity object in the 'entityList' field that is associated with the chain the group belongs to [binary (type 8)]
                            }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Atoms data
 --
-data AtomData = AtomData { atomIdList    :: !(IArray Int32)        -- ^ List of atom serial numbers [binary (type 8)]
-                         , altLocList    :: !(IArray Text)         -- ^ List of alternate location labels, one for each atom [binary (type 6)]
-                         , bFactorList   :: !(IArray Float)        -- ^ List of atom B-factors in in A^2, one for each atom [binary (type 10)]
-                         , xCoordList    :: !(IArray Float)        -- ^ List of x atom coordinates in A, one for each atom [binary (type 10)]
-                         , yCoordList    :: !(IArray Float)        -- ^ List of y atom coordinates in A, one for each atom [binary (type 10)]
-                         , zCoordList    :: !(IArray Float)        -- ^ List of z atom coordinates in A, one for each atom [binary (type 10)]
-                         , occupancyList :: !(IArray Float)        -- ^ List of atom occupancies, one for each atom [binary (type 9)]
+data AtomData = AtomData { atomIdList    :: !(Vector Int32)        -- ^ List of atom serial numbers [binary (type 8)]
+                         , altLocList    :: !(Vector Text)         -- ^ List of alternate location labels, one for each atom [binary (type 6)]
+                         , bFactorList   :: !(Vector Float)        -- ^ List of atom B-factors in in A^2, one for each atom [binary (type 10)]
+                         , xCoordList    :: !(Vector Float)        -- ^ List of x atom coordinates in A, one for each atom [binary (type 10)]
+                         , yCoordList    :: !(Vector Float)        -- ^ List of y atom coordinates in A, one for each atom [binary (type 10)]
+                         , zCoordList    :: !(Vector Float)        -- ^ List of z atom coordinates in A, one for each atom [binary (type 10)]
+                         , occupancyList :: !(Vector Float)        -- ^ List of atom occupancies, one for each atom [binary (type 9)]
                          }
   deriving (Show, Eq, Generic, NFData)
 
diff --git a/src/Bio/PDB.hs b/src/Bio/PDB.hs
--- a/src/Bio/PDB.hs
+++ b/src/Bio/PDB.hs
@@ -1,5 +1,61 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 module Bio.PDB
   (
-
   ) where
 
+import qualified Bio.PDB.Type  as PDB
+import           Bio.Structure
+
+import           Control.Arrow ((&&&))
+import           Data.Coerce   (coerce)
+import           Data.Foldable (Foldable (..))
+import           Data.Text     as T (Text, singleton, unpack)
+import qualified Data.Vector   as V
+import           Linear.V3     (V3 (..))
+
+instance StructureModels PDB.PDB where
+    modelsOf PDB.PDB {..} = fmap mkModel models
+      where
+        mkModel :: PDB.Model -> Model
+        mkModel = flip Model V.empty . fmap mkChain
+
+        mkChain :: PDB.Chain -> Chain
+        mkChain = uncurry Chain . (mkChainName &&& mkChainResidues)
+
+        mkChainName :: PDB.Chain -> Text
+        mkChainName = T.singleton . PDB.atomChainID . safeFirstAtom
+
+        mkChainResidues :: PDB.Chain -> V.Vector Residue
+        mkChainResidues = V.fromList . fmap mkResidue . flip groupByResidue [] . pure . toList
+
+        -- can be rewritten with sortOn and groupBy
+        groupByResidue :: [[PDB.Atom]] -> [PDB.Atom] -> [[PDB.Atom]]
+        groupByResidue res []       = res
+        groupByResidue [] (x : xs)  = groupByResidue [[x]] xs
+        groupByResidue res@(lastList : resultTail) (x : xs)
+          | (PDB.atomResSeq x, PDB.atomICode x) == (PDB.atomResSeq (head lastList), PDB.atomICode (head lastList))
+                                              = groupByResidue ((x : lastList) : resultTail) xs
+          | otherwise                         = groupByResidue ([x] : res) xs
+
+        safeFirstAtom :: V.Vector PDB.Atom -> PDB.Atom
+        safeFirstAtom arr | V.length arr > 0 = arr V.! 0
+                          | otherwise        = error "Could not pick first atom"
+
+
+        mkResidue :: [PDB.Atom] -> Residue
+        mkResidue []    = error "Cound not make residue from empty list"
+        mkResidue atoms = Residue (PDB.atomResName . head $ atoms)
+                                  (V.fromList $ mkAtom <$> atoms)
+                                  V.empty   -- now we do not read bonds
+                                  Undefined -- now we do not read secondary structure
+                                  ""        -- chemical component type?!
+
+
+        mkAtom :: PDB.Atom -> Atom
+        mkAtom PDB.Atom{..} = Atom (coerce atomSerial)
+                                   atomName
+                                   atomElement
+                                   (V3 atomX atomY atomZ)
+                                   (read $ T.unpack atomCharge)
+                                   atomTempFactor
+                                   atomOccupancy
diff --git a/src/Bio/PDB/Type.hs b/src/Bio/PDB/Type.hs
--- a/src/Bio/PDB/Type.hs
+++ b/src/Bio/PDB/Type.hs
@@ -1,125 +1,88 @@
-{-# LANGUAGE DuplicateRecordFields #-}
 module Bio.PDB.Type where
 
-import           Data.Text                      ( Text )
-import           Data.Array                     ( Array )
-
-data Header = Header { classification :: Text
-                     , depDate        :: Text
-                     , idCode         :: Text
-                     }
-
-data Obsoleted = Obsoleted { repDate      :: Text
-                           , idCode       :: Text
-                           , rIdCode      :: Array Int Text
-                           }
-
-data Caveat = Caveat { idCode       :: Text
-                     , comment      :: Text
-                     }
-
-data Compound = Compound { molId            :: Text
-                         , molecule         :: Text
-                         , chain            :: Array Int Text
-                         , fragment         :: Text
-                         , synonym          :: Array Int Text
-                         , enzymeCommission :: Array Int Int
-                         , engineered       :: Bool
-                         , mutation         :: Bool
-                         , otherDetails     :: Array Int Text
-                         }
-
-data Organism = Organism { scientific :: Text
-                         , common     :: Text
-                         , taxId      :: Text
-                         }
-
-data ExpressionSystem = ExpressionSystem { name             :: Text
-                                         , common           :: Text
-                                         , taxId            :: Text
-                                         , strain           :: Text
-                                         , variant          :: Text
-                                         , cellLine         :: Text
-                                         , atcc             :: Text
-                                         , organ            :: Text
-                                         , tissue           :: Text
-                                         , cell             :: Text
-                                         , organelle        :: Text
-                                         , cellularLocation :: Text
-                                         , vectorType       :: Text
-                                         , vector           :: Text
-                                         , plasmid          :: Text
-                                         , gene             :: Text
-                                         , otherDetails     :: Text
-                                         }
-
-data Source = Source { molId            :: Text
-                     , synthetic        :: Text
-                     , fragment         :: Text
-                     , organism         :: Organism
-                     , strain           :: Text
-                     , variant          :: Text
-                     , cellLine         :: Text
-                     , atcc             :: Text
-                     , tissue           :: Text
-                     , cell             :: Text
-                     , organelle        :: Text
-                     , secretion        :: Text
-                     , cellularLocation :: Text
-                     , plasmid          :: Text
-                     , gene             :: Text
-                     , expressionSystem :: ExpressionSystem
-                     , otherDetails     :: Text
-                     }
-
---
-data Title = Title { header    :: Header
-                   , obsoleted :: Obsoleted
-                   , title     :: Text
-                   , split     :: Array Int Text
-                   , caveat    :: Caveat
-                   , compound  :: Array Int Compound
-                   -- TODO
-                   }
-
-data PrimaryStructure = PrimaryStructure -- TODO
-
-data Heterogen = Heterogen -- TODO
-
-data Helix = Helix {
-
-                   }
-
-data Sheet = Sheet {
-
-                   }
-
-data Secondary = Secondary { helixes :: Array Int Helix
-                           , sheets  :: Array Int Sheet
-                           }
-
-data ConnectivityAnnotation = ConnectivityAnnotation -- TODO
+import           Control.DeepSeq (NFData (..))
+import           Data.Map.Strict (Map)
+import           Data.Text       (Text)
+import           Data.Vector     (Vector)
+import           GHC.Generics    (Generic)
 
-data Miscellaneous = Miscellaneous -- TODO
+-- * Read PDB specification [here](http://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html).
 
-data Transformation = Transformation -- TODO
+data PDB = PDB { title       :: Text
+               , models      :: Vector Model
+               , remarks     :: Map RemarkCode RemarkData
+               , otherFields :: Map FieldType FieldData
+               }
+  deriving (Show, Eq, Generic, NFData)
 
-data Coordinate = Coordinate {
+type RemarkCode = Maybe Int
+type RemarkData = Vector Text
 
-                             }
+type FieldData = Vector Text
+data FieldType
+   =
+   -- Title Section (except TITLE and REMARKS)
+     HEADER
+   | OBSLTE
+   | SPLT
+   | CAVEAT
+   | COMPND
+   | SOURCE
+   | KEYWDS
+   | EXPDTA
+   | NUMMDL
+   | MDLTYP
+   | AUTHOR
+   | REVDAT
+   | SPRSDE
+   | JRNL
+   -- Primary Structure Section
+   | DBREF
+   | DBREF1
+   | DBREF2
+   | SEQADV
+   | SEQRES
+   | MODRES
+   -- Heterogen Section
+   | HET
+   | FORMUL
+   | HETNAM
+   | HETSYN
+   -- Secondary Structure Section
+   | HELIX
+   | SHEET
+   -- Connectivity Annotation Section
+   | SSBOND
+   | LINK
+   | CISPEP
+   -- Miscellaneous Features Section
+   | SITE
+   -- Crystallographic and Coordinate Transformation Section
+   | CRYST1
+   | MTRIXn
+   | ORIGXn
+   | SCALEn
+   -- Bookkeeping Section
+   | MASTER
+  deriving (Show, Eq, Read, Generic, NFData)
 
-data Connectivity = Connectivity -- TODO
+type Model = Vector Chain
 
-data Bookkeeping = Bookkeeping -- TODO
+type Chain = Vector Atom
 
-data PDB = PDB { title                  :: Title
-               , primaryStructure       :: PrimaryStructure
-               , heterogen              :: Heterogen
-               , secondary              :: Secondary
-               , connectivityAnnotation :: ConnectivityAnnotation
-               , miscellaneous          :: Miscellaneous
-               , transformation         :: Transformation
-               , coordinate             :: Coordinate
-               , connectivity           :: Connectivity
-               , bookkeeping            :: Bookkeeping
-               }
+data Atom = Atom { atomSerial     :: Int     -- ^ Atom serial number.
+                 , atomName       :: Text    -- ^ Atom name.
+                 , atomAltLoc     :: Char    -- ^ Alternate location indicator.
+                 , atomResName    :: Text    -- ^ Residue name.
+                 , atomChainID    :: Char    -- ^ Chain identifier.
+                 , atomResSeq     :: Int     -- ^ Residue sequence number.
+                 , atomICode      :: Char    -- ^ Code for insertion of residues.
+                 , atomX          :: Float   -- ^ Orthogonal coordinates for X in Angstroms.
+                 , atomY          :: Float   -- ^ Orthogonal coordinates for Y in Angstroms.
+                 , atomZ          :: Float   -- ^ Orthogonal coordinates for Z in Angstroms.
+                 , atomOccupancy  :: Float   -- ^ Occupancy.
+                 , atomTempFactor :: Float   -- ^ Temperature factor.
+                 , atomElement    :: Text    -- ^ Element symbol, right-justified.
+                 , atomCharge     :: Text    -- ^ Charge on the atom.
+                 }
+  deriving (Show, Eq, Generic, NFData)
diff --git a/src/Bio/Structure.hs b/src/Bio/Structure.hs
--- a/src/Bio/Structure.hs
+++ b/src/Bio/Structure.hs
@@ -1,17 +1,19 @@
-{-# LANGUAGE DeriveGeneric  #-}
 {-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric  #-}
 module Bio.Structure
   ( SecondaryStructure (..)
   , Atom (..), Bond (..)
   , Residue (..), Chain (..), Model (..)
   , StructureModels (..), StructureSerializable (..)
+  , LocalID (..)
+  , GlobalID (..)
   ) where
 
-import           Data.Array      ( Array )
-import           Data.Text       ( Text )
-import           GHC.Generics    ( Generic )
-import           Control.DeepSeq ( NFData (..) )
-import           Linear.V3       ( V3 )
+import           Control.DeepSeq (NFData (..))
+import           Data.Text       (Text)
+import           Data.Vector     (Vector)
+import           GHC.Generics    (Generic)
+import           Linear.V3       (V3)
 
 -- | Protein secondary structure
 --
@@ -28,9 +30,16 @@
 
 instance NFData SecondaryStructure
 
+newtype GlobalID = GlobalID { getGlobalID :: Int }
+  deriving (Eq, Show, Generic, NFData)
+
+newtype LocalID  = LocalID { getLocalID :: Int }
+  deriving (Eq, Show, Generic, NFData)
+
 -- | Generic atom representation
 --
-data Atom = Atom { atomName     :: Text     -- ^ IUPAC atom name 
+data Atom = Atom { atomId       :: GlobalID -- ^ global identifier
+                 , atomName     :: Text     -- ^ IUPAC atom name
                  , atomElement  :: Text     -- ^ atom chemical element
                  , atomCoords   :: V3 Float -- ^ 3D coordinates of atom
                  , formalCharge :: Int      -- ^ Formal charge of atom
@@ -43,42 +52,44 @@
 
 -- | Generic chemical bond
 --
-data Bond = Bond { bondStart :: Int  -- ^ index of first incident atom
-                 , bondEnd   :: Int  -- ^ index of second incident atom
-                 , bondOrder :: Int  -- ^ the order of chemical bond
-                 }
+data Bond m = Bond { bondStart :: m    -- ^ index of first incident atom
+                   , bondEnd   :: m    -- ^ index of second incident atom
+                   , bondOrder :: Int  -- ^ the order of chemical bond
+                   }
   deriving (Show, Eq, Generic)
 
-instance NFData Bond
+instance NFData a => NFData (Bond a)
 
 -- | A set of atoms, organized to a residues
 --
-data Residue = Residue { resName         :: Text               -- ^ residue name
-                       , resAtoms        :: Array Int Atom     -- ^ a set of residue atoms
-                       , resBonds        :: Array Int Bond     -- ^ a set of residue bonds
-                       , resSecondary    :: SecondaryStructure -- ^ residue secondary structure
-                       , resChemCompType :: Text               -- ^ chemical component type
+data Residue = Residue { resName         :: Text                  -- ^ residue name
+                       , resAtoms        :: Vector Atom           -- ^ a set of residue atoms
+                       , resBonds        :: Vector (Bond LocalID) -- ^ a set of residue bonds with local identifiers (position in 'resAtoms')
+                       , resSecondary    :: SecondaryStructure    -- ^ residue secondary structure
+                       , resChemCompType :: Text                  -- ^ chemical component type
                        }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Chain organizes linear structure of residues
 --
 data Chain = Chain { chainName     :: Text              -- ^ name of a chain
-                   , chainResidues :: Array Int Residue -- ^ residues of a chain
+                   , chainResidues :: Vector Residue    -- ^ residues of a chain
                    }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Model represents a single experiment of structure determination
 --
-newtype Model = Model { modelChains :: Array Int Chain }
+data Model = Model { modelChains :: Vector Chain           -- ^ chains in the model
+                   , modelBonds  :: Vector (Bond GlobalID) -- ^ bonds with global identifiers (field `atomId` in 'Atom')
+                   }
   deriving (Show, Eq, Generic, NFData)
 
 -- | Convert any format-specific data to an intermediate representation of structure
 class StructureModels a where
     -- | Get an array of models
-    modelsOf :: a -> Array Int Model
+    modelsOf :: a -> Vector Model
 
 -- | Serialize an intermediate representation of sequence to some specific format
 class StructureSerializable a where
     -- | Serialize an array of models to some format
-    serializeModels :: Array Int Model -> a
+    serializeModels :: Vector Model -> a
diff --git a/test/FASTASpec.hs b/test/FASTASpec.hs
--- a/test/FASTASpec.hs
+++ b/test/FASTASpec.hs
@@ -2,10 +2,11 @@
 
 module FASTASpec where
 
-import           Bio.FASTA          (fromFile, toFile)
-import           Bio.FASTA.Type     (FastaItem(..), Fasta)
-import           Bio.Sequence       (bareSequence)
-import           Prelude     hiding (writeFile, readFile)
+import           Bio.FASTA        (fromFile, toFile)
+import           Bio.FASTA.Type   (Fasta, FastaItem (..))
+import           Bio.Sequence     (bareSequence)
+import           Prelude          hiding (readFile, writeFile)
+import           System.Directory (removeFile)
 import           Test.Hspec
 
 correctFasta :: Fasta Char
@@ -27,4 +28,5 @@
     it "correctly write fasta into file" $ do
         toFile correctFasta path
         fasta <- fromFile path
+        removeFile path
         fasta `shouldBe` correctFasta
diff --git a/test/MAEParserSpec.hs b/test/MAEParserSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/MAEParserSpec.hs
@@ -0,0 +1,136 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module MAEParserSpec where
+
+import           Bio.MAE              (Block (..), MaeValue (..), Table (..),
+                                       fromFile)
+import           Bio.MAE.Parser       (blockP, tableP, versionP)
+import           Data.Attoparsec.Text (parseOnly)
+import qualified Data.Map.Strict      as M (fromList)
+import           Data.Text            (Text)
+import           Test.Hspec
+
+maeParserSpec :: Spec
+maeParserSpec = describe "Mae format parser." $ do
+    versionPSpec
+    tablePSpec
+    blockPSpec
+    maePSpec
+
+versionPSpec :: Spec
+versionPSpec = describe "version parser" $
+    it "one and only version" $ parseOnly versionP version' `shouldBe` Right "2.0.0"
+  where
+    version' :: Text
+    version' = "{\n  s_m_m2io_version\n ::: \n 2.0.0 \n } \n"
+
+tablePSpec :: Spec
+tablePSpec = describe "table parser" $ do
+    it "simple table" $ parseOnly tableP simpleTableT `shouldBe` Right simpleTable
+    it "missing values" $ parseOnly tableP tableWithMissingMaeValuesT `shouldBe` Right tableWithMissingMaeValues
+    it "negative values" $ parseOnly tableP tableWithNegativeMaeValuesT `shouldBe` Right tableWithNegativeMaeValues
+    it "quoted values" $ parseOnly tableP tableWithQuotedMaeValuesT `shouldBe` Right tableWithQuotedMaeValues
+    it "with comments" $ parseOnly tableP tableWithCommentsT `shouldBe` Right tableWithComments
+
+blockPSpec :: Spec
+blockPSpec = describe "block parser" $ do
+    it "simple block" $ parseOnly blockP simpleBlockT `shouldBe` Right simpleBlock
+    it "with many tables" $ parseOnly blockP blockWithManyTablesT `shouldBe` Right blockWithManyTables
+
+maePSpec :: Spec
+maePSpec = describe "parses mae files up to the EOF" $ do
+    it "Capri.mae" $ parseFileSpec "test/MAE/Capri.mae"
+    it "h2o.mae" $ parseFileSpec "test/MAE/h2o.mae"
+    it "docking_1.mae" $ parseFileSpec "test/MAE/docking_1.mae"
+    it "docking_2.mae" $ parseFileSpec "test/MAE/docking_2.mae"
+
+parseFileSpec :: FilePath -> Expectation
+parseFileSpec path = fromFile path >> pure ()
+
+simpleTableMap :: [(Text, [MaeValue])]
+simpleTableMap = [ ("i_val", fmap IntMaeValue [1, 2, 3])
+                 , ("r_val", fmap RealMaeValue [1.0, 2.28, 3.22])
+                 , ("s_val", fmap StringMaeValue ["aaa", "bbb", "ccc"])
+                 , ("b_val", fmap BoolMaeValue [False, True, False])
+                 ]
+
+simpleTable :: Table
+simpleTable = Table "simple_table" $ M.fromList simpleTableMap
+
+simpleTableT :: Text
+simpleTableT = "  simple_table[3] {\n  i_val \n  r_val   \n  s_val\n b_val\n ::: \n 1 1 1.0 aaa 0  \n2 2 2.28 bbb 1\n  3 3 3.22   ccc 0 \n   ::: \n  } \n"
+
+tableWithMissingMaeValuesMap :: [(Text, [MaeValue])]
+tableWithMissingMaeValuesMap = [ ("i_can_be_missing", fmap IntMaeValue [1, 2, 3] <> [Absent, Absent])
+                               , ("r_can_be_missing", fmap RealMaeValue [1.0, 2.28] <> [Absent, Absent, RealMaeValue 3.22])
+                               , ("s_can_be_missing", [Absent, Absent] <> fmap StringMaeValue ["aaa", "bbb", "ccc"])
+                               , ("r_can_be_missing_1", replicate 5 Absent)
+                               , ("r_can_be_missing_2", [RealMaeValue 1.0, Absent, Absent] <> fmap RealMaeValue [2.28, 3.22])
+                               , ("s_can_be_missing_1", fmap StringMaeValue ["aaa", "bbb", "ccc", "ddd", "eee"])
+                               ]
+
+tableWithMissingMaeValues :: Table
+tableWithMissingMaeValues = Table "table_with_missing_values" $ M.fromList tableWithMissingMaeValuesMap
+
+tableWithMissingMaeValuesT :: Text
+tableWithMissingMaeValuesT = "table_with_missing_values[5]{  \n i_can_be_missing\n r_can_be_missing \n s_can_be_missing \n r_can_be_missing_1 \n r_can_be_missing_2 \n s_can_be_missing_1 \n :::\n 1 1 1.0 <> <> 1.0 aaa\n 2 2 2.28 <> <> <> bbb \n 3 3 <> aaa <> <> ccc \n 4 <> <> bbb <> 2.28 ddd \n 5 <> 3.22 ccc <> 3.22 eee \n ::: \n } \n"
+
+tableWithNegativeMaeValuesMap :: [(Text, [MaeValue])]
+tableWithNegativeMaeValuesMap = [ ("i_neg", fmap IntMaeValue [-1, -2, -3])
+                                , ("r_neg", fmap RealMaeValue [-1.0, -2.28, -3.22])
+                                ]
+
+tableWithNegativeMaeValues :: Table
+tableWithNegativeMaeValues = Table "table_with_negative_values" $ M.fromList tableWithNegativeMaeValuesMap
+
+tableWithNegativeMaeValuesT :: Text
+tableWithNegativeMaeValuesT = "table_with_negative_values[3]{  \n i_neg\n r_neg \n ::: \n 1 -1 -1.0 \n 2 -2 -2.28 \n 3 -3 -3.22\n ::: \n} \n"
+
+tableWithQuotedMaeValuesMap :: [(Text, [MaeValue])]
+tableWithQuotedMaeValuesMap = [ ("s_quote0", fmap StringMaeValue ["\"   ssss s \"", "\" more quotes \"", "\" this is long and dull comment \""])
+                              , ("r_neg", fmap RealMaeValue [-1.0, -2.28, -3.22])
+                              , ("s_quote", fmap StringMaeValue ["\" \"", "aaa", "\" this is long and dull comment \""])
+                              , ("r_neg", fmap RealMaeValue [-1.0, -2.28, -3.22])
+                              ]
+
+tableWithQuotedMaeValues :: Table
+tableWithQuotedMaeValues = Table "table_with_quoted_values" $ M.fromList tableWithQuotedMaeValuesMap
+
+tableWithQuotedMaeValuesT :: Text
+tableWithQuotedMaeValuesT = "table_with_quoted_values[3]{  \n s_quote0\n r_neg \n s_quote\n r_neg \n ::: \n 1 \"   ssss s \" -1.0 \" \" -1.0 \n 2 \" more quotes \" -2.28 aaa -2.28 \n 3 \" this is long and dull comment \" -3.22 \" this is long and dull comment \" -3.22\n ::: \n} \n"
+
+tableWithCommentsMap :: [(Text, [MaeValue])]
+tableWithCommentsMap = [ ("i_val", replicate 7 Absent)
+                       , ("r_val", replicate 7 Absent)
+                       ]
+
+tableWithComments :: Table
+tableWithComments = Table "table_with_comments" $ M.fromList tableWithCommentsMap
+
+tableWithCommentsT :: Text
+tableWithCommentsT = "  table_with_comments[7] {\n  # comments here? \n # this is useful comment # \n i_val \n # i can write \" any \" thing  in these comment \' s \n r_val \n # comments even here? \n ::: \n 1 <> <> \n 2 <> <> \n 3 <> <> \n 4 <> <> \n 5 <> <> \n 6 <> <> \n 7 <> <> \n ::: \n  } \n"
+
+simpleBlockMap :: [(Text, MaeValue)]
+simpleBlockMap = [ ("i_val", IntMaeValue 3)
+                 , ("r_val", RealMaeValue 2.28)
+                 , ("s_val", StringMaeValue "\"aaa\"")
+                 ]
+
+simpleBlock :: Block
+simpleBlock = Block "simple_block" (M.fromList simpleBlockMap) [simpleTable]
+
+simpleBlockT :: Text
+simpleBlockT = "simple_block {\n i_val \n r_val \n s_val \n ::: \n 3 \n 2.28 \n \"aaa\"\n  " <> simpleTableT <> "}\n"
+
+blockWithManyTablesMap :: [(Text, MaeValue)]
+blockWithManyTablesMap = [ ("i_val", IntMaeValue 3)
+                         , ("s_val", StringMaeValue "\" path/ to /my /favorite /dir\"")
+                         , ("r_val", RealMaeValue (-2.28))
+                         , ("s_val1", StringMaeValue "aaa")
+                         ]
+
+blockWithManyTables :: Block
+blockWithManyTables = Block "block_with_many_tables" (M.fromList blockWithManyTablesMap) [simpleTable, tableWithComments, tableWithNegativeMaeValues]
+
+blockWithManyTablesT :: Text
+blockWithManyTablesT = "block_with_many_tables {\n i_val \n s_val \n r_val \n s_val1 \n ::: \n 3 \n \" path/ to /my /favorite /dir\" \n -2.28 \n aaa\n  " <> simpleTableT <> "\n" <> tableWithCommentsT <> "\n" <> tableWithNegativeMaeValuesT <> "}\n"
diff --git a/test/MMTFSpec.hs b/test/MMTFSpec.hs
--- a/test/MMTFSpec.hs
+++ b/test/MMTFSpec.hs
@@ -2,8 +2,8 @@
 
 import           Bio.MMTF
 import           Bio.MMTF.Decode.Codec
-import           Data.Array            ((!))
 import           Data.Int              (Int8)
+import           Data.Vector           ((!))
 import           Test.Hspec
 
 mmtfCodecSpec :: Spec
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,14 +1,15 @@
 import           ABISpec
+import           FastaParserSpec
+import           FASTASpec
+import           FastaWriterSpec
 import           GBParserSpec
 import           GBWriterSpec
+import           MAEParserSpec
 import           MMTFSpec
 import           SequenceSpec
 import           System.IO
 import           Test.Hspec
 import           UniprotSpec
-import           FastaParserSpec
-import           FastaWriterSpec
-import           FASTASpec
 
 main :: IO ()
 main = do
@@ -35,3 +36,5 @@
          fastaParserSpec
          fastaSpec
          fastaWriterSpec
+         -- Mae
+         maeParserSpec
