-- | Shared data structure definitions
module Base where

import qualified Data.IntMap as M
import Data.Time
import System.IO (stderr, hPutStrLn)
import Control.Concurrent
import System.IO.Unsafe
import Control.Monad (when)
import Control.Applicative
import Bio.SamTools.Bam
import Bio.SamTools.Classify
import Data.List (sortBy, groupBy)
import Data.Maybe (fromMaybe)
import Data.ByteString.Char8 (ByteString)
import Data.Int

_prev_time :: MVar UTCTime
_prev_time = unsafePerformIO $ newEmptyMVar

type ContigID = Int
type ClusterID = Int

type Reads = M.IntMap (ByteString,Int)

targetlength rs c = case M.lookup c rs of
  Just (n,i) -> fromIntegral i
  Nothing    -> error ("Didn't find read #"++show c)

data Links = Links { stats :: Stats Statistics, left, right :: M.IntMap Link }
type Link = [Bam1]

-- | Order link candidates from a set of Bam records by number of matches 
sort_matches :: [Bam1] -> [Link]
sort_matches = sortOn (negate . length) . groupOn (mateTargetID &&& isMateReverse)

(&&&) f g = \x -> (f x,g x)

lookup_left, lookup_right :: Links -> ContigID -> [Link]
lookup_left ls c = sort_matches . fromMaybe [] $ M.lookup c $ left ls
lookup_right ls c = sort_matches . fromMaybe [] $ M.lookup c $ right ls

-- | Extract info from a 
contigid :: Link -> ContigID
contigid = fromMaybe (error "No contig ID??") . targetID . head

targetid :: Link -> ContigID
targetid = fromMaybe (error "No target ID??") . mateTargetID . head

targetLeft :: Link -> Bool
targetLeft = isMateReverse . head

-- | Expected distance, and standard deviation
distance :: Link -> (Double,Double)
distance = error "undefined distance"

timestamp :: String -> IO ()
timestamp msg = do
  e <- isEmptyMVar _prev_time
  when e $ putMVar _prev_time =<< getCurrentTime  
  t <- getCurrentTime
  old <- takeMVar _prev_time
  hPutStrLn stderr (show t ++ "\t" ++ msg ++ "\t" ++ show (diffUTCTime t old))
  putMVar _prev_time t
  
-- | Group 'Bam1' (or any other) records based on some selector field or function.
groupOn :: Ord a => (b -> a) -> [b] -> [[b]]
groupOn f = groupBy ((==) `on` f) . sortOn f

sortOn f = sortBy (compare `on` f)

on :: (a -> a -> b) -> (d -> a) -> d -> d -> b
c `on` g = \x y -> c (g x) (g y)
  