module Sound.MED.Generic.Block where

import qualified Sound.MED.Raw.MMD0Block as MMD0Block
import qualified Sound.MED.Raw.MMD0NoteData as MMD0NoteData
import qualified Sound.MED.Raw.MMD1Block as MMD1Block
import qualified Sound.MED.Raw.MMD1NoteData as MMD1NoteData
import qualified Sound.MED.Raw.BlockInfo as BlockInfo
import qualified Sound.MED.Raw.BlockCmdPageTable as BlockCmdPageTable
import qualified Sound.MED.Raw.CmdPageData as CmdPageData

import Sound.MED.Basic.Human(Human(human),bold)
import Sound.MED.Basic.Utility(stringFromBytes)

import Text.Printf(printf)
import Control.Monad(liftM2)
import Data.Bits(shiftR, (.&.))
import Data.Maybe(catMaybes)

type Note      = Int
type Inst      = Int
type Cmd       = Int
type Val       = Int
type Highlight = Bool
type Line      = ( Maybe Highlight, [ ( Note, Inst, [ ( Cmd, Val ) ] ) ])

data MEDBlock = MEDBlock
  { name    :: Maybe String
  , tracks  :: Int
  , lines   :: Int
  , pages   :: Int
  , seqdata :: [ Line ]
  }

medblock0 :: MMD0Block.MMD0Block -> MEDBlock
medblock0 b =
  let name'       = Nothing
      tracks'     = fromIntegral $ MMD0Block.numtracks b
      lines'      = fromIntegral $ MMD0Block.lines     b + 1
      pages'      = 1
      highlights' = repeat Nothing
      f           = fromIntegral
      g (MMD0NoteData.MMD0NoteData n i c v) = (f n, f i, [(f c, f v)])
      notedata'   = map (map g) (MMD0Block.notedata b)
      seqdata'    = zip highlights' notedata'
  in MEDBlock name' tracks' lines' pages' seqdata'


medblock1 :: MMD1Block.MMD1Block -> MEDBlock
medblock1 b =
  let i           = MMD1Block.info b
      name'       = fmap stringFromBytes $ BlockInfo.blockname =<< i
      tracks'     = fromIntegral $ MMD1Block.numtracks b
      lines'      = fromIntegral $ MMD1Block.lines     b + 1
      pages'      = case BlockInfo.pagetable =<< i of
        Nothing -> 1
        Just pt -> 1 + (length . catMaybes $ BlockCmdPageTable.pages pt)
      hlbit h bpos = ((h `shiftR` bpos) .&. 1) == 1
      hlbits h    = map (hlbit h) [0..31]
      highlights' = case BlockInfo.hlmask =<< i of
        Nothing -> repeat Nothing
        Just hl -> map Just $ concatMap hlbits $ hl
      fI          = fromIntegral
      nd (MMD1NoteData.MMD1NoteData n j c v) = (fI n, fI j, [(fI c, fI v)])
      notedata'   = map (map nd) (MMD1Block.notedata b)
      cv (CmdPageData.CmdPageData c v) = (fI c, fI v)
      cmddata'    = case BlockInfo.pagetable =<< i of
        Nothing -> []
        Just pt -> map (map (map cv)) (catMaybes (BlockCmdPageTable.pages pt))
      p (c,v) (n,j,cvs) = (n, j, cvs ++ [(c,v)])
      ncdata'     = foldr (zipWith (zipWith p)) notedata' cmddata'
      seqdata'    = zip highlights' ncdata'
  in MEDBlock name' tracks' lines' pages' seqdata'


instance Human MEDBlock where
  human b =
    let name' = maybe "" (' ':) $ name b
        blocklines = Sound.MED.Generic.Block.lines
        dim'  = printf "%d*%d*%d" (blocklines b) (tracks b) (pages b)
        seq'  = unlines (zipWith highlightLine [0..] (seqdata b))
    in dim' ++ name' ++ "\n" ++ seq'

highlightLine :: Int -> Line -> String
highlightLine i (highlight, ds) =
  let bLine = humanLine i ds
  in if highlight == Just True then bold bLine else bLine

humanLine :: Int -> [ ( Note, Inst, [ ( Cmd, Val ) ] ) ] -> String
humanLine i ds =
  let mapWords fmt = concatMap (' ':) . map fmt
      hCV (c, v) = printf "%02X%02X" c v
      hTrack (n, j, cvs) = printf "%s %02X%s" (notes!!n) j (mapWords hCV cvs)
  in  printf "%04X:%s" i (mapWords hTrack ds)

notes :: [String]
notes =
  "---" :
  liftM2 (flip (printf "%s%1X"))
    [(1::Int) ..]
    ["C-","C#","D-","D#","E-","F-","F#","G-","G#","A-","A#","B-"]