{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}

module Music.Instrument.Common where

import Music.Diatonic hiding (transpose)
import Music.Diatonic.Note hiding (transpose)
import Music.Diatonic.Degree
import Music.Diatonic.Chord
import Music.Diatonic.Scale
import Music.Diatonic.Harmony
import Control.Monad
import Data.List
import Data.Maybe
import Data.Char
import qualified Data.Set

class NewNotes a where
  newNotes :: a -> [Note]
  lastIntervals :: a -> NewSteps
  lastIntervals a = NewSteps se . notesToSteps . newNotes $ a
    where se = requiresSequence' a
  requiresSequence' :: a -> Bool
  getChords :: a -> Maybe [Chord]
  isSlash :: a -> Bool
  isSlash = const False

instance NewNotes Chord where
  newNotes = notes
  requiresSequence' _ = True
  getChords = const Nothing

instance NewNotes Scale where
  newNotes = notes
  requiresSequence' _ = False
  getChords = const Nothing

instance NewNotes NewScale where
  newNotes (NewScale chord) = notes chord
  requiresSequence' _ = False
  getChords = const Nothing

instance NewNotes Note where
  newNotes n = [n]
  requiresSequence' _ = False
  getChords = const Nothing

instance NewNotes [Note] where
  newNotes n = n
  requiresSequence' _ = True
  getChords = const Nothing

instance NewNotes NewSteps where
  newNotes ns = (map stepToNote) . deStep $ ns
  lastIntervals = id
  requiresSequence' (NewSteps b _) = b
  getChords = const Nothing

instance NewNotes Harmony where
  newNotes = undefined
  requiresSequence' _ = undefined
  getChords = Just . chords

instance NewNotes [Chord] where
  newNotes = undefined
  requiresSequence' _ = undefined
  getChords = Just

instance Nts NewChord where
  notes (NewChord inversion b c) = applyMaybe (\x y-> y:x) (x:canonize(tf y):xs) inversion
    where
    (x:y:xs) = notes c
    tf = case b of
        (Just (Sus 2)) -> (\x -> flat (flat x))
        (Just (Sus 4)) -> (\x -> (sharp x))
        _ -> id

instance NewNotes NewChord where
  newNotes = notes
  requiresSequence' _ = True
  getChords = const Nothing
  isSlash (NewChord i _ _) = isJust i

sus n c = NewChord Nothing (Just $ Sus n) c

slash note chord = NewChord (Just note) Nothing chord

getNoteIndexFromChord note chord = elemIndex note (notes chord)
getNoteFromChordIndex chordIndex chord = (notes chord) !! chordIndex

{-
sus2 chord = (x:canonize(flat(flat y)):xs)
  where (x:y:xs) = newNotes chord
sus4 chord = (x:canonize(sharp y):xs)
  where (x:y:xs) = newNotes chord
-}

data NewScale = NewScale Chord
data NewSteps = NewSteps Bool [Int]

data NewChord = NewChord (Maybe Note) (Maybe ChordModifier) Chord

data ChordModifier = Sus Int

applyMaybe f a m = case m of
  Just b -> f a b
  _ -> a

instance Show ChordModifier where
 show (Sus x) = "sus"++show x

instance Show NewChord where
 show (NewChord inversion modifier c) = applyMaybe (\x y -> x ++ "/" ++show y) (applyMaybe (\x y -> x++show y) (show c) modifier) inversion

stepMap f (NewSteps b d) = NewSteps b (f d)
deStep (NewSteps b xs) = xs
getSteps (NewSteps b xs) = xs

convertToSteps = lastIntervals
shiftOctave n = shiftStep (n*12)
shiftStep n = stepMap (map (+n))

chordToScale = NewScale

stepToNote interval = chromaticScale !! (interval `rem` chromaticScaleLength)
notesToSteps notes = concatMap (\(o,y) -> map (\i -> i + (o*chromaticScaleLength)  ) y ) $ zip [0..] (properGroup (\x y -> x > y) (map noteToChromaticIndex notes))

data ControlAnnotation = AnnotateNote | AnnotatePositionVertical | AnnotatePositionHorizontal | AnnotateMarking

abbreviateNote x = "CdDeEFgGaAbB" !! fromJust (elemIndex x chromaticScale)

chromaticScale = [C,sharp C,D,sharp D,E,F,sharp F,G,sharp G,A,sharp A,B]

chromaticScaleLength = length chromaticScale

tuningAndPosToNote stringTuning pos = canonize $ applyNTimes sharp pos stringTuning

applyNTimes f n x = iterate f x !! n

noteToChromaticIndex note = fromJust (findIndex (flip equiv note) chromaticScale)

extractDegrees' concept = map (+ (noteToChromaticIndex root')) $ map (semitones . distance root' ) notes'
 where
 root' = head notes'
 notes' = newNotes concept

degreeToChromaticIndex degree = fromJust (findIndex (flip equiv degree) degreeScale)

degreeScale = iterate (noteMap sharp) First

levelChord = map (flip mod chromaticScaleLength)

inversions = map sequenceDegrees . rotations

rotations = reverse . (\list -> map (\n -> (take (length list) . drop (length list -n)) (cycle list)) [1..length list])

sequenceDegrees ds = scanl1 (\x y-> x + mod (y-x) chromaticScaleLength) ds

findChord inputNotes = do
 chordType <- chordTypes
 root <- chromaticScale
 let notes = chordToNotes (chordType root)
 guard (Data.Set.isSubsetOf (Data.Set.fromList (uns inputNotes )) (Data.Set.fromList notes))
 return (chordType root)
 where uns = map canonize

chordTypes = [majorChord, minorChord, diminishedChord, augmentedChord,
              major7thChord, dominant7thChord, minor7thChord, minorMajor7thChord, minor7thFlat5thChord, diminished7thChord, augmentedMajor7thChord]

chordToNotes chord = map snd $ degrees chord

horizontalConcat str1 str2 = unlines $ horizontalConcat' (lines str1) (lines str2)
horizontalConcat' str1 str2 = zipWith (++) str1 str2

deepenListOfListsAndAddEmpties =  addEmpties . deepenListOfLists
deepenListOfLists = map deepenList

deepenList = map (:[])
addEmpties = map ([]:)
addEmptiesToEmpties xs = map (\x -> applyIf (null x) ([]:) x) xs

findIndicess p xs ys = map (\x -> findIndices (p x) xs) ys

demoEquiv string number = length string == (length (show number))

uniqueBy eq l = uniqueBy' l []
  where
    uniqueBy' [] _ = []
    uniqueBy' (y:ys) xs
      | elem_by eq y xs = uniqueBy' ys xs
      | otherwise = y : uniqueBy' ys (y:xs)

elem_by :: (a -> a -> Bool) -> a -> [a] -> Bool
elem_by _ _ [] = False
elem_by eq y (x:xs) = x `eq` y || elem_by eq y xs

applyIf :: Bool -> (a -> a) -> a -> a
applyIf p f v = if p then f v else v

insertAt = (\n x xs -> case splitAt n xs of { (a, b) -> a ++ [x] ++ b })

properGroup f (x:xs) = properGroup' f [x] xs
properGroup f [] = []
properGroup' f buf@(_:_) (x:xs) = if f x (last buf) then properGroup' f (buf++[x]) xs
                                                    else buf : properGroup' f [x] (xs)
properGroup' f buf [] = [buf]


overlay xs ys = map head $ transpose [xs,ys]

hAppend w w2 =   unlines
               $ map concat
               $ transpose
               $  map (\lines ->
                    map (\line ->
                        overlay line (replicate (maximum (map length lines)+1) ' ')
                    ) lines
                  )
               $ (\documents -> map (\document -> overlay document (replicate (maximum (map length documents)) " ")) documents) [lines w,lines w2]


hConcat = foldl1 hAppend