{-
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 #-}

-- | Parse magnet URI
-- The spec for magnetURI: https://en.wikipedia.org/wiki/Magnet_URI_scheme
-- An example from tpb:
--   magnet:?xt=urn:btih:1f8a4ee3c3f57e81f8f0b4e658177201fc2a3118&dn=Honey+Bee+2+%5B2017%5D+Malayalam+DVDRiP+x264+AAC+700MB+ZippyMovieZ+E&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Fzer0day.ch%3A1337&tr=udp%3A%2F%2Fopen.demonii.com%3A1337&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Fexodus.desync.com%3A6969
-- xt   - extra topic - urn containing filehash
-- btih - bittorrent infohash
-- dn   - display name (for the user)
-- tr   - tracker URL
-- xl   - exact length
-- mt   - link to a meta file (manifest topic) that contains a list of magnet links
-- urn  - uniform resource name

-- How does a client join the swarm with only the magnet uri?
-- This is detailed in http://www.bittorrent.org/beps/bep_0009.html
-- The protocol depends on the extensions protocol specified in BEP 0010.
-- http://www.bittorrent.org/beps/bep_0009.html

-- 1. First we parse the magnet uri and get a list of trackers
-- 2. We then use the usual tracker protocol to get a list of peers.
-- 3. Then we talk to the peers to create the metadata via the BEP 0009 protocol

module FuncTorrent.MagnetURI where

import Text.ParserCombinators.Parsec
import qualified Text.Parsec.ByteString as ParsecBS
import Data.ByteString.Char8 (ByteString, pack)
import Network.HTTP.Base (urlDecode)

data Magnetinfo = Magnetinfo { infoHash :: ByteString
                             , trackerlist :: [String]
                             , name :: String
                             , xlen :: Maybe Integer
                             }
                deriving (Eq, Show)

magnetHdr :: ParsecBS.Parser String
magnetHdr = string "magnet:?"

kvpair :: ParsecBS.Parser (String, String)
kvpair = do
  k <- many1 letter
  _ <- char '='
  v <- many1 (noneOf "&")
  return (k, v)

magnetBody :: ParsecBS.Parser Magnetinfo
magnetBody = do
  pairs <- kvpair `sepBy1` (char '&')
  -- walk through pairs, populate Magnetinfo (fold?)
  return $ foldl f magnetInfo pairs
    where f magnetRecord pair =
            let (k, v) = pair
            in
              case k of
                "xt" -> magnetRecord { infoHash = pack v }
                "tr" -> let trVal = trackerlist magnetRecord
                        in
                          magnetRecord { trackerlist = trVal ++ [urlDecode v] }
                "dn" -> magnetRecord { name = urlDecode v }
                "xl" -> magnetRecord { xlen = Just (read v :: Integer) }
                _    -> magnetInfo
          magnetInfo = Magnetinfo { infoHash = mempty
                                  , trackerlist = mempty
                                  , name = mempty
                                  , xlen = Nothing }

magnetUri :: ParsecBS.Parser Magnetinfo
magnetUri = magnetHdr >> magnetBody

parseMagneturi :: ByteString -> Either String Magnetinfo
parseMagneturi input =
  case parse magnetUri "magnetParse" input of
    Right minfo -> Right minfo
    Left e -> Left $ "Cannot parse the magnet URI: " ++ show e
