module Csound.Render(
    renderCsd, renderCsdBy
) where

import Data.Default
import Data.Maybe(catMaybes)
import qualified Data.Map as M
import qualified Data.Set as S

import Control.Monad.Trans.State(evalState)
import Data.Fix

import Csound.Exp
import Csound.Exp.Wrapper hiding (double)
import Csound.Render.Sco
import Csound.Render.Instr
import Csound.Render.Options
import Csound.Exp.Numeric

import Csound.Render.Pretty

import Csound.Opcode(clip, zeroDbfs)

-- | Renders Csound file.
renderCsd :: [SigOut] -> String
renderCsd = renderCsdBy def

-- | Renders Csound file with options.
renderCsdBy :: CsdOptions -> [SigOut] -> String
renderCsdBy opt as = show $ ppCsdFile 
    (renderFlags opt)
    (renderInstr0 (nchnls lastInstr) (midiAssignTable ids as) opt)
    (ppOrc $ zipWith (renderInstr krateSet) allIds (fmap (substInstrTabs fts) allInstrs))
    (ppSco $ firstInstrNote : lastInstrNote : zipWith (renderScores strs) ids (fmap (substScoreTabs fts) $ scos))
    (renderStringTable strs)
    (renderTotalDur $$ renderTabs fts)
    where scos   = fmap (scoSigOut' . sigOutContent) as          
          (instrs, effects, initOuts) = unzip3 $ zipWith runExpReader as ids    
          fts    = tabMap allInstrs scos
          strs   = stringMap $ concat scos
          ids    = take nInstr [2 .. ]
          
          allInstrs = fmap (defineInstrTabs (tabResolution opt)) $ firstInstr : lastInstr : instrs
          allIds    = firstInstrId : lastInstrId : ids
          
          nInstr = length as
          firstInstrId = 1
          lastInstrId  = nInstr + 2          
        
          firstInstr = execSE $ sequence_ initOuts
          lastInstr  = mixingInstrExp globalEffect effects
           
          scoSigOut' x = case x of
              PlainSigOut _ _ -> defineScoreTabs (tabResolution opt) $ scoSigOut x
              _ -> []            

          dur = maybe 64000000 id $ totalDur as
          renderTotalDur = ppTotalDur dur
          firstInstrNote = alwayson firstInstrId dur
          lastInstrNote  = alwayson lastInstrId dur
          alwayson instrId time = ppNote instrId 0 time []
          krateSet = S.fromList $ csdKrate opt
          globalEffect = csdEffect opt


midiAssignTable :: [Int] -> [SigOut] -> [MidiAssign]
midiAssignTable ids instrs = catMaybes $ zipWith mk ids instrs
    where mk n instr = case sigOutContent $ instr of
            Midi ty chn _ -> Just $ MidiAssign ty chn n
            _ -> Nothing

renderTabs = ppMapTable ppTabDef
renderStringTable = ppMapTable ppStrset

mixingInstrExp :: ([[Sig]] -> SE [Sig]) -> [SE [Sig]] -> E
mixingInstrExp globalEffect effects = execSE $ outs' . fmap clip' =<< globalEffect =<< sequence effects
    where clip' x = clip x 0 zeroDbfs
          
totalDur :: [SigOut] -> Maybe Double
totalDur as 
    | null as'  = Nothing
    | otherwise = Just $ maximum $ map eventEnd . scoSigOut =<< as' 
    where as' = filter isNotMidi $ map sigOutContent as
          isNotMidi x = case x of
            Midi _ _ _ -> False
            _ -> True