#!/usr/bin/env python
# Ingen Interactive Shell
# Copyright 2011-2012 David Robillard <http://drobilla.net>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import os.path
import shlex
import socket
import sys
import time
import re
try:
    import readline
except:
    pass

class Client:
    def __init__(self, uri='unix:///tmp/ingen.sock'):
        print 'Connecting to server %s' % uri
        if uri.startswith('unix://'):
            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.sock.connect(uri[len('unix://'):])
        elif uri.startswith('tcp://'):
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            parsed = re.split('[:/]', uri[len('tcp://'):])
            addr = (parsed[0], int(parsed[1]))
            self.sock.connect(addr)

    def __del__(self):
        self.sock.close()

    def msgencode(self, msg):
        if sys.version_info[0] == 3:
            return bytes(msg, 'utf-8')
        else:
            return msg

    def send(self, msg):
        self.sock.send(self.msgencode(msg))
        #response = self.sock.recv(1024)
        # if response != self.msgencode('OK'):
        #     print('Error: %s' % response)
        #     return False
        # else:
        #     return True
        
    def put(self, path, body):
        return self.send('''
[]
 	a patch:Put ;
 	patch:subject <%s> ;
 	patch:body [
%s
] .
''' % (path, body))

    def set(self, path, body):
        return self.send('''
[]
	a patch:Set ;
	patch:subject <%s> ;
	patch:body [
%s
	] .
''' % (path, body))

    def connect(self, tail, head):
        return self.send('''
[]
	a patch:Put ;
	patch:subject <%s> ;
	patch:body [
		a ingen:Edge ;
		ingen:tail <%s> ;
		ingen:head <%s> ;
	] .
''' % (os.path.commonprefix([tail, head]), tail, head))

    def disconnect(self, tail, head):
        return self.send('''
[]
	a patch:Delete ;
	patch:body [
		a ingen:Edge ;
		ingen:tail <%s> ;
		ingen:head <%s> ;
	] .
''' % (tail, head))

    def delete(self, path):
        return self.send('''
[]
	a patch:Delete ;
	patch:subject <%s> .
''' % (path))

def print_usage():
    print('''Usage: ingenish [OPTION]... [COMMAND [ARGUMENT]...]

A command line interface to an Ingen server.  A command can be given directly
on the command line, or when run with no arguments an interactive shell is
launched.

Options:
    -s ADDRESS    The address of the Ingen server.  Default is the local server
                  at unix:///tmp/ingen.sock but remote servers can be used with
                  an address like tcp:///my-ingen-server-host:16180

Commands:
    put PATH TURTLE_FRAGMENT
    set PATH TURTLE_FRAGMENT
    connect TAIL_PATH HEAD_PATH
    disconnect TAIL_PATH HEAD_PATH
    delete PATH
    help
    exit

Paths are UNIX-style paths with strict LV2 symbol components, e.g. /foo/bar_2.
Turtle fragments are used verbatim as the body of blank nodes, the syntax is
identical to the descriptions in Ingen patch files.

Example:
    ingenish put /left_in 'a lv2:InputPort ; a lv2:AudioPort'
    ingenish put /left_out 'a lv2:OutputPort ; a lv2:AudioPort'
    ingenish put /tone 'a ingen:Node ; ingen:prototype <http://drobilla.net/plugins/mda/Shepard>'
    ingenish put /combo 'a ingen:Node ; ingen:prototype <http://drobilla.net/plugins/mda/Combo>'
    ingenish connect /left_in /tone/left_in
    ingenish connect /tone/left_out /combo/left_in
    ingenish connect /combo/left_out /left_out
    ingenish set /tone/output 'ingen:value 0.7'
''')

def run(cmd):
    if cmd[0] == 'help':
        print_usage()
    elif cmd[0] == 'exit':
        sys.exit(0)
    elif cmd[0] == 'put' and len(cmd) == 3:
        return ingen.put(cmd[1], cmd[2])
    elif cmd[0] == 'set' and len(cmd) == 3:
        return ingen.set(cmd[1], cmd[2])
    elif cmd[0] == 'connect' and len(cmd) == 3:
        return ingen.connect(cmd[1], cmd[2])
    elif cmd[0] == 'disconnect' and len(cmd) == 3:
        return ingen.disconnect(cmd[1], cmd[2])
    elif cmd[0] == 'delete' and len(cmd) == 2:
        return ingen.delete(cmd[1])
    else:
        return False

a = 1
server = 'unix:///tmp/ingen.sock'
if len(sys.argv) > 1:
    if sys.argv[a] == '-s':
        server = sys.argv[a + 1]
        a = a + 2
    elif sys.argv[a][0] == '-':
        print_usage()
        sys.exit(1)

ingen = Client(server)

if len(sys.argv) - a == 0:
    while True:
        try:
            run(shlex.split(raw_input('> ')))
        except (EOFError, KeyboardInterrupt, SystemExit):
            print('')
            break
        except:
            print('error: %s' % sys.exc_info()[0])
else:
    if not run(sys.argv[1:]):
        print_usage()
        sys.exit(1)