

"""

This program generates interactive streams of numbers and,
in keeping with (but not restricted to) pitch- and beat-class
set theory, can bend them according to your whim,
and express them as music.

How to use this program:

1) Start a single Phraser instrument by entering
any playback, display and score options, in the terminal
in which you started Phraser ("-h" for help).
Two more terminals will open, for input and output.

2) In the input terminal, enter options for any of the available
musical parameters (see the help on them by typing "h").
Dashes are not required before these options.

3) Each of these options may be followed by a set of sub-options
inside square brackets (OPTION [OPTION VALUE ...]) instead of
numerical values. This will launch a sub-number-generator
to control their behaviour. Sub-options may contain sub-sub options,
and so on recursively (OPTION [OPTION [OPTION VALUE ...] ...])
Type "h" inside square brackets for help.

4) New options can be entered while the instance runs, and
multiple instances can be started. They will stay synchronised.

How the options work:

"Repeatable" options take a list of values.
If invoked repeatedly, will cycle through the lists.
A couple of these take lists composed of pairs of values,
where the first value specifies a parameter, and the second
specifies a value which may be written as a number or as
a dash-separated range (OPTION VALUE MINVALUE-MAXVALUE...).

"Boolean" options cycle through the values given
but will only apply to each value-th sequence.
The default of 1 means they always apply.

Otherwise, options take multiple numerical values which
will be cycled through. At least one value is required.
A few of these can also take dash-separated ranges
(OPTION MINVALUE-MAXVALUE...).

"""

###Main launcher

import sys, os, socket, threading, subprocess, readline, time
from  .inphuncs import optparser, itertree
from .outphuncs import work, play

def deepdater(insock, phrases, flag):
    """Write to options on the fly from a separate terminal"""
    while flag.is_set():
        try:
            insock.send(b'Next please')   
            data = insock.recv(1024).decode() or 'q'
        except socket.error:
            data = 'q'
        if data == 'q':
            flag.clear()
            break
        phrases.deepdate(itertree(data))
    

def phrinstance(work_opts, score_opts, playargs, startflag, outsock, insock):
    """Instance of a Phraser instrument"""
    data = insock.recv(1024).decode() or 'q'
    if data == 'q':
        insock.send(b'Close')
        outsock.send(b'Close')            
    else:
        flag = threading.Event()
        flag.set()
        phrases = itertree(data)
        threading.Thread(target=deepdater,
                args=(insock, phrases, flag), name='dater').start()
        startflag.wait()        
        work(flag, phrases, outsock, score_opts, playargs, **work_opts)
        flag.clear()
        insock.send(b'Done')
        insock.recv(1024)
        try:
            outsock.send(b'Close')
        except socket.error:
            pass
       
        
def run(outterm, interm):
    """Start any number of phraser instances"""
    if sys.argv[1:]:
        print(__doc__)
    inst = 0
    terms, phreads = [], []
    startflag = threading.Event()
    startflag.set()
    fluid = None
    while 1:
        if startflag.is_set():
            prompt = (
            "\nEnter playback and score options for one instrument,\n" +
            "or 'M' to initialise multiple instruments,\n"
            "or '-h' for help, or 'q' to quit:\n\n"
            )
        else:
            prompt = (
            "\nEnter playback and score options for this instrument,\n" +
            "or 'S' to start all initialised instruments,\n"
            "or '-h' for help, or 'q' to quit:\n\n"
            )    
        try:
            title, phrargs = optparser(input(prompt).split())
        except (SystemExit, IOError, OSError) as mesg:
            print(mesg)
            continue
        if title == 'q':
            break
        if title == 'M':
            startflag.clear()
            continue
        elif title == 'S':
            startflag.set()
            continue    
        elif not title:
            title = 'Phraser' + str(inst)
        phreads.append(title)
        
        playargs, fluid = play(**phrargs.pop())
        phrargs.append(playargs)
        phrargs.append(startflag)
        for i, script in enumerate((outterm, interm)):
            sock = socket.socket()
            sock.bind(('', 0))
            sock.listen(1)
            y = '+' + str(inst * 10 % 60) if i else '-' + str(60 - inst * 10 % 60)
            titletag = ' input' if i else ' output'  
            terms.append(subprocess.Popen
                ([os.environ['TERM'], '-sb','-T', title + titletag, 
                '-geometry', '+' + str(inst * 80 % 960) + y, 
                '-e', script.__file__, str(sock.getsockname()[1])]))           
            phrargs.append(sock.accept()[0])
        inst += 1         
        threading.Thread(target=phrinstance, args=phrargs, name=title).start()
           
    #cleanup
    for term in terms:
        term.terminate()
        
    while threading.active_count() > 1:
        time.sleep(.1)

    if fluid:
        fluid.terminate()

    for i in phreads:
        try:
            os.remove(os.path.expanduser("~") + '/' + i + 'display.pdf')
        except OSError:
            pass
