# main.py that controls the whole app
# to run: just run bokeh serve --show crossfilter_app in the benchmark-view repo

from random import random
import os

from bokeh.layouts import column
from bokeh.models import Button
from bokeh.models.widgets import Select, MultiSelect, Slider
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc


#### CROSSFILTER PART ##### >>> Module load errors throwing up how to do a relative import ?
from crossview.crossfilter.models import CrossFilter
#from benchmark.loader import load

#### DATA INPUT FROM REST API ######
#from benchmark.loader import load

#### DATA INPUT STRAIGHT FROM PANDAS for test purposes ####
import pandas as pd

##### PLOTTING PART -- GLOBAL FIGURE CREATION ########
# create a plot and style its properties

## gloabl data interface to come from REST API
vasp_data = pd.read_csv('../benchmark/data/francesca_data_head.csv')

p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location='below')
#p.border_fill_color = 'black'
#p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None


#### FORMAT OF DATA SENT TO WIDGET #######

# add a text renderer to out plot (no data yet)
r = p.text(x=[], y=[], text=[], text_color=[], text_font_size="20pt",
           text_baseline="middle", text_align="center")

r2 = p.circle(x=[], y=[])

i = 0

ds = r.data_source

ds2 = r2.data_source

##### WIDGET RESPONSES IN THE FORM OF CALLBACKS ######

# create a callback that will add a number in a random location
def callback():
    global i


    # BEST PRACTICE --- update .data in one step with a new dict
    new_data = dict()
    new_data['x'] = ds.data['x'] + [random()*70 + 15]
    new_data['y'] = ds.data['y'] + [random()*70 + 15]
    new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
    new_data['text'] = ds.data['text'] + [str(i)]
    ds.data = new_data

    i = i + 1


#### The make crossfilter callback

#### make data loading as easy as possible for now straight from
#### the benchmark data csv file not from the API with the decorators

#### TO DO after we see that the crossfilter and new bokeh play nicely
##########: integrate with API and uncomment the decorators and data loader
#@bokeh_app.route("/bokeh/benchmark/")
#@object_page("benchmark")

#### RENDERERS OF WIDGETS #####

def make_bokeh_crossfilter(axis='k-point'):
    """The root crossfilter controller"""
    # Loading the dft data head as a
    # pandas dataframe
    new_data = dict()
#    new_data = load("./benchmark/data/francesca_data_head")
    # use a straight pandas dataframe for now instead and follow the
    # BEST PRACTICE described above basically clean up the data object on each callback.
    # data that will be given back on the callback
    new_data = vasp_data # our data that will be replaced by the API
    global p
    p = CrossFilter.create(df=new_data)
    print (type(p))
    # dont know what Crossfilter class really returns in terms of data but for testnig purposes lets
    # return something that is compatible with the new_data dictionary return in the
    # vanilla example through the global object ds.data
    # for example the x - y coordinates on the plots correspond to mins on the data set in k-point and value fields
#    new_data['x'] = ds2.data['x'] + list(data[axis])
#    new_data['y'] = ds2.data['y'] + list(data['value'])
    # other stuff default as in vanilla callback()
    # for test purposes to see actually what coordinate is getting plotted
    # it is always going to be the same duh beccause only one min exist in the dataset
    # its at x = 6, y = -12 ,
    # SUCESS learnt how to create a custom callback !!! that loads a CSV file and does something with it
#    print ("New data from crossfilter", new_data)
    # finally assign to ds.data
#    ds2.data = new_data





def make_wflow_crossfilter(tags={'element_widget':['Cu', 'Pd', 'Mo'], 'code_widget':['VASP'], 'ExchCorr':['PBE']}):
    """
    demo crossfilter based on pure pandas dataframes that serves a data processing
    workflow that selects inputs from widgets

    args:
     tags: dict of selections by upto 3 widgets

    returns:
     dictionary of crossfiltered dataframes that can further be processed down the workflow
    """

    ## Actual widget controlled inputs ##

    # elements = tags['element']
    # exchanges = tags['ExchCorr']
    # propys = tags['code_widget']

    ## Demo user inputs for testing selects everything in the test csv : max data load ##

    elements = np.unique(vasp_data['element'])
    exchanges = np.unique(vasp_data['exchange'])
    propys = ['B','dB','a0']


    # final dictionary of crossfiltered dataframes
    crossfilts = {}
    # crossfiltering part - playing the role of the "Crossfilter class in bokeh.models"

    for pr in propys:
      for el in elements:
         for ex in exchanges:
            # crossfilter down to exchange and element
            elems = vasp_data[vasp_data['element']==el]
            exchs = elems[elems['exchange']==ex]
            # separate into properties, energy, kpoints
            p = exchs[exchs['property']==pr]
            e = exchs[exchs['property']=='e0']

            ##### *** Accuracy calculation based on default standards *** #####
            # choose reference from dict
            ref_e = expt_ref_prb[el][pr]
            ref_w = wien_ref[el][pr]
            # calculate percent errors on property - ACCURACY CALCULATION based on default standards
            props = [v for v in p['value'] ]
            percs_wien = [ (v - ref_w) / ref_w * 100 for v in p['value']]
            percs_prb = [ (v - ref_e) / ref_e * 100 for v in p['value']]
            kpts = [ k for k in p['k-point']]
            kpts_atom = [ k**3 for k in p['k-point'] ]
            ##### *** Accuracy calculation based on default standards *** #####

            ##### *** Calculate prec_sigma of energy *** #####
            energy = [ v for v in e['value']]
            end= len(energy) - 1
            prec_sigma = [ v - energy[end] for v in energy]

            # make data frame of kpoints, energy, percent errors on property
            if kpts and energy and props:
                NAME = '_'.join([el,ex,pr])
                Rdata =\
                pd.DataFrame({'Kpoints_size':kpts, 'Kpoints_atom_density':kpts_atom, 'Energy':energy, 'Prec_Sigma':prec_sigma , pr:props, 'percent_error_wien':percs_wien, 'percent_error_expt':percs_prb  })
                crossfilts[NAME] = Rdata



def calculate_prec(cross_df, automate= False):
    """
    function that calculates the prec_inf using R
    and returns a fully contructed plottable dataframe

    Args:
     cross_df: pandas dataframe containing the data
     automate: bool, a To do feature to automatically calculate the best fit

    Returns:
     dataframe contining the R added precision values to be
     received most always by the plotting commander.
    """
    import rpy2.robjects as ro
    from rpy2.robjects import pandas2ri
    from rpy2.robjects.packages import importr
    import rpy2.robjects.numpy2ri
    import rpy2.rinterface as rin


    stats = importr('stats')
    base = importr('base')
    # activate R environemnt in python
    rpy2.robjects.numpy2ri.activate()
    pandas2ri.activate()
    # read in necessary elements ofmenu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), ("Item 3", "item_3_value")]
    df = pd.DataFrame({'x': cross_df['Kpoints_atom_density'],
                       'y': cross_df['Energy']})
    ro.globalenv['dataframe']=df

    ### *** R used to obtain the fit on the data to calculate prec_inf *** ###
    # perform regression  - bokeh widgets can be used here to provide the inputs to the nls regression

    # some python to R translation of object names via the pandas - R dataframes
    y = df['y']
    x = df['x']
    l = len(y) - 1  # needed because R indexes list from 1 to len(list)

    # ***WIDGET inputs*** # OR AUTOMATE
    # the slider  inputs on starting point or can be automated also
    l1 = 3
    l2 = 0
    fitover = rin.SexpVector(list(range(l1,l-l2)), rin.INTSXP)

    # numeric entry widget for 'b' is plausible for user to choose best starting guess
    start_guess = {'a': y[l], 'b': 5}
    start=pandas2ri.py2ri(pd.DataFrame(start_guess,index=start_guess))

    # drop down list selection of model
    model = 'y~a*x/(b+x)'

    # Minimize function with weights and selection
    m = \
    stats.nls(model, start = start, algorithm = "port", subset = fitover, weights = x^2, data=base.as_symbol('dataframe'))

    # Estimation of goodness of fit
    g = stats.cor(y[l1:l-l2],stats.predict(m))

    # Report summary of fit, values and error bars
    print( base.summary(m).rx2('coefficients') )

    # Extrapolation value is given by a
    a = stats.coef(m)[1]

    # Calculation of precision
    prec = abs(y-a)

    # test print outs of the data ? how to render onto html like Shiny if necesary ?

    print("We learn that the converged value is: {0} and best precision achieved in the measurement is {1}".format(a, min(abs(prec))))

    cross_df['Energy_Prec_Inf'] = prec

    # close the R environments
    rpy2.robjects.numpy2ri.deactivate()
    pandas2ri.deactivate()

    return (cross_df)

def make_widgets():
    """
    main function that will control the rendering of UI widgets

    """
    pass

    
#### WIDGET CREATIONS ####

# OLD VANILLA
# add a button widget and configure with the call back
# button_basic = Button(label="Press Me")
# button_basic.on_click(callback)
#make_bokeh_crossfilter()


# create a button for Select button for input

#menu = [("Bulk Modulus", "B"), ("B'", "dB"), ("Lattice Constant", "a0")]
#select_property = Select(name="Selection", options=menu, value="B")
#select_property.on_click(make_bokeh_crossfilter(axis=value))


# create a button for make crossfilter app 

button_crossfilter = Button(label="Make Crossfilter")
button_crossfilter.on_click(make_bokeh_crossfilter)

#create a button for crossfilter_workflwo
button_w_crossfilter = Button(label="Make Crossfilter Workflow")
button_w_crossfilter.on_click(make_wflow_crossfilter)

# put the button and plot in a layout and add to the document
curdoc().add_root(column(button_crossfilter, button_w_crossfilter, p))
