module Ripple.Path (PathSet(..), Path(..), PathEntry(..)) where

import Data.Bits ((.&.), (.|.))
import Data.List (find)
import Control.Arrow (first, second)
import Control.Applicative ((<$>), (<*>))
import Data.Word (Word8)
import Data.Binary (Binary(..), Get, putWord8, getWord8)
import Data.Binary.Get (lookAheadM)
import Data.Base58Address (RippleAddress)

import Ripple.Amount (CurrencySpecifier)

newtype PathSet = PathSet [Path]
	deriving (Show, Eq)

instance Binary PathSet where
	get = do
		maybeEmpty <- lookAheadM (maybeGetWord8 [0x00])
		case maybeEmpty of
			Just _ -> return (PathSet [Path []])
			_      -> PathSet <$> getPaths

	put (PathSet []) = fail "Empty PathSet is not allowed"
	put (PathSet (Path entries : [])) = do
		mapM_ put entries
		putWord8 0x00
	put (PathSet (Path entries : ps)) = do
		mapM_ put entries
		putWord8 0xFF
		put (PathSet ps)

getPaths :: Get [Path]
getPaths = do
	(done, path) <- getEntries
	if done then return [Path path] else
		getPaths >>= \rest -> return (Path path : rest)

getEntries :: Get (Bool, [PathEntry])
getEntries = do
	entry <- get
	nextOrDone <- lookAheadM (maybeGetWord8 [0xFF,0x00])
	case nextOrDone of
		Just 0x00 -> return (True, [entry])
		Just 0xFF -> return (False, [entry])
		_         -> getEntries >>= \(done, rest) -> return (done, entry:rest)

newtype Path = Path [PathEntry]
	deriving (Show, Eq)

data PathEntry = PathEntry {
		account    :: Maybe RippleAddress,
		toCurrency :: Maybe CurrencySpecifier,
		issuer     :: Maybe RippleAddress
	} deriving (Show, Eq)

instance Binary PathEntry where
	get = getWord8 >>= \typ -> PathEntry <$>
		(if typ .&. 0x01 == 0x01 then Just <$> get else return Nothing) <*>
		(if typ .&. 0x10 == 0x10 then Just <$> get else return Nothing) <*>
		(if typ .&. 0x20 == 0x20 then Just <$> get else return Nothing)

	put (PathEntry Nothing Nothing Nothing) =
		fail "Invalid empty PathEntry"
	put (PathEntry account currency issuer) = putWord8 typ >> dta
		where
		(dta, typ) = first sequence_ $ second (foldr (.|.) 0x00) $ unzip [
				maybePut 0x01 account,
				maybePut 0x10 currency,
				maybePut 0x20 issuer
			]
		maybePut tag = maybe (return (), 0x00) (\x -> (put x, tag))

maybeGetWord8 :: [Word8] -> Get (Maybe Word8)
maybeGetWord8 ws = fmap (\w -> find (==w) ws) getWord8