import linphone
import time
import threading

DIAL_TONE_FILE = 'freizeichen.wav'
DIAL_TONE_LEN = 5
BUSY_TONE_FILE = 'besetztzeichen.wav'
BUSY_TONE_LEN = 0.96
RINGBACK_TONE = 'weckzeichen.wav'

class PhoneEvent(object):
    RegInProgress,\
    RegSuccessfull,\
    RegReset,\
    CallIncoming,\
    CallRinging,\
    CallAccepted,\
    CallEnded,\
    CallBusy,\
    CallInvalidNumber = range(9)

    @classmethod
    def string(cls, val):
        for k, v in vars(cls).iteritems():
            if v == val:
                return k

class PhoneInterface(object):
    def __init__(self, config, roconfig):
        cbs = {
            'global_state_changed': self.__global_state_changed,
            'registration_state_changed': self.__registration_state_changed,
            'call_state_changed': self.__call_state_changed
        }

        self.__event_cbs = []

        self.__core = linphone.Core.new(cbs, config, roconfig)

        # Set default parameters overriding the ones from the given config file
        self.__core.set_user_agent('FeTAp 615', '0.1')
        self.__core.ringback = RINGBACK_TONE
        self.__core.max_calls = 1
        self.__core.set_call_error_tone(linphone.Reason.Busy, '')
        self.__core.disable_chat(linphone.Reason.None)
        self.__core.echo_cancellation_enabled = False
        self.__core.video_capture_enabled = False
        self.__core.video_display_enabled = False

    def __global_state_changed(self, core, state, msg):
        print 'Global state changed:', state, msg
        # TODO: Do we need events emitted here?
        pass

    def __registration_state_changed(self, core, proxyconf, state, msg):
        print 'Registration state changed:', proxyconf, state, msg
        evt = None
        if state == linphone.RegistrationState.Progress:
            evt = PhoneEvent.RegInProgress
        elif state == linphone.RegistrationState.Ok:
            evt = PhoneEvent.RegSuccessfull
        elif state == linphone.RegistrationState.None:
            evt = PhoneEvent.RegReset

        if evt is not None:
            for cb in self.__event_cbs:
                cb(evt)
        else:
            print 'Unhandled registration state:', linphone.RegistrationState.string(state)

    def __call_state_changed(self, core, call, state, msg):
        print 'Call state changed:', call, state, msg
        evt = None
        if state == linphone.CallState.IncomingReceived:
            evt = PhoneEvent.CallIncoming
        elif state == linphone.CallState.OutgoingRinging:
            evt = PhoneEvent.CallRinging
        elif state == linphone.CallState.Connected:
            evt = PhoneEvent.CallAccepted
        elif state == linphone.CallState.End:
            evt = PhoneEvent.CallEnded
        elif state == linphone.CallState.Error:
            error = call.error_info.reason
            if error == linphone.Reason.Busy:
                evt = PhoneEvent.CallBusy
            elif error == linphone.Reason.NotFound:
                evt = PhoneEvent.CallInvalidNumber
            else:
                evt = PhoneEvent.CallEnded

        if evt is not None:
            for cb in self.__event_cbs:
                cb(evt)
        else:
            print 'Unhandled call state:', linphone.CallState.string(state)
        
    def __pollthread(self):
        while self.__running:
            self.__core.iterate()
            time.sleep(0.02) # Value from example code

    def start(self):
        self.__running = True
        t = threading.Thread(target=self.__pollthread)
        t.start()

    def stop(self):
        self.__running = False

    def add_event_cb(self, cb):
        self.__event_cbs.append(cb)

    def call(self, number):
        self.__core.invite(number)

    def accept_call(self):
        self.__core.accept_call(self.__core.current_call)

    def decline_call(self):
        self.__core.decline_call(self.__core.current_call, linphone.Reason.Busy)

    def end_call(self):
        self.__core.terminate_call(self.__core.current_call)

    def __play_loop_thread(self, filename, length, continous):
        self.__core.play_local(filename)
        # Start replay of file 500ms before the old playing will end
        # to get more or less continous play
        i = 50 if continous else 0
        while self.__playing and self.__running:
            # TODO: Schoener?
            if i == length * 100:
                self.__core.play_local(filename)
                i = 0
            time.sleep(0.01)
            i += 1
        self.__core.stop_ringing()

    def __play_file(self, filename, length, continous=False):
        print 'Playing file:', filename
        self.__playing = True
        t = threading.Thread(target=self.__play_loop_thread, args=(filename, length, continous))
        t.start()

    def play_dial_tone(self):
        self.__play_file(DIAL_TONE_FILE, DIAL_TONE_LEN, True)

    def play_busy_tone(self):
        self.__play_file(BUSY_TONE_FILE, BUSY_TONE_LEN)

    def stop_playing(self):
        self.__playing = False

if __name__ == '__main__':
    def event_cb(evt):
        print 'Got event:', PhoneEvent.string(evt)

    try:
        phone = PhoneInterface('.linphonerc-foo', '.linphonerc')
        phone.add_event_cb(event_cb)
        phone.start()
        i = 0
        while True:
            time.sleep(1)
            i += 1
            if i == 5:
                phone.call('3474')
                #phone.play_busy_tone()
                pass
    except KeyboardInterrupt:
        phone.stop()