module Music.Instrument.GuitarRender where

import Music.Diatonic
import Music.Diatonic.Note
import Music.Diatonic.Degree
import Music.Diatonic.Chord
import Music.Diatonic.Scale
import Data.List
import Data.Maybe
import Data.Char
import Control.Monad
import qualified Data.Set
import Debug.Trace

import Music.Instrument.Guitar (
   findPositionPatterns
  ,getPositionMultiPatternMin
  ,PositionPatternProgression
  ,getPositionPatternMinAdjusted
  ,getPositionPatternHeight
  ,getPositionPatternSpannedFrets
  )
  
import Music.Instrument.Piano
import Music.Instrument.Common (
 ControlAnnotation (..),tuningAndPosToNote,abbreviateNote,horizontalConcat,applyIf,insertAt
 ,NewNotes
 )
 
maxFretHeight = 30

renderGuitarConcept 
 :: (PositionPatternProgression a,NewNotes a)
 => Bool 
 -> ControlAnnotation 
 -> Bool
 -> Bool
 -> Bool
 -> [Note]
 -> a 
 -> Int 
 -> Int
 -> Bool 
 -> Bool
 -> [[Bool]]
 -> Bool
 -> [Char] 
renderGuitarConcept 
 allowOpens 
 controlAnnotation 
 annotateFrets
 firstTuningLast
 orientationVertical
 tuning 
 chord
 maxHeight
 from
 utilizeAllStrings 
 rootNoteLowest
 selectionMask
 renderAllFrets 
 =
   head
 $ renderGuitarChord' renderAllFrets controlAnnotation annotateFrets firstTuningLast orientationVertical tuning maxHeight from positionPatternProgressions 
 where 
 positionPatternProgressions = 
     take maxFretHeight 
   $ findPositionPatterns allowOpens chord tuning maxHeight utilizeAllStrings rootNoteLowest selectionMask
       
renderGuitarChord' renderAllFrets controlAnnotation annotateFrets firstTuningLast orientationVertical tuning maxHeight from positionPatternsProgressions =
    drop from
  $ map (renderGuitarChord'' renderAllFrets controlAnnotation annotateFrets firstTuningLast orientationVertical tuning maxHeight) positionPatternsProgressions

renderGuitarChord'' renderAllFrets controlAnnotation annotateFrets firstTuningLast orientationVertical tuning maxHeight positionPatterns =
    concat 
  $ intersperse "\n" 
  $ renderPositionPatternsRange renderAllFrets annotateFrets firstTuningLast orientationVertical controlAnnotation tuning maxHeight positionPatterns

renderPositionPatternsRange renderAllFrets annotateFrets firstTuningLast orientationVertical controlAnnotation tuning maxHeight positionPatterns' = 
  map (renderPositionPattern renderAllFrets annotateFrets firstTuningLast orientationVertical controlAnnotation tuning minPosition (maxHeight-1)) positionPatterns'
  where 
  minPosition = getPositionMultiPatternMin positionPatterns'

renderPositionPattern renderAllFrets annotateFrets firstTuningLast orientationVertical controlAnnotation tuning from maxHeight positionPattern = 
    applyIf (minPositionAdjusted /= 0 && not renderAllFrets) ((++) ("Fret: " ++ show minPositionAdjusted ++ "\n"))
  $ unlines
  $ renderPositionPattern' renderAllFrets annotateFrets firstTuningLast orientationVertical controlAnnotation tuning from maxHeight positionPattern
  where
  minPositionAdjusted = getPositionPatternMinAdjusted maxHeight positionPattern

renderPositionPattern' 
  renderAllFrets
  annotateFrets 
  firstTuningLast
  orientationVertical
  controlAnnotation
  tuning
  from
  maxHeight
  positionPattern
  = 
  applyIf orientationVertical (map reverse . Data.List.transpose) guitarStringTexts'
  where
  guitarStringTexts' = applyIf annotateFrets (++ (Data.List.transpose fretAnnotations)) guitarStringTexts''
  fretAnnotations = map (overlayStringRight fretAnnotationPadding) fretAnnotations'
  guitarStringTexts'' =
    map (\(pos,stringIndex) -> 
	       renderGuitarString
	       renderAllFrets 
	       (if firstTuningLast then stringIndex else compliment (length tuning) stringIndex)
		   orientationVertical
		   controlAnnotation 
		   from 
		   pos
		   (tuning'!!stringIndex) 
	       positionPatternSpannedFrets
	    )
    (zip (reverse positionPattern) stringIndicies)
  overlayStringRight x y = map last $ Data.List.transpose [x,y]
  fretAnnotationPadding = take maximumFretAnnotationLength (repeat ' ')
  maximumFretAnnotationLength = maximum . map length $ fretAnnotations'
  fretAnnotations' = concat $ intersperse [" "] $ map (map show) $ (consec positionPatternSpannedFrets)
  stringIndicies = [0..]
  guitarStringCount = length positionPattern
  positionPatternSpannedFrets = if renderAllFrets then [0..maximum positionPatternSpannedFrets']
                                                  else positionPatternSpannedFrets'
  positionPatternSpannedFrets' = getPositionPatternSpannedFrets positionPattern maxHeight
  tuning' = reverse tuning


consec (x:xs) = consec' [x] xs
consec [] = [] 
consec' buf@(_:_) (x:xs) = if x - last buf == 1 then consec' (buf++[x]) xs
                                                else buf : consec' [x] (xs)
consec' buf [] = [buf]

firstGap [] = Nothing
firstGap xs = listToMaybe (take 1 $ map fst $ dropWhile (uncurry (==)) $ zip [head xs..] xs)

compliment m n = m - n

renderGuitarString 
 renderAllFrets
 stringIndex 
 orientationVertical
 controlAnnotation
 from
 positionIndices
 stringTuning
 positionPatternSpannedFrets 
 = 
   applyIf (not (positionPatternsGap == Nothing)) (addGap positionPatternsGap)
 $ map (char stringIndex orientationVertical stringTuning positionIndices controlAnnotation) positionPatternSpannedFrets
 where 
 positionPatternsGap = firstGap positionPatternSpannedFrets 
 addGap (Just n) str = insertAt n (gapChar orientationVertical) str

gapChar orientationVertical | orientationVertical = '^'
                            | otherwise = 'S'

char stringIndex orientationVertical stringTuning positionIndices controlAnnotation index 
  | index `elem` positionIndices = fingeringChar stringIndex stringTuning index controlAnnotation
  | otherwise = fretChar orientationVertical index

fingeringChar stringIndex stringTuning positionIndex controlAnnotation = 
  case controlAnnotation of 
    AnnotateNote -> abbreviateNote $ tuningAndPosToNote stringTuning positionIndex
    AnnotateMarking -> fingeringCharUnannotated positionIndex
    AnnotatePositionVertical -> head (show positionIndex)
    AnnotatePositionHorizontal -> head (show stringIndex)

fretChar orientationVertical 0 | orientationVertical = '='
                               | otherwise = '|'
                               
fretChar orientationVertical _ | orientationVertical = '-'
                               | otherwise = '-'

fingeringCharUnannotated 0 = 'o'
fingeringCharUnannotated _ = '*'