{-# LANGUAGE OverloadedStrings #-}

module Ssb.Types.Link (
	Link(..),
	parseLink,
	formatLink,
	FeedLink(..),
	MessageLink(..),
	BlobLink(..),
	IsLink(..),
) where

import Ssb.Types.Key
import Ssb.Types.Hash

import Data.Monoid
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import qualified Data.Text as T
import qualified Crypto.Sign.Ed25519 as Ed

-- | A link to a message, feed, or blob.
data Link = Link
	{ linkSigil :: Char
	-- ^ sigil that starts the link, eg '@'
	, linkTo :: T.Text
	-- ^ thing being linked to (base64 encoded)
	, linkTag :: T.Text
	-- ^ hash or key algorithm of thing being linked to, eg "sha256"
	} deriving (Eq, Ord)

instance Show Link where
	show = T.unpack . formatLink

instance Aeson.FromJSON Link where
	parseJSON = Aeson.withText "Link" $ either fail pure . parseLink

instance Aeson.ToJSON Link where
	toJSON = Aeson.String . formatLink

-- | Parses a Link eg
-- "@LA9HYf5rnUJFHHTklKXLLRyrEytayjbFZRo76Aj/qKs=.ed25519"
parseLink :: T.Text -> Either String Link
parseLink t
	| T.null t = Left "empty link"
	| otherwise =
		let (sigil, rest) = T.splitAt 1 t
		    (linkto, linktag) = T.breakOnEnd "." rest
		in if T.null linktag
			then Left "missing linkTag"
			else if T.null linkto
				then Left "empty link"
				else Right $ Link
					{ linkSigil = T.head sigil
					, linkTo = T.init linkto
					, linkTag = linktag
					}

-- | Formats a Link.
formatLink :: Link -> T.Text
formatLink l = T.singleton (linkSigil l) <> linkTo l <> "." <> linkTag l

-- | Class of types that are links.
class IsLink t where
	fromLink :: Link -> Either String t

	toLink :: t -> Link

	toJSONLink :: t -> Aeson.Value
	toJSONLink = Aeson.toJSON . toLink

	fromJSONLink :: Aeson.Value -> Aeson.Parser t
	fromJSONLink o = do
		l <- Aeson.parseJSON o
		either fail pure (fromLink l)

-- | A link to a feed.
newtype FeedLink = FeedLink { unFeedLink :: PublicKey }
	deriving (Show, Eq, Ord)

instance IsLink FeedLink where
	fromLink l
		| linkSigil l == '@' && linkTag l == "ed25519" =
			maybe (Left "ed25519 key parse failed")
				(Right . FeedLink)
				(parseEd25519PublicKey $ encodeUtf8 $ linkTo l)
		| otherwise = Left "wrong sigil for feed link"
	toLink fl = Link '@' linkto "ed25519"
	  where
		linkto = decodeUtf8 $ Ed.unPublicKey $ ed25519Key $ unFeedLink fl

instance Aeson.ToJSON FeedLink where
	toJSON = toJSONLink

instance Aeson.FromJSON FeedLink where
	parseJSON = fromJSONLink

-- | A link to a message.
newtype MessageLink = MessageLink { unMessageLink :: Hash }
	deriving (Show, Eq, Ord)

instance IsLink MessageLink where
	fromLink l
		| linkSigil l == '%' && linkTag l == "sha256" =
			maybe (Left "sha256 hash parse failed")
				(Right . MessageLink)
				(parseSha256 $ encodeUtf8 $ linkTo l)
		| otherwise = Left "wrong sigil for message link"
	toLink fl = Link '%' linkto "sha256"
	  where
		linkto = decodeUtf8 $ formatHash $ unMessageLink fl

instance Aeson.ToJSON MessageLink where
	toJSON = toJSONLink

instance Aeson.FromJSON MessageLink where
	parseJSON = fromJSONLink

-- | A link to a blob.
newtype BlobLink = BlobLink { unBlobLink :: Hash }
	deriving (Show, Eq, Ord)

instance IsLink BlobLink where
	fromLink l
		| linkSigil l == '&' && linkTag l == "sha256" =
			maybe (Left "sha256 hash parse failed")
				(Right . BlobLink)
				(parseSha256 $ encodeUtf8 $ linkTo l)
		| otherwise = Left "wrong sigil for blob link"
	toLink fl = Link '&' linkto "sha256"
	  where
		linkto = decodeUtf8 $ formatHash $ unBlobLink fl

instance Aeson.ToJSON BlobLink where
	toJSON = toJSONLink

instance Aeson.FromJSON BlobLink where
	parseJSON = fromJSONLink