module HarmTrace.Base.Chord.Analysis (
  
  
    analyseTriad
  , analyseTetra
  , toTriad
  , toMajMinChord
  
  , Third (..)
  , Fifth (..)
  , Sevth (..)
  , analyseThird
  , analyseFifth
  , analyseSevth
  
  , toMode
  , toMajMin
  , toClassType
  , isSus2
  , isSus4
  
  , transposeRoot
  , transposeCL
  , transposeSD
  , toChordDegree
  , toScaleDegree
  , intervalToPitch
  , pitchToInterval
  , toChord    
  ) where
import HarmTrace.Base.Chord.Datatypes
import HarmTrace.Base.Chord.PitchClass
import HarmTrace.Base.Chord.Intervals
import HarmTrace.Base.Chord.Internal
import Data.IntSet                     ( IntSet, toAscList, member, (\\) )
toClassType :: Chord a -> ClassType
toClassType NoChord    = NoClass
toClassType UndefChord = NoClass
toClassType (Chord  _r  sh []   _b) = shToClassType sh 
toClassType c = analyseDegClassType . toIntSet $ c
analyseDegClassType :: IntSet -> ClassType
analyseDegClassType degs =
    case (analyseThird degs, analyseFifth degs, analyseSevth degs) of
       
       (MinThird, DimFifth , DimSev) -> DimClass
       (MajThird, _        , MinSev) -> DomClass
       (_       , AugFifth , _     ) -> DomClass
       (MajThird, DimFifth , _     ) -> DomClass
       (MajThird, _        , _     ) -> MajClass
       (MinThird, PerfFifth, _     ) -> MinClass
       (MinThird, _        , _     ) -> MinClass
       (NoThird,  _        , _     ) -> NoClass
shToClassType :: Shorthand -> ClassType
shToClassType Maj     = MajClass
shToClassType Min     = MinClass
shToClassType Dim     = DimClass
shToClassType Aug     = DomClass
shToClassType Maj7    = MajClass
shToClassType Min7    = MinClass
shToClassType Sev     = DomClass
shToClassType Dim7    = DimClass
shToClassType HDim7   = MinClass
shToClassType MinMaj7 = MinClass
shToClassType Aug7    = DomClass
shToClassType Maj6    = MajClass
shToClassType Min6    = MinClass
shToClassType Nin     = DomClass
shToClassType Maj9    = MajClass
shToClassType Min9    = MinClass
shToClassType Five    = NoClass
shToClassType Sus2    = NoClass
shToClassType Sus4    = NoClass
shToClassType SevSus4 = NoClass
shToClassType None    = NoClass
shToClassType Min11    = MinClass
shToClassType Eleven   = DomClass
shToClassType Min13    = MinClass
shToClassType Maj13    = MajClass
shToClassType Thirteen = DomClass
data Third = MajThird | MinThird             | NoThird deriving (Eq, Show)
data Fifth = DimFifth | PerfFifth | AugFifth | NoFifth deriving (Eq, Show)
data Sevth = DimSev   | MinSev    | MajSev   | NoSev   deriving (Eq, Show)
triadToSh :: Triad -> Shorthand
triadToSh t = case t of
                 MajTriad -> Maj
                 MinTriad -> Min
                 AugTriad -> Aug
                 DimTriad -> Dim
                 NoTriad  -> None
analyseTetra :: IntSet -> Shorthand
analyseTetra is = case (analyseTriad is, analyseSevth is) of
                    (MajTriad, MinSev) -> Sev
                    (MajTriad, MajSev) -> Maj7
                    (MinTriad, MinSev) -> Min7
                    (MinTriad, MajSev) -> MinMaj7
                    (DimTriad, MinSev) -> HDim7
                    (DimTriad, DimSev) -> Dim7
                    (AugTriad, MinSev) -> Aug7
                    (t       , NoSev ) -> triadToSh t
                    _                  -> None
toTriad :: Chord a -> Triad
toTriad NoChord    = error "toTriad: a NoChord has no triad to analyse"
toTriad UndefChord = error "toTriad: a UndefChord has no triad to analyse"
toTriad (Chord  _r  sh [] _b) = shToTriad sh 
toTriad c = analyseTriad . toIntSet $ c
analyseTriad :: IntSet -> Triad
analyseTriad is =
    case (analyseThird is, analyseFifth is) of
       (MajThird, PerfFifth) -> MajTriad
       (MajThird, AugFifth ) -> AugTriad
       (MajThird, DimFifth ) -> NoTriad
       (MinThird, PerfFifth) -> MinTriad
       (MinThird, AugFifth ) -> NoTriad
       (MinThird, DimFifth ) -> DimTriad
       (NoThird,  _        ) -> NoTriad
       (_      ,  NoFifth  ) -> NoTriad
analyseThird :: IntSet -> Third
analyseThird is
  | member 4 is = MajThird
  | member 3 is = MinThird
  | otherwise   = NoThird
analyseFifth :: IntSet -> Fifth
analyseFifth is
  | member 7 is = PerfFifth
  | member 6 is = DimFifth
  | member 8 is = AugFifth
  | otherwise   = NoFifth
analyseSevth :: IntSet -> Sevth
analyseSevth is
  | member 10 is = MinSev
  | member 11 is = MajSev
  | member 9  is = DimSev
  | otherwise    = NoSev
shToTriad :: Shorthand -> Triad
shToTriad Maj     = MajTriad
shToTriad Min     = MinTriad
shToTriad Dim     = DimTriad
shToTriad Aug     = AugTriad
shToTriad Maj7    = MajTriad
shToTriad Min7    = MinTriad
shToTriad Sev     = MajTriad
shToTriad Dim7    = DimTriad
shToTriad HDim7   = DimTriad
shToTriad MinMaj7 = MinTriad
shToTriad Aug7    = AugTriad
shToTriad Maj6    = MajTriad
shToTriad Min6    = MinTriad
shToTriad Nin     = MajTriad
shToTriad Maj9    = MajTriad
shToTriad Min9    = MinTriad
shToTriad Five    = NoTriad
shToTriad Sus2    = NoTriad
shToTriad Sus4    = NoTriad
shToTriad SevSus4 = NoTriad
shToTriad None    = NoTriad
shToTriad Min11    = MinTriad
shToTriad Eleven   = MajTriad
shToTriad Min13    = MinTriad
shToTriad Maj13    = MajTriad
shToTriad Thirteen = MajTriad
toMode :: Triad -> Mode
toMode MajTriad = MajMode
toMode MinTriad = MinMode
toMode t        = error (  "HarmTrace.Base.MusicRep.toMode: cannot convert "
                        ++ " triad to mode: " ++ show t)
toMajMin :: Triad -> ClassType
toMajMin MajTriad = MajClass
toMajMin MinTriad = MinClass
toMajMin AugTriad = MajClass
toMajMin DimTriad = MinClass
toMajMin NoTriad  = NoClass
toMajMinChord :: ChordLabel -> ChordLabel
toMajMinChord NoChord    = NoChord
toMajMinChord UndefChord = UndefChord
toMajMinChord c@(Chord r _ _ b) = case toMajMin (toTriad c) of
                     MajClass -> Chord r Maj [] b
                     MinClass -> Chord r Min [] b
                     NoClass  -> UndefChord
                     
                     _        -> error ("HarmTrace.Base.MusicRep.toMajMinChord"
                                        ++ " unexpected chord " ++ show c)
isSus2 :: ChordLabel -> Bool
isSus2 c = let is = toIntSet c in      member 2 is
                               && not (member 3 is)
                               && not (member 4 is)
                               && not (member 5 is)
isSus4 :: ChordLabel -> Bool
isSus4 c = let is = toIntSet c in not (member 2 is)
                               && not (member 3 is)
                               && not (member 4 is)
                               &&     (member 5 is)
toChordDegree :: Key -> ChordLabel -> ChordDegree
toChordDegree k (Chord r sh a b) = Chord (toScaleDegree k r) sh a b
toChordDegree _ c =
  error("HarmTrace.Base.Chord.Analysis: cannot create scale degree for " ++ show c)
toScaleDegree :: Key -> Root -> ScaleDegree
  
toScaleDegree (Key kr _) cr  = 
  scaleDegrees!!(((toPitchClass cr)  (toPitchClass kr)) `mod` 12)
transposeRoot :: Root -> Int -> Root
transposeRoot = transpose roots
transposeCL :: ChordLabel -> Int -> ChordLabel
transposeCL c sem = fmap (flip transposeRoot sem) c
transposeSD :: ScaleDegree -> Int -> ScaleDegree
transposeSD = transpose scaleDegrees
transpose :: Diatonic a => [Note a] -> Note a -> Int -> Note a
transpose ns n sem = ns !! ((sem + (toPitchClass n)) `mod` 12)
intervalToPitch :: Root -> Interval -> Root
intervalToPitch r = pcToRoot . intValToPitchClss r
pitchToInterval :: Root -> Root -> Interval
pitchToInterval ra rb = intervals !! ((toPitchClass rb  toPitchClass ra) `mod` 12)
toChord :: Root -> IntSet -> Interval -> Chord Root
toChord r is mi = Chord r sh add mi
 where add = map (Add . icToInterval) $ toAscList (is \\ shToIntSet sh)
       sh  = analyseTetra is