module Contact (
    Contact(..),
    contactView,

    ContactService,
    contactRequest,
    contactAccept,
) where

import Control.Arrow
import Control.Monad
import Control.Monad.Except
import Control.Monad.Reader

import Data.Maybe
import Data.Proxy
import Data.Text (Text)
import qualified Data.Text as T

import Identity
import Network
import Pairing
import PubKey
import Service
import State
import Storage
import Storage.Merge

data Contact = Contact
    { contactIdentity :: ComposedIdentity
    , contactName :: Maybe Text
    }

data ContactData = ContactData
    { cdPrev :: [Stored ContactData]
    , cdIdentity :: [Stored (Signed IdentityData)]
    , cdName :: Maybe Text
    }

instance Storable ContactData where
    store' x = storeRec $ do
        mapM_ (storeRef "PREV") $ cdPrev x
        mapM_ (storeRef "identity") $ cdIdentity x
        storeMbText "name" $ cdName x

    load' = loadRec $ ContactData
        <$> loadRefs "PREV"
        <*> loadRefs "identity"
        <*> loadMbText "name"

instance SharedType ContactData where
    sharedTypeID _ = mkSharedTypeID "34fbb61e-6022-405f-b1b3-a5a1abecd25e"

contactView :: [Stored ContactData] -> [Contact]
contactView = helper []
    where helper used = filterAncestors >>> \case
              x:xs | Just cid <- validateIdentityF (cdIdentity (fromStored x))
                   , not $ any (sameIdentity cid) used
                   -> Contact { contactIdentity = cid
                              , contactName = lookupProperty cid cdName (x:xs)
                              } : helper (cid:used) (cdPrev (fromStored x) ++ xs)
                   | otherwise -> helper used (cdPrev (fromStored x) ++ xs)
              [] -> []

lookupProperty :: forall a. ComposedIdentity -> (ContactData -> Maybe a) -> [Stored ContactData] -> Maybe a
lookupProperty idt sel = join . fmap (sel . fromStored) . listToMaybe . filterAncestors . helper
    where helper (x:xs) | Just cid <- validateIdentityF (cdIdentity (fromStored x))
                        , cid `sameIdentity` idt
                        , Just _ <- sel $ fromStored x
                        = x : helper xs
                        | otherwise = helper $ cdPrev (fromStored x) ++ xs
          helper [] = []


type ContactService = PairingService ContactAccepted

data ContactAccepted = ContactAccepted

instance Storable ContactAccepted where
    store' ContactAccepted = storeRec $ do
        storeText "accept" ""
    load' = loadRec $ do
        (_ :: T.Text) <- loadText "accept"
        return ContactAccepted

instance PairingResult ContactAccepted where
    pairingServiceID _ = mkServiceID "d9c37368-0da1-4280-93e9-d9bd9a198084"

    pairingHookRequest = do
        peer <- asks $ svcPeer
        svcPrint $ "Contact pairing from " ++ T.unpack (displayIdentity peer) ++ " initiated"

    pairingHookResponse confirm = do
        peer <- asks $ svcPeer
        svcPrint $ "Confirm contact " ++ T.unpack (displayIdentity $ finalOwner peer) ++ ": " ++ confirm

    pairingHookRequestNonce confirm = do
        peer <- asks $ svcPeer
        svcPrint $ "Contact request from " ++ T.unpack (displayIdentity $ finalOwner peer) ++ ": " ++ confirm

    pairingHookRequestNonceFailed = do
        peer <- asks $ svcPeer
        svcPrint $ "Failed contact request from " ++ T.unpack (displayIdentity peer)

    pairingHookConfirm ContactAccepted = do
        svcPrint $ "Contact confirmed by peer"
        return $ Just ContactAccepted

    pairingHookAccept ContactAccepted = return ()

contactRequest :: (MonadIO m, MonadError String m) => (String -> IO ()) -> UnifiedIdentity -> Peer -> m ()
contactRequest _ = pairingRequest @ContactAccepted Proxy

contactAccept :: (MonadIO m, MonadError String m) => (String -> IO ()) -> Head LocalState -> Peer -> m ()
contactAccept printMsg h peer = do
    let self = headLocalIdentity h
    sendToPeerWith self peer $ \case
        NoPairing -> throwError $ "none in progress"
        OurRequest {} -> throwError $ "waiting for peer"
        OurRequestConfirm Nothing -> do
            liftIO $ printMsg $ "Contact accepted, waiting for peer confirmation"
            return (Nothing, OurRequestReady)
        OurRequestConfirm (Just ContactAccepted) -> do
            PeerIdentityFull pid <- peerIdentity peer
            liftIO $ do
                printMsg $ "Contact accepted"
                updateLocalState_ h $ finalizeContact pid
            return (Nothing, PairingDone)
        OurRequestReady -> throwError $ "alredy accepted, waiting for peer"
        PeerRequest {} -> throwError $ "waiting for peer"
        PeerRequestConfirm -> do
            PeerIdentityFull pid <- peerIdentity peer
            liftIO $ do
                printMsg $ "Contact accepted"
                updateLocalState_ h $ finalizeContact pid
            return (Just $ PairingAccept ContactAccepted, PairingDone)
        PairingDone -> throwError $ "alredy done"
        PairingFailed -> throwError $ "alredy failed"

finalizeContact :: MonadIO m => UnifiedIdentity -> Stored LocalState -> m (Stored LocalState)
finalizeContact identity slocal = liftIO $ do
    let st = storedStorage slocal
    contact <- wrappedStore st ContactData
            { cdPrev = lookupSharedValue $ lsShared $ fromStored slocal
            , cdIdentity = idDataF $ finalOwner identity
            , cdName = Nothing
            }
    shared <- makeSharedStateUpdate st [contact] (lsShared $ fromStored slocal)
    wrappedStore st (fromStored slocal) { lsShared = [shared] }