{-
 - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
 -
 - This file is part of FuncTorrent.
 -
 - FuncTorrent is free software; you can redistribute it and/or modify
 - it under the terms of the GNU General Public License as published by
 - the Free Software Foundation; either version 3 of the License, or
 - (at your option) any later version.
 -
 - FuncTorrent is distributed in the hope that it will be useful,
 - but WITHOUT ANY WARRANTY; without even the implied warranty of
 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 - GNU General Public License for more details.
 -
 - You should have received a copy of the GNU General Public License
 - along with FuncTorrent; if not,  see <http://www.gnu.org/licenses/>
 -}

{-# LANGUAGE OverloadedStrings #-}
module FuncTorrent.Metainfo
    (Info(..),
     Metainfo(..),
     torrentToMetainfo
    ) where

import Prelude hiding (lookup)
import Data.ByteString.Char8 (ByteString, unpack)
import Data.Map as M ((!), lookup)
import Data.List (intersperse)
import Crypto.Hash.SHA1 (hash)
import Data.Maybe (maybeToList)

import FuncTorrent.Bencode (BVal(..), encode, decode, bstrToString, bValToInteger)

data FileMeta = FileMeta { lengthInBytes :: !Integer
                         , md5sum :: !(Maybe String)
                         , path :: String
                         } deriving (Eq, Show)

data Info = Info { pieceLength :: !Integer
                 , pieces :: !ByteString
                 , private :: !(Maybe Integer)
                 , name :: !String
                 , filemeta :: [FileMeta]
                 } deriving (Eq, Show)

data Metainfo = Metainfo { info :: !(Maybe Info)
                         , announceList :: ![String]
                         , creationDate :: !(Maybe Integer)
                         , comment :: !(Maybe String)
                         , createdBy :: !(Maybe String)
                         , encoding :: !(Maybe String)
                         , infoHash :: !ByteString
                         } deriving (Eq, Show)

bvalToInfo :: BVal -> Maybe Info
bvalToInfo (Bdict minfo) = let (Bint pieceLength') = minfo ! "piece length"
                               (Bstr pieces') = minfo ! "pieces"
                               private' = Nothing
                               (Bstr name') = minfo ! "name"
                               -- is the key "files" present? If so, it is a multi-file torrent
                               -- if not, it is a single file torrent.
                               filesIfMulti = lookup "files" minfo
                               partialInfo = Info { pieceLength = pieceLength'
                                                  , pieces = pieces'
                                                  , private = private'
                                                  , name = unpack name'
                                                  , filemeta = []
                                                  }
                           in
                             case filesIfMulti of
                               Nothing -> let (Bint length') = minfo ! "length"
                                              filemeta' = FileMeta { lengthInBytes = length'
                                                                   , md5sum = Nothing
                                                                   , path = unpack name' }
                                          in Just (partialInfo { filemeta = [filemeta'] })
                               Just (Blist files) -> mapM toFileMeta files >>=
                                                     \filemeta' ->
                                                       Just partialInfo { filemeta = filemeta' }
                               Just _ -> Nothing
bvalToInfo _ = Nothing

toFileMeta :: BVal -> Maybe FileMeta
toFileMeta (Bdict fm) = let (Bint length') = fm ! "length"
                            (Blist pathElems) = fm ! "path"
                            pathStrings = fmap bstrToString pathElems
                        in
                          sequence pathStrings >>=
                          \pathList -> let path' = concat $ intersperse "/" pathList
                                       in Just (FileMeta { lengthInBytes = length'
                                                         , md5sum = Nothing
                                                         , path = path' })
toFileMeta _ = Nothing

mkMetaInfo :: BVal   -> Either String Metainfo
mkMetaInfo (Bdict minfo)  =
    let info'         = bvalToInfo $ minfo ! "info"
        announce'     = lookup "announce" minfo
        announceList' = lookup "announce-list" minfo
        creationDate' = lookup "creation date" minfo
        comment'      = lookup "comment" minfo
        createdBy'    = lookup "created by" minfo
        encoding'     = lookup "encoding" minfo
    in Right Metainfo {
               info         = info'
             , announceList = maybeToList (announce' >>= bstrToString)
                              ++ getAnnounceList announceList'
             , creationDate = bValToInteger =<< creationDate'
             , comment      = bstrToString  =<< comment'
             , createdBy    = bstrToString  =<< createdBy'
             , encoding     = bstrToString  =<< encoding'
             , infoHash     = hash . encode $ (minfo ! "info")
             }
mkMetaInfo _ = Left "mkMetaInfo: expect an input dict"

getAnnounceList :: Maybe BVal -> [String]
getAnnounceList Nothing = []
getAnnounceList (Just (Bint _)) = []
getAnnounceList (Just (Bstr _)) = []
getAnnounceList (Just (Blist l)) = map (\s -> case s of
                                               (Bstr s') ->  unpack s'
                                               (Blist s') -> case s' of
                                                              [Bstr s''] -> unpack s''
                                                              _ -> ""
                                               _ -> "") l
getAnnounceList (Just (Bdict _)) = []

torrentToMetainfo :: ByteString -> Either String Metainfo
torrentToMetainfo s =
  case decode s of
    Right d -> mkMetaInfo d
    Left e -> Left ("Cannot parse the torrent file: " ++ show e)

