#!/usr/bin/env python3

VERSION="0.5.0" # MAJOR.MINOR.PATCH | http://semver.org

from collections import defaultdict
from collections import OrderedDict
from io import open

import zipfile
import wave
import math
import re
import os
import sys
import operator
import struct
import argparse


def parse_commandline():
    parser = argparse.ArgumentParser(prog='sfz2bitwig', description='Convert an sfz instrument into a Bitwig multisample instrument.')
    parser.add_argument('-v', '--version', action='version', version='%(prog)s v{0}'.format(VERSION))
    parser.add_argument('--noloop', default=False, action='store_true', help='disable wav loop point extraction')
    parser.add_argument('--category', default='', help='set category field of generated multisample')
    parser.add_argument('--creator', default='sfz2bitwig', help='set creator field of generated multisample')
    parser.add_argument('--description', default='', help='set description field of generated multisample')
    parser.add_argument('--keywords', default='', nargs='*', help='set keywords field of generated multisample')
    parser.add_argument('sfzfile', nargs='+', help='sfz file(s) to convert')

    return parser.parse_args()


def main():
    args = parse_commandline()

    for fn in args.sfzfile:
        # Convert file
        multisamp = Multisample(category=args.category, creator=args.creator, description=args.description, keywords=args.keywords )
        multisamp.initFromSFZ(fn,args.noloop)
        multisamp.write()

    return


class Multisample(object):
    def __init__(self, name='default', category='', creator='', description='', keywords=None ):
        self.name = name
        self.category = category
        self.creator = creator
        self.description = description
        self.keywords = keywords
        self.samples = []
        pass

    def initFromSFZ(self, sfzfile, noloop=False):
        cur_global_defaults = {}
        cur_control_defaults = {}
        cur_group_defaults = {}
        sfz_opcodes_ignored = defaultdict(int)
        region_count = 0

        print("\nConverting {} to multisample".format(sfzfile))
        sfz = SFZParser(sfzfile)
        #print("Finished parsing {}".format(sfzfile))

        self.name = "{}".format(os.path.splitext(sfzfile)[0])

        for section in sfz.sections:
            sectionName = section[0]
            #print("start section <{}>".format(sectionName))
            if sectionName == "control":
                cur_control_defaults = {}
                for k, v in section[1].items():
                    cur_control_defaults[k] = v
                    if k == "default_path":
                        cur_control_defaults["default_path"] = os.path.join(os.path.dirname(os.path.abspath(sfzfile)),os.path.normpath(v.replace('\\','/')))

                    #print("Set control default: {}={}".format(k,cur_control_defaults[k]))

            elif sectionName == "group":
                cur_group_defaults = {}
                for k, v in section[1].items():
                    cur_group_defaults[k] = v
                    #print("Set group default: {}={}".format(k,v))

            elif sectionName == "global":
                cur_global_defaults = {}
                for k, v in section[1].items():
                    cur_global_defaults[k] = v
                    #print("Set global default: {}={}".format(k,v))

            elif sectionName == "region":
                region_count += 1
                newsample = {}

                # Apply settings with priority global < group < region
                opcodes = dict(cur_global_defaults)
                opcodes.update(cur_group_defaults)
                opcodes.update(section[1])

                for k, v in opcodes.items():
                    #print(" {}={}".format(k,v))
                    if k == "sample":
                        newsample['file'] = os.path.normpath(v.replace('\\','/'))
                        if newsample['file'][0] == '/': # relative path should not contain leading slash
                            newsample['file'] = newsample['file'][1:]
                    elif k == "lokey":
                        newsample['keylow'] = self.sfz_note_to_midi_key(v)
                    elif k == "hikey":
                        newsample['keyhigh'] = self.sfz_note_to_midi_key(v)
                    elif k == "pitch_keycenter":
                        newsample['root'] = self.sfz_note_to_midi_key(v)
                    elif k == "key":
                        newsample['keylow'] = self.sfz_note_to_midi_key(v)
                        newsample['keyhigh'] = self.sfz_note_to_midi_key(v)
                        newsample['root'] = self.sfz_note_to_midi_key(v)
                    elif k == "pitch_keytrack":
                        newsample['track'] = v
                    elif k == "lovel":
                        newsample['velocitylow'] = v
                    elif k == "hivel":
                        newsample['velocityhigh'] = v
                    elif k == "volume":
                        newsample['gain'] = v
                    elif k == "tune":
                        newsample['tune'] = int(v) * 0.01
                    elif k == "loop_mode":
                        if v != 'one_shot':
                            newsample['loopmode'] = 'sustain' # bitwig currently supports off or sustain
                    elif k == "loop_start":
                        newsample['loopstart'] = v
                    elif k == "loop_end":
                        newsample['loopstop'] = v
                    elif k == "trigger":
                        newsample['trigger'] = v
                    elif k == "lorand":
                        if float(v) > 0.0:
                            newsample['playlogic'] = "conditional"
                    elif k == "hirand":
                        if float(v) < 1.0:
                            newsample['playlogic'] = "conditional"
                    elif k == "seq_length":
                        if int(v) > 1:
                            newsample['playlogic'] = "conditional"
                    else:
                        sfz_opcodes_ignored["{}={}".format(k,v)] += 1

                defaultPath = cur_control_defaults.get('default_path',os.path.dirname(os.path.abspath(sfzfile)))
                newsampleFullPath = os.path.join(defaultPath,newsample['file'])
                newsample['filepath'] = newsampleFullPath