#!/usr/bin/env python

"""This provides functionality for working with matlab.
It's really a wrapper around pymat, with some extra goodies.

Functions:
open        Open a MATLAB session.
close       Close a MATLAB session.
put         Put a variable into MATLAB.
get         Get a variable from MATLAB.
eval        Evaluate a MATLAB statement.
call        Call a MATLAB function.
_safeput
"""

import types
import string

import Numeric
import pymat

# Create wrappers for functions in pymat
for fn in ["open", "close", "put", "get", "eval"]:
    exec("""def %s(*args):
    return(apply(pymat.%s, args))
%s.__doc__ = pymat.%s.__doc__
""" % (fn, fn, fn, fn))
del fn

def call(handle, nretvals, func, *args):
    """call(handle, nretvals, func, *args) -> tuple of return values

    Call a matlab function func.  nretvals is the number of return
    values expected from the function.  handle is the handle returned by
    pymat."""

    inputs = []    # Put the inputs into MATLAB space.
    for arg in args:
        # Only put it if it's not a basic data type.
        if(type(arg) is types.StringType):
            inputs.append("'%s'" % arg)  # put quotes around the string
        elif(type(arg) in [types.IntType, types.FloatType]):
            inputs.append(str(arg))
        else:
            name = "var__%d" % len(inputs)   # make a unique variable name
            _safeput(handle, name, arg)      # put in into matlab
            inputs.append(name)

    outputs = []   # Name the return variables.
    for i in range(nretvals):
        name = "retvar__%s" % len(outputs)
        outputs.append(name)

    if(nretvals):
        fncall = "[%s] = %s(%s)" % (string.join(outputs, ", "), func,
                                    string.join(inputs, ", "))
    else:
        fncall = "%s(%s)" % (func, string.join(inputs, ", "))
    pymat.eval(handle, fncall)
    # How do I check for errors?

    success = 1
    retvals = []
    for output in outputs:
        try:
            retvals.append(pymat.get(handle, output))
        except RuntimeError:  # call not successful, no return value
            success = 0
            break

    # clean up the variables
    pymat.eval(handle, "clear var__*")
    pymat.eval(handle, "clear retvar__*")

    if(not success):
        raise RuntimeError, "matlab %s call unsuccessful" % func
    
    return(tuple(retvals))

class Matlab:
    def __init__(self, *args):
        self.handle = pymat.open(*args)

    def __del__(self):
        pymat.close(self.handle)

    def __getattr__(self, attr):
        # Ignore special attributes.
        if attr[:2] == attr[-2:] == '__':
            raise AttributeError, attr
        class mycall:
            def __init__(self, handle, func):
                self.handle = handle
                self.func = func
            def __call__(self, nretvals, *args):
                return apply(call, (self.handle, nretvals, self.func) + args)
        return mycall(self.handle, attr)

def _safeput(handle, name, value):
    """_safeput(handle, name, value)

    pymat.put, with typechecking.  pymat.put(h, 'x', 5) will segfault."""

    allowed = [types.StringType, types.ListType, types.TupleType,
               Numeric.ArrayType]
    if(type(value) not in allowed):
        raise ValueError, "I cannot put a variable of type %s" % type(value)
    pymat.put(handle, name, value)
