{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoMonomorphismRestriction  #-}
{-# LANGUAGE OverloadedStrings          #-}

module Music.Score.Import.Midi (
        IsMidi(..),
        fromMidi,
        readMidi,
        readMidiMaybe,
        readMidiEither
  ) where

import           Music.Pitch.Literal       (IsPitch)
import           Music.Score.Score

import           Control.Applicative
import           Control.Lens
import Codec.Midi hiding (Track)
-- import           Control.Reactive          hiding (Event)
-- import qualified Control.Reactive          as R
-- import           Control.Reactive.Midi

import           Music.Dynamics.Literal
import           Music.Pitch.Literal
import           Music.Score.Articulation
import           Music.Score.Chord
import           Music.Score.Combinators
import           Music.Score.Dynamics
import           Music.Score.Export.Common
import           Music.Score.Ornaments
import           Music.Score.Part
import           Music.Score.Pitch
import           Music.Score.Rhythm
import           Music.Score.Score
import           Music.Score.Ties
import           Music.Score.Track
import           Music.Score.Voice
import           Music.Time

import qualified Codec.Midi                as Midi
import qualified Data.List                 as List
import qualified Data.Map                  as Map
import qualified Music.Lilypond            as Lilypond
import qualified Music.MusicXml.Simple     as Xml
import qualified Text.Pretty               as Pretty

import qualified Music.Pitch.Literal       as Pitch
-- import qualified Data.ByteString.Lazy as ByteString

-- |
-- This constraint includes all note types that can be constructed from a Midi representation.
--
type IsMidi a = (
    -- TODO
    IsPitch a,
    HasPart' a,
    Enum (Part a),
    -- HasPitch a,
    Num (Pitch a),
    HasTremolo a,
    HasArticulation a,
    Tiable a
    )


-- |
-- Convert a score from a Midi representation.
--
fromMidi :: IsMidi a => Midi -> Score a
fromMidi = undefined
    -- Map each track to a part (scanning for ProgramChange, name etc)
    -- Subdivide parts based on channels
    -- Set channel 10 tracks to "percussion"

    -- Remove all non-used messages (KeyPressure, ChannelPressure, ProgramChange)
    -- Create reactives from variable values
    -- Create notes
    -- Superimpose variable values

    -- Compose
    -- Add meta-information

-- TODO

-- |
-- Read a Midi score from a file. Fails if the file could not be read or if a parsing
-- error occurs.
--
readMidi :: IsMidi a => FilePath -> IO (Score a)
readMidi path = fmap (either (\x -> error $ "Could not read MIDI file" ++ x) id) $ readMidiEither path

-- |
-- Read a Midi score from a file. Fails if the file could not be read, and returns
-- @Nothing@ if a parsing error occurs.
--
readMidiMaybe :: IsMidi a => FilePath -> IO (Maybe (Score a))
readMidiMaybe path = fmap (either (const Nothing) Just) $ readMidiEither path

-- |
-- Read a Midi score from a file. Fails if the file could not be read, and returns
-- @Left m@ if a parsing error occurs.
--
readMidiEither :: IsMidi a => FilePath -> IO (Either String (Score a))
readMidiEither path = fmap (fmap fromMidi) $ importFile path