'''
    coredump with argument, works without.

    search source ``argv'' to find execution path difference.

    $ python3rc1plus dump.py
    {'relerr': 1.0000000000000001e-09, 'abserr': 9.9999999999999995e-07, 'values': (-6.1629758220391547e-33, 3.7982270983039195e-65, -1.047107890216294e-17, 1.0964349337532184e-34, 9.4368957093138306e-16, 8.9055000628465786e-31), 'xUB': 9.4368957093138306e-16, 'fUB': 8.9055000628465786e-31, 'converged': True, 'xn': -6.1629758220391547e-33, 'fLB': 1.0964349337532184e-34, 'xLB': -1.047107890216294e-17, 'fn': 3.7982270983039195e-65}

    $ python3rc1plus dump.py blech
    zsh: illegal hardware instruction (core dumped)  PYTHONSTARTUP= /usr/users/lambert/bin/python/bin/python3.0 dump.py blech 
'''

import sys
from ctypes import *

gslblas = CDLL('libgslcblas.so',mode=RTLD_GLOBAL)
gsl = CDLL('libgsl.so')

def setup(f,argument_types,result_type=c_int):
    f.argtypes = argument_types
    f.restype = result_type
    return f

class gsl_function(Structure):

    '''
        /* Definition of an arbitrary function with parameters */

        struct gsl_function_struct {
          double (* function) (double x, void * params);
          void * params;
        };
        typedef struct gsl_function_struct gsl_function ;
    '''

    def __init__(self,f,extra_args):
        super().__init__()
        self.function = f
        self.params = None

    _fields_ = [('function',CFUNCTYPE(c_double,c_double,c_void_p)),
                ('params',c_void_p)]

def GSL_FUNCTION(f):
    '''
        decorator to produce the gsl_function structure
        see gsl_math.h about the structure.
    '''
    return gsl_function(CFUNCTYPE(c_double,c_double,c_void_p)(f),None)

class gsl_min_fminimizer_type(Structure):

    '''
        typedef struct {
          const char *name;
          size_t size;
          int (*set) (
              void *state, gsl_function * f,
              double x_minimum, double f_minimum,
              double x_lower, double f_lower,
              double x_upper, double f_upper);
          int (*iterate) (
              void *state, gsl_function * f,
              double * x_minimum, double * f_minimum,
              double * x_lower, double * f_lower,
              double * x_upper, double * f_upper);
        } gsl_min_fminimizer_type;
    '''

    _fields_ = [
        ('name',c_char_p),
        ('size',c_size_t),
        ('set',CFUNCTYPE(
            c_int,c_void_p,POINTER(gsl_function),
            c_double, c_double,
            c_double, c_double,
            c_double, c_double,)),
        ('iterate',CFUNCTYPE(
            c_int,c_void_p,POINTER(gsl_function),
            POINTER(c_double), POINTER(c_double),
            POINTER(c_double), POINTER(c_double),
            POINTER(c_double), POINTER(c_double),))        
        ]

class gsl_min_fminimizer(Structure):

    '''
        typedef struct {
          const gsl_min_fminimizer_type * type;
          gsl_function * function ;
          double x_minimum ;
          double x_lower ;
          double x_upper ;
          double f_minimum, f_lower, f_upper;
          void *state;
        } gsl_min_fminimizer;
    '''

    _fields_ = [               # rats, Probably only need enough space
        ('type',gsl_min_fminimizer_type), # but I have spelled it out.
        ('function',POINTER(gsl_function)), # oh well
        ('x_minimum',c_double),
        ('x_lower',c_double),
        ('x_upper',c_double),
        ('f_minimum',c_double),
        ('f_lower',c_double),
        ('f_upper',c_double),
        ('state',c_void_p)
        ]

alloc = setup(gsl.gsl_min_fminimizer_alloc,
              (POINTER(gsl_min_fminimizer_type),),
              POINTER(gsl_min_fminimizer))

free = setup(gsl.gsl_min_fminimizer_free,(POINTER(gsl_min_fminimizer),))

set_with_values = setup(gsl.gsl_min_fminimizer_set_with_values,
                        (POINTER(gsl_min_fminimizer),POINTER(gsl_function),
                         c_double,c_double,
                         c_double,c_double,
                         c_double,c_double,))

iterate = setup(gsl.gsl_min_fminimizer_iterate,(POINTER(gsl_min_fminimizer),))
name = setup(
    gsl.gsl_min_fminimizer_name,(POINTER(gsl_min_fminimizer),),c_char_p)
x_minimum = setup(
    gsl.gsl_min_fminimizer_x_minimum,(POINTER(gsl_min_fminimizer),),c_double)
x_lower = setup(
    gsl.gsl_min_fminimizer_x_lower,(POINTER(gsl_min_fminimizer),),c_double)
x_upper = setup(
    gsl.gsl_min_fminimizer_x_upper,(POINTER(gsl_min_fminimizer),),c_double)
f_minimum = setup(
    gsl.gsl_min_fminimizer_f_minimum,(POINTER(gsl_min_fminimizer),),c_double)
f_lower = setup(
    gsl.gsl_min_fminimizer_f_lower,(POINTER(gsl_min_fminimizer),),c_double)
f_upper = setup(
    gsl.gsl_min_fminimizer_f_upper,(POINTER(gsl_min_fminimizer),),c_double)
test_interval = setup(
    gsl.gsl_min_test_interval,(c_double,c_double,c_double,c_double))

goldensection = POINTER(gsl_min_fminimizer_type).in_dll(
    gsl,'gsl_min_fminimizer_goldensection')
brent = POINTER(gsl_min_fminimizer_type).in_dll(
    gsl,'gsl_min_fminimizer_brent')


class minimize:

    '''
        Prepare to find multiple minima of a function.
        Can call object with its own return to narrow
        the interval of the minimum.
    '''

    def __init__(self,f,abserr=1e-6,relerr=1e-9,iterations=40,algorithm=brent):
        self.tolerance = abserr,relerr
        self.iterations = iterations
        self.f = f
        self.callback = GSL_FUNCTION(f)
        self.minimizer = alloc(algorithm)

    def __call__(self,bounds,iterations=None,abserr=None,relerr=None,):
        mnzr = self.minimizer
        xn,fn,xLB,fLB,xUB,fUB = (
            (bounds[k] for k in 'xn fn xLB fLB xUB fUB'.split())
            #if isinstance(bounds,dict) else bracket(self.f,bounds)
            )

        ################################################################

        if 1 < len(sys.argv):
            set_with_values(mnzr,GSL_FUNCTION(self.f),xn,fn,xLB,fLB,xUB,fUB)
        else:
            set_with_values(mnzr,self.callback,xn,fn,xLB,fLB,xUB,fUB)

        ################################################################

        iterations = iterations or self.iterations
        abserr = abserr or self.tolerance[0]
        relerr = relerr or self.tolerance[1]
        success = 0 # gsl_return_code['SUCCESS']
        for iteration in range(iterations):
            if test_interval(xLB,xUB,abserr,relerr) == success:
                break
            iterate(mnzr)
            xLB,xUB = x_lower(mnzr),x_upper(mnzr)
        xn,fn = x_minimum(mnzr),f_minimum(mnzr)
        fLB,fUB = f_lower(mnzr),f_upper(mnzr)
        return {
            'values': (xn,fn,xLB,fLB,xUB,fUB),
            'converged': not test_interval(xLB,xUB,abserr,relerr),
            'abserr': abserr,
            'relerr': relerr,
            'xn': xn,
            'fn': fn,
            'xLB': xLB,
            'fLB': fLB,
            'xUB': xUB,
            'fUB': fUB,
            }

    def __del__(self):
        free(self.minimizer)



def SQUARE(x,*args,**kwargs):
    return x*x

mn = minimize(SQUARE)

print(mn(dict(xn=4,fn=16,xLB=-20,fLB=400,xUB=40,fUB=1600)))

del mn
