newHandle :: Config -> IO Handle
newHandle config = do
    st <- newTVarIO $ State Nothing (return ()) M.empty
    l <- newTMVarIO ()

    msgs <- newTQueueIO
    void $ forkIO $ forever $ do
        (Message time aId msg) <- atomically $ readTQueue msgs
        putStrLn $ mconcat
            [ formatTime defaultTimeLocale "%H:%M:%S" time
            , " [ " <> take 24 (padL 24 (T.unpack $ unAssetId aId)) <> " ] "
            , msg
            ]
        hFlush stdout

    e <- newTQueueIO
    void $ forkIO $ forever $ do
        join $ atomically $ readTQueue e

    kH <- runMaybeT $ do
        apiKey <- MaybeT $ lookupEnv "KRAKEN_API_KEY"
        apiSecret <- MaybeT $ lookupEnv "KRAKEN_API_SECRET"

        MaybeT $ Just <$> K.newHandle (K.Config apiKey apiSecret)

    let h = Handle st msgs e kH l


    -- This background thread periodically checks if there are any assets
    -- marked as dirty and forks a build thread for each.
    tId <- forkIO $ forever $ do

        -- Get a list of dirty assets. Those are the ones which we need to
        -- rebuild. The check runs in a STM transaction, and will block until
        -- at least one asset is dirty. Much efficient, wow.
        dirtyAssetIds <- atomically $ do
            s <- readTVar st
            let assetIds = M.keys $ M.filter ((==) Dirty . arsState) (assets s)
            if length assetIds == 0 then retry else return assetIds

        forM_ dirtyAssetIds $ \aId -> do
            -- First we have to mark the asset as being built. This is to avoid
            -- forking two or more build threads for the same asset.
            markBuilding h aId

            forkIO $ do
                assetDef <- lookupAssetDefinition config h aId
                case assetDef of
                    Nothing -> do
                        -- failBuild h aId (AssetNotFound aId)
                        logMessage h aId $ "Asset not found, treating as external: " ++ show aId
                        buildAsset h aId $ AssetDefinition (externalBuilder $ PublicIdentifier $ unAssetId aId) id (\_ _ _ -> return ())
                    Just ad -> do
                        -- logMessage h aId $ "Building"
                        buildAsset h aId ad

    atomically $ modifyTVar st (\s -> s { dispatcherThreadId = Just tId })

    return h
newHandle :: Config -> IO Handle
newHandle config = do
    st <- newTVarIO $ State Nothing (return ()) M.empty
    l <- newTMVarIO ()

    msgs <- newTQueueIO
    void $ forkIO $ forever $ do
        (Message time aId msg) <- atomically $ readTQueue msgs
        putStrLn $ mconcat
            [ formatTime defaultTimeLocale "%H:%M:%S" time
            , " [ " <> take 24 (padL 24 (T.unpack $ unAssetId aId)) <> " ] "
            , msg
            ]
        hFlush stdout

    e <- newTQueueIO
    void $ forkIO $ forever $ do
        join $ atomically $ readTQueue e

    kH <- runMaybeT $ do
        apiKey <- MaybeT $ lookupEnv "KRAKEN_API_KEY"
        apiSecret <- MaybeT $ lookupEnv "KRAKEN_API_SECRET"

        MaybeT $ Just <$> K.newHandle (K.Config apiKey apiSecret)

    let h = Handle st msgs e kH l


    -- This background thread periodically checks if there are any assets
    -- marked as dirty and forks a build thread for each.
    tId <- forkIO $ forever $ do

        -- Get a list of dirty assets. Those are the ones which we need to
        -- rebuild. The check runs in a STM transaction, and will block until
        -- at least one asset is dirty. Much efficient, wow.
        dirtyAssetIds <- atomically $ do
            s <- readTVar st
            let assetIds = M.keys $ M.filter ((==) Dirty . arsState) (assets s)
            if length assetIds == 0 then retry else return assetIds

        forM_ dirtyAssetIds $ \aId -> do
            -- First we have to mark the asset as being built. This is to avoid
            -- forking two or more build threads for the same asset.
            markBuilding h aId

            forkIO $ do
                assetDef <- lookupAssetDefinition config h aId
                case assetDef of
                    Nothing -> do
                        -- failBuild h aId (AssetNotFound aId)
                        logMessage h aId $ "Asset not found, treating as external: " ++ show aId
                        buildAsset h aId $ AssetDefinition (externalBuilder $ PublicIdentifier $ unAssetId aId) id (\_ _ _ -> return ())
                    Just ad -> do
                        -- logMessage h aId $ "Building"
                        buildAsset h aId ad

    atomically $ modifyTVar st (\s -> s { dispatcherThreadId = Just tId })

    return h
newHandle config = do
    st <- newTVarIO $ State Nothing (return ()) M.empty
    l <- newTMVarIO ()

    msgs <- newTQueueIO
    void $ forkIO $ forever $ do
        (Message time aId msg) <- atomically $ readTQueue msgs
        putStrLn $ mconcat
            [ formatTime defaultTimeLocale "%H:%M:%S" time
            , " [ " <> take 24 (padL 24 (T.unpack $ unAssetId aId)) <> " ] "
            , msg
            ]
        hFlush stdout

    e <- newTQueueIO
    void $ forkIO $ forever $ do
        join $ atomically $ readTQueue e

    kH <- runMaybeT $ do
        apiKey <- MaybeT $ lookupEnv "KRAKEN_API_KEY"
        apiSecret <- MaybeT $ lookupEnv "KRAKEN_API_SECRET"

        MaybeT $ Just <$> K.newHandle (K.Config apiKey apiSecret)

    let h = Handle st msgs e kH l


    -- This background thread periodically checks if there are any assets
    -- marked as dirty and forks a build thread for each.
    tId <- forkIO $ forever $ do

        -- Get a list of dirty assets. Those are the ones which we need to
        -- rebuild. The check runs in a STM transaction, and will block until
        -- at least one asset is dirty. Much efficient, wow.
        dirtyAssetIds <- atomically $ do
            s <- readTVar st
            let assetIds = M.keys $ M.filter ((==) Dirty . arsState) (assets s)
            if length assetIds == 0 then retry else return assetIds

        forM_ dirtyAssetIds $ \aId -> do
            -- First we have to mark the asset as being built. This is to avoid
            -- forking two or more build threads for the same asset.
            markBuilding h aId

            forkIO $ do
                assetDef <- lookupAssetDefinition config h aId
                case assetDef of
                    Nothing -> do
                        -- failBuild h aId (AssetNotFound aId)
                        logMessage h aId $ "Asset not found, treating as external: " ++ show aId
                        buildAsset h aId $ AssetDefinition (externalBuilder $ PublicIdentifier $ unAssetId aId) id (\_ _ _ -> return ())
                    Just ad -> do
                        -- logMessage h aId $ "Building"
                        buildAsset h aId ad

    atomically $ modifyTVar st (\s -> s { dispatcherThreadId = Just tId })

    return h