import json
from pprint import pprint
import sys
import os
import keywords
import metronome
import  math
from timestamp import formatTimeStamp

JMEP_START_TIMESTAMP = (-100000)
DEBUG_ENABLE = 0

def is_array(var):
    return isinstance(var, (list, tuple))

class JmepParser:
    outFile=''
    inFile=''
    ts = 0.0
    lineNumber = 0
    header = {}
    header['version'] = 1
    header['copyright'] = "JamKazam 2015"

    metroList = []
    preludeList = []
    playList=[]
    syncList = []

    def __init__(self, fileName, ts=JMEP_START_TIMESTAMP, outKeyFile='', lineNumber=0):
        self.inFile = fileName
        self.outFile = outKeyFile
        self.ts = ts
        self.lineNumber = lineNumber
        #self.processFile()

    def is_number(self,s):
        try:
            float(s)
            return True
        except ValueError:
            return False

    def ValidTsFormat(self,t):
        sgn = 0
        if t[0] == '+' or t[0] == '-':
            #is forward relative to current time
            if t[0] == '+':
                sgn = 1
            else:
                sgn = -1

            t = t[1:]

        if self.is_number(t):
            if sgn:
                tVal = float(t) * sgn + self.ts
                return (True, tVal)
            else:
                tVal = float(t)
                return (True, tVal)

        #check if h:m:s:ms
        tt = t.split(':')
        if len(tt) != 4:
            print("Unknown timestamp format  at ",self.inFile,":",self.lineNumber)
            return  (False,0)

        #covert to absolute number of seconds
        tv = float(tt[0])*3600 + float(tt[1])*60 + float(tt[2]) + float(tt[3])/1000.0
        if sgn:
            tVal = tv * sgn + self.ts
            return (True, tVal)
        else:
            tVal = tv
            return (True, tVal)

    def extractNumval(self,nameVal,lineNumer):
        try:
            val = float(nameVal[1])
            return (True,val)
        except:
            print("Invalid name value format '",nameVal,"'  at ",self.inFile,":",lineNumer)
            return  (False,0)

    def extraStringVal(self,nameVal,validator,lineNumber):
        if not is_array(nameVal):
            print("Invalid  value in format '",nameVal,"'  at ",self.inFile,":",lineNumber)
            return  (False,'')
        str = nameVal[1].lower().strip()
        if validator:
            if str not in validator:
                print("Invalid  value in format '",nameVal,"'  at ",self.inFile,":",lineNumber)
                return  (False,'')
        return (True,str)

    def processTimestamp(self,kw):
        mylist = kw.split('@')
        t = mylist[1]
        (val,t) = self.ValidTsFormat(t)
        return (val,t)

    def metronome(self, val, bStart, arg, lineNumber):
        (val,t) = self.processTimestamp(val)
        if not val:
            return False


        ticks = 0
        bpm = 0
        duration = 0
        vol = 100
        meter = 1
        name = 'default'
        pmode='stream'
        play='mono'
        #create tuples of (name,values)

        valid_names = ['default','sine', 'click','kick', 'beep','snare']
        valid_play = ['mono','left','right']
        valid_pmode = ['stream','distributed']

        args = arg.split(',')
        for nameVal in args:
            if '=' not in nameVal:
                print("Invalid name value format '",nameVal,"'  at ",self.inFile,":",lineNumber)
                return  False
            nv = nameVal.split("=")

            n = nv[0].lower().strip()
            b = True
            if n == 'bpm':
                (b,bpm) = self.extractNumval(nv,lineNumber)
            elif n == 'ticks':
                (b,ticks) = self.extractNumval(nv,lineNumber)
            elif n == 'vol':
                (b,vol) = self.extractNumval(nv,lineNumber)
            elif n == 'len':
                (b,duration) = self.extractNumval(nv,lineNumber)
            elif n == 'pmode':
                (b,pmode) = self.extraStringVal(nv,valid_pmode,lineNumber)
            elif n == 'name':
                (b,name) = self.extraStringVal(nv,valid_names,lineNumber)
            elif n == 'play':
                (b,play) = self.extraStringVal(nv,valid_play,lineNumber)
            else:
                print("Invalid name value format '",nameVal,"'  at ",self.inFile,":",lineNumber)
                return  False
            #check if argument was not a number
            if not b:
                return  False

        if duration and ticks:
            print("Cannot specify both len and ticks '",self.inFile,":",lineNumber)
            return  False

        if not bpm:
            print("BPM value must be specified '",self.inFile,":",lineNumber)
            return  False

        if ticks:
            duration = ticks*60/bpm

        tStart = 0
        tStop = 0

        if bStart:
            tStart = t
            tStop = t + duration
        else:
            tStart = t - duration
            tStop = t

        mn = metronome.Metronome(ticks, bpm, tStart, tStop, vol, pmode, name,play, meter)

        #check for time overlap
        for om in self.metroList:
            if mn.doesTimeOverlap(om):
                print("Metronome time range overlap '",nameVal,"'  at ",self.inFile,":",lineNumber)
                return False

        self.metroList.append(mn)
        #newlist = sorted(self.metroList, key=lambda x: x.startTs, reverse=True)
        return  True

    def prelude(self, val, arg, lineNumber):
        (val,t) = self.processTimestamp(val)
        if not val:
            return False
        dt = {}
        t = abs(t)* -1.0
        dt['ts']=  formatTimeStamp(t)
        dt['duration'] = t
        dt['count'] = t
        self.preludeList = []
        self.preludeList.append(dt)
        return  True

    def play(self, val, arg, lineNumber):
        (val,t) = self.processTimestamp(val)
        if not val:
            return False
        if t:
            print("Play time must be 0 '",val,"'  at ",self.inFile,":",lineNumber)
            return False

        pd = {}
        pd['ts']=formatTimeStamp(0)
        pdTrack = ['All']
        pd['tracks']=pdTrack
        self.playList = pd
        return  True

    def syncTs(self, val, arg, lineNumber):
        (val,t) = self.processTimestamp(val)
        if not val:
            return False

        pd = {}
        pd['ts'] = formatTimeStamp(t)
        self.syncList.append(pd)
        return True

    def includeFile(self,val,  arg, lineNumber):
        (val,t) = self.processTimestamp(val)
        if not val:
            return False

        p = JmepParser(arg,t,lineNumber)
        if not p.processFile():
            print("Error processing include file'",val,"'  at ",self.inFile,":",lineNumber)

        #TODO
        #merge the objects
        return

    def setTimeStamp(self, val,arg, lineNumber):
        (val,t) = self.processTimestamp(val)
        if not val:
            return False
        self.ts = t
        return True

    def author(self, val, arg, lineNumber):
        args = arg.split(',')
        for nameVal in args:
            if '=' not in nameVal:
                print("Invalid name value format '",nameVal,"'  at ",self.inFile,":",lineNumber)
                return  False
            nv = nameVal.split("=")

            n = nv[0].lower().strip()
            if n == 'email':
                self.header['email'] = nv[1].strip()
            elif n == 'name':
                self.header['creator'] = nv[1].strip()
            else:
                print("Unknown name value format '",nameVal,"'  at ",self.inFile,":",lineNumber)
        return True

    def generateJson(self):
        if not self.playList:
            #load default play all
            pd = {}
            pd['ts']=formatTimeStamp(0)
            pdTrack = ['All']
            pd['tracks']=pdTrack
            self.playList = pd

        data = {}
        eventData = {}
        data['header']=self.header
        eventList = []
        if self.metroList:
            newlist = sorted(self.metroList, key=lambda x: x.startTs, reverse=False)
            md = []
            for x in newlist:
               y = x.generateStart()
               md.append(y)
               y = x.generateStop()
               md.append(y)

            mt={'metronome': md}
            eventList.append(mt)

            #eventData['metronome'] = md
            #print md
        if self.preludeList:
            mt={'count_down': self.preludeList}
            eventList.append(mt)
            #eventData['count_down'] = self.preludeList
        if self.playList:
            mt={'track_play': [self.playList]}
            eventList.append(mt)
            #eventData['track_play']= self.playList
        if self.syncList:
            mt={'sync': self.syncList}
            eventList.append(mt)
            #eventData['sync'] = self.syncList

        #make events into list
        #l = []
        #l.append(eventData)
        data['Events'] = eventList

        jdata = json.dumps(data)

        pprint(jdata)

        fo = open(self.outFile, 'w')
        fo.write(jdata)
        fo.close()

    def processLine(self, str, lineNum):
        # remove comment part from string
        if '#' in str:
            mylist = str.split('#')
            str = mylist[0].strip();
        #split string into name value pairs
        str.strip()
        #print str
        if not str:
            #empty string
            return True
        #pick of the first word - this is the key
        keyword = str.partition(' ')[0]
        #print 'keyword=',keyword

        args = str.replace(keyword,'')
        keyword = keyword.lower()
        b = False
        if keywords.METRO_START in keyword:
            b = self.metronome(keyword,True,args,lineNum)
        elif keywords.METRO_FIN in keyword:
            b= self.metronome(keyword,False,args,lineNum)
        elif keywords.PRELUDE in keyword:
            b = self.prelude(keyword,args,lineNum)
        elif keywords.PLAY_FILE in keyword:
            return self.play(keyword,args,lineNum)
        elif keywords.EXPLICIT_TIMESTAMP in keyword:
            b =self.setTimeStamp(keyword,args,lineNum)
        elif keywords.AUTHOR in keyword:
            b = self.author(keyword,args,lineNum)
        elif keywords.EXPLICIT_SYNC in keyword:
            b = self.syncTs(keyword,args,lineNum)
        elif keywords.INCLUDE_FILE in keyword:
            b = self.includeFile(keyword,args,lineNum)
        else:
            print("Unknown keyword '",keyword,"' at ",self.inFile,":",lineNum)
            return False
        return b

    def processFile(self):
        content = []
        with open(self.inFile ) as f:
            content = f.readlines()
        content = [x.strip('\n') for x in content]

        #now remove comments portion from lines
        linenum = 1
        for x in content:
            #print x
            self.lineNumber = linenum
            if DEBUG_ENABLE:
                print(linenum, x)
            if not self.processLine(x,linenum):
                return  False
            linenum = linenum + 1

        return True
