# -*- coding: utf-8 -*-

import os
import sys
import time
import wave
from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, c_char_p, c_double
from ctypes import c_int, c_uint, c_short, c_void_p, byref, sizeof, string_at

DEBUG=0

def win32_utf8_argv():
    from ctypes import POINTER, byref, cdll, c_int, windll
    from ctypes.wintypes import LPCWSTR, LPWSTR
    GetCommandLineW = cdll.kernel32.GetCommandLineW
    GetCommandLineW.argtypes = []
    GetCommandLineW.restype = LPCWSTR
    CommandLineToArgvW = windll.shell32.CommandLineToArgvW
    CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
    CommandLineToArgvW.restype = POINTER(LPWSTR)
    cmd = GetCommandLineW()
    argc = c_int(0)
    argv = CommandLineToArgvW(cmd, byref(argc))
    argnum = argc.value
    sysnum = len(sys.argv)
    result = []
    if argnum > 0:
        start = argnum - sysnum
        for i in range(start, argnum):
            result.append(argv[i].encode('utf-8'))
    return result

class GEOtts_tts_engine_struct(Structure):
    pass
GEOtts_tts_engine=POINTER(GEOtts_tts_engine_struct)

class GEOtts_message_struct(Structure):
    pass
GEOtts_message=POINTER(GEOtts_message_struct)

class GEOtts_callback_types:
    play_speech=CFUNCTYPE(c_int,POINTER(c_short),c_uint,c_void_p)
    process_mark=CFUNCTYPE(c_int,c_char_p,c_void_p)
    word_starts=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
    word_ends=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
    sentence_starts=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
    sentence_ends=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
    play_audio=CFUNCTYPE(c_int,c_char_p,c_void_p)

class GEOtts_callbacks(Structure):
    _fields_=[("play_speech",GEOtts_callback_types.play_speech),
              ("process_mark",GEOtts_callback_types.process_mark),
              ("word_starts",GEOtts_callback_types.word_starts),
              ("word_ends",GEOtts_callback_types.word_ends),
              ("sentence_starts",GEOtts_callback_types.sentence_starts),
              ("sentence_ends",GEOtts_callback_types.sentence_ends),
              ("play_audio",GEOtts_callback_types.play_audio)]

class GEOtts_init_params(Structure):
    _fields_=[("data_path",c_char_p),
              ("config_path",c_char_p),
              ("resource_paths",POINTER(c_char_p)),
              ("callbacks",GEOtts_callbacks),
              ("options",c_uint)]

class GEOtts_voice_info(Structure):
    _fields_=[("language",c_char_p),
              ("name",c_char_p),
              ("gender",c_int)]

class GEOtts_synth_params(Structure):
    _fields_=[("voice_profile",c_char_p),
              ("absolute_rate",c_double),
              ("absolute_pitch",c_double),
              ("absolute_volume",c_double),
              ("relative_rate",c_double),
              ("relative_pitch",c_double),
              ("relative_volume",c_double),
              ("punctuation_mode",c_int),
              ("punctuation_list",c_char_p),
              ("capitals_mode",c_int)]

class GEOtts_message_type:
    text=0
    ssml=1
    characters=2

class GEOtts_voice_gender:
    unknown=0
    male=1
    female=2


class GEOtts_punctuation_mode:
    default=0
    none=1
    all=2
    some=3

class GEOtts_capitals_mode:
    default=0
    off=1
    word=2
    pitch=3
    sound=4

def get_library_location():
    root = os.path.dirname(__file__.decode(sys.getfilesystemencoding()))
    if os.name == 'nt':
        libname = 'GEOtts.dll'
    else:
        libname = 'libGEOtts.so'
    lookup_paths = [root, os.path.join(root, "../../build/linux/lib")]
    for p in lookup_paths:
        libpath = os.path.abspath(os.path.join(p, libname))
        if os.path.exists(libpath):
            break
    else:
        libpath = libname
    if DEBUG:
        print("detected dll/so path: %s " % libpath)
    return libpath

def get_datadir_location():
    root = os.path.dirname(__file__.decode(sys.getfilesystemencoding()))
    lookup_paths = ["GEOtts.langdata", "../../data", "data"]
    for p in lookup_paths:
        datadir = os.path.abspath(os.path.join(root, p))
        if os.path.exists(os.path.join(datadir, "languages")):
            break
    else:
        datadir = lookup_paths[0]
    if DEBUG:
        print("detected data path: %s " % datadir)
    return datadir

def load_tts_library():
    lib=CDLL(get_library_location().encode(sys.getfilesystemencoding()))
    lib.GEOtts_get_version.restype=c_char_p
    lib.GEOtts_new_tts_engine.argtypes=(POINTER(GEOtts_init_params),)
    lib.GEOtts_new_tts_engine.restype=GEOtts_tts_engine
    lib.GEOtts_delete_tts_engine.argtypes=(GEOtts_tts_engine,)
    lib.GEOtts_delete_tts_engine.restype=None
    lib.GEOtts_get_number_of_voices.argtypes=(GEOtts_tts_engine,)
    lib.GEOtts_get_number_of_voices.restype=c_uint
    lib.GEOtts_get_voices.argtypes=(GEOtts_tts_engine,)
    lib.GEOtts_get_voices.restype=POINTER(GEOtts_voice_info)
    lib.GEOtts_get_number_of_voice_profiles.argtypes=(GEOtts_tts_engine,)
    lib.GEOtts_get_number_of_voice_profiles.restype=c_uint
    lib.GEOtts_get_voice_profiles.argtypes=(GEOtts_tts_engine,)
    lib.GEOtts_get_voice_profiles.restype=POINTER(c_char_p)
    lib.GEOtts_are_languages_compatible.argtypes=(GEOtts_tts_engine,c_char_p,c_char_p)
    lib.GEOtts_are_languages_compatible.restype=c_int
    lib.GEOtts_new_message.argtypes=(GEOtts_tts_engine,c_char_p,c_uint,c_int,POINTER(GEOtts_synth_params),c_void_p)
    lib.GEOtts_new_message.restype=GEOtts_message
    lib.GEOtts_delete_message.arg_types=(GEOtts_message,)
    lib.GEOtts_delete_message.restype=None
    lib.GEOtts_speak.argtypes=(GEOtts_message,)
    lib.GEOtts_speak.restype=c_int
    return lib

class SpeechCallback(object):
    sample_size = sizeof(c_short)

    def __call__(self, samples, count, user_data):
        return True

class DebugCallback(SpeechCallback):

    def __init__(self):
        self.counter = 0
        self.datasize = 0
        self.starttime = time.clock()

    def __call__(self, samples, count, user_data):
        self.counter += 1
        size = count*self.sample_size
        self.datasize += size
        kbps = self.datasize / (time.clock() - self.starttime) / 1024
        self.debug(count, size, kbps)
        return True

    def debug(self, count, size, kbps):
        print("speech callback %s time(s) samples: %s, size: %s, %.2f kBps" % (self.counter, count, size, kbps))


class WaveWriteCallback(SpeechCallback):
    def __init__(self, filename, ):
        super(WaveWriteCallback, self).__init__()
        self.wavefile = wave.open(filename, 'wb')
        self.wavefile.setnchannels(1)
        self.wavefile.setsampwidth(self.sample_size)
        self.wavefile.setframerate(16000)
 
    def __call__(self, samples, count, user_data):
        self.wavefile.writeframes(string_at(samples, count*self.sample_size))
        return True

LIB = None

def get_geotts_version():
    global LIB
    if not LIB:
        LIB = load_tts_library()
    return LIB.GEOtts_get_version()

def init_geotts(datadir=get_datadir_location(), callback=DebugCallback()):
    global LIB
    global init_params
    if not LIB:
        LIB = load_tts_library()
    LIB.GEOtts_set_logging(True)

    c_speech_callback = GEOtts_callback_types.play_speech(callback)
    callbacks = GEOtts_callbacks()
    callbacks.play_speech = c_speech_callback
    """
    GEOtts_callbacks(self.__c_speech_callback,
        self.__c_mark_callback,
        cast(None,GEOtts_callback_types.word_starts),
        cast(None,GEOtts_callback_types.word_ends),
        cast(None,GEOtts_callback_types.sentence_starts),
        cast(None,GEOtts_callback_types.sentence_ends),
        cast(None,GEOtts_callback_types.play_audio)),
    """


    init_params = GEOtts_init_params()
    init_params.data_path = datadir
    init_params.callbacks = callbacks

    return LIB.GEOtts_new_tts_engine(byref(init_params))

def get_voices(engine):
    global LIB
    genders = {1: 'male', 2:'female'}
    voices = dict()
    voices_total = LIB.GEOtts_get_number_of_voices(engine)
    first_voice = LIB.GEOtts_get_voices(engine)
    for voiceno in range(voices_total):
        vi = first_voice[voiceno]
        key = vi.name.lower()
        voices[key] = dict(
            no = voiceno,
            name = vi.name,
            lang = vi.language,
            gender = genders[vi.gender]
        )
    return voices

def main():
    global DEBUG

    usage = """
Usage:
  1. GEOtts.py <command>
  2. GEOtts.py [--debug] [-o output.wav] \"text\"
  3. GEOtts.py [--debug] [-o output.wav] -i input.txt

Commands:
  list          - list voices loaded from datadir
  version       - show version of C module and Python API

Options:
  -i --input FILE       file with text encoded in UTF-8
  -o --output FILE      output filename (default: output.wav)
  --voice NAME[,NAME]   choose voices
  --pitch 1.0           tone of voice
  --rate 1.0            speed of speech
  --volume 1.0          speech volume
  --datadir DATADIR     path to language data (default: GEOtts.langdata/)
  --debug               show debug info
"""
    if sys.platform == "win32":
        sys.argv = win32_utf8_argv()

    possible_command = sys.argv[1:2]
    if possible_command == ['version']:
        print("GEOtts %s" % get_geotts_version())
        #print("Python API %s" % __version__)
        sys.exit(0)


    import optparse
    parser = optparse.OptionParser()
    parser.add_option("-i", "--input",
                      help="file with text encoded in UTF-8")
    parser.add_option("-o", "--output", default="output.wav",
                      help="output filename (default: output.wav)")
    parser.add_option("--datadir",
                      help="path to language data (default: GEOtts.langdata/)")
    parser.add_option("--voice", help="choose voices")
    parser.add_option("--pitch", type="float", default=1.0, help="tone of voice")
    parser.add_option("--rate", type="float", default=1.0, help="speed of speech")
    parser.add_option("--volume", type="float", default=1.0, help="speech volume")
    parser.add_option("--debug", help="show debug info", action="store_true")
    opts, args = parser.parse_args()
    if not args and not opts.input:
        print(usage)
        print("\nError: No input text")
        sys.exit(-1)

    if opts.debug:
        DEBUG = 1

    data_path = get_datadir_location()
    if opts.datadir:
        data_path = opts.datadir

    if DEBUG:
        print("GEOtts Python %s" % __version__)
        print("Data Path: %s" % data_path)
        print("")
    if not os.path.exists(data_path):
        print("WARNING: Language resource dir does not exist - ")
        print("    %s" % data_path)
        print("")

    engine = init_geotts(datadir=data_path, callback=WaveWriteCallback(opts.output))
    if not engine:
        if DEBUG:
            raise RuntimeError("GEOtts: engine initialization error")
        else:
            sys.exit("RuntimeError: GEOtts: engine initialization error")
    print("Writing to: %s" % opts.output)

    voices = get_voices(engine)
    voice_selected = []
    voice_selected_name = []
    if opts.voice:
        for v in opts.voice.split(','):
            if v.lower() not in voices:
                print("\nError: Voice %s is unknown." % v)
                sys.exit(-1)
            else:
                if DEBUG:
                    print("Selecting voice: %s" % v)
                voice_selected.append(voices[v.lower()]["no"])
                voice_selected_name.append(voices[v.lower()]["name"])
    if not voice_selected:
        voice_selected = [0]
        voice_selected_name = [voices[v]["name"] for v in voices if voices[v]["no"] == 0]
    elif len(voice_selected) > 1:
        print("\nNote: Sorry, multiple language selection doesn't work yet")
    print("Selected voice: %s" % voice_selected_name[0])
    if possible_command == ['list']:
        if DEBUG:
            print("    Voice     Language  Gender")
        voice_order = sorted(voices.items(), key=lambda x: x[1]["no"])
        voice_order = [v[0] for v in voice_order]
        for i in range(len(voices)):
            voice = voices[voice_order[i]]
            print("  %(name)-16s  %(lang)2s    %(gender)2s " % voice)
        if DEBUG:
            print("Number of voices: %s" % len(voices))
        sys.exit(0)

    lib = LIB

    profiles_total = lib.GEOtts_get_number_of_voice_profiles(engine)
    profiles = []
    first_profile = lib.GEOtts_get_voice_profiles(engine)
    for profno in range(profiles_total):
            profiles.append(first_profile[profno])
    if DEBUG:
        print("Voice Profiles")
        for p in profiles:
            print(" %s" % p)

    text = ""
    if args:
        text += args[0]
    else:
        text += open(opts.input, "rb").read()
    synth_params = GEOtts_synth_params()
    synth_params.voice_profile = profiles[voice_selected[0]]
    synth_params.relative_pitch = opts.pitch
    synth_params.relative_rate = opts.rate
    synth_params.relative_volume = opts.volume

    message = lib.GEOtts_new_message(engine,
                                      text,
                                      len(text),
                                      GEOtts_message_type.text,
                                      byref(synth_params),
                                      None)

    if not message:
        raise RuntimeError("GEOtts: message building error")
    lib.GEOtts_speak(message)
    lib.GEOtts_delete_message(message)

if __name__ == '__main__':
    main()
