import bokeh.plotting as blt
from bokeh.models.glyphs import Text
import pandas as pd
# from bokeh.layouts import row, widgetbox
# from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure, show, ColumnDataSource
import os, sys
from bokeh.models import CustomJS, Select
from bokeh.models.widgets import Button
from io import StringIO
import base64
from bokeh.models.selections import Selection

#os.chdir('/Users/johnc.thomas/Dropbox/crispr/pycharm/bokeh_charts/tst')

test_csv = "index,lfc,p\na,5,1\nb,3,4"
for fn in ('somedat.csv', 'somedat2.csv'):
    if not os.path.isfile(fn):
        with open(fn, 'w') as f:
            f.write(test_csv)

DEBUG = False
def debugprint(x):
    if DEBUG:
        print(x)
    return

# file upload JS from https://github.com/bokeh/bokeh/issues/6096#issuecomment-299002827
JSloadfile = """
function read_file(filename) {
    var reader = new FileReader();
    reader.onload = load_handler;
    reader.onerror = error_handler;
    // readAsDataURL represents the file's data as a base64 encoded string
    reader.readAsDataURL(filename);
}

function load_handler(event) {
    var b64string = event.target.result;
    file_source.data = {'file_contents' : [b64string], 'file_name':[input.files[0].name]};
    file_source.trigger("change");
}

function error_handler(evt) {
    if(evt.target.error.name == "NotReadableError") {
        alert("Can't read file!");
    }
}

var input = document.createElement('input');
input.setAttribute('type', 'file');
input.onchange = function(){
    if (window.FileReader) {
        read_file(input.files[0]);
    } else {
        alert('FileReader is not supported in this browser');
    }
}
input.click();
"""

def open_table(file_io, fn):
    """return a pandas DF from a .csv, .tsv, .txt, or .xlsx

    If an Excel file, the first sheet will be used.
    txt and tsv means tab seperated."""
    if fn.endswith('.xlsx'):
        df = pd.read_excel(file_io, index_col=0)
    else:
        if fn.endswith('.txt') or fn.endswith('.tsv'):
            sep = '\t'
        elif fn.endswith('.csv'):
            sep = ','
        else:
            print("Only .csv, .tsv, .txt or .xlsx recognised.")
            raise TypeError

        df = pd.read_table(file_io, sep=sep, index_col=0)
    return df

file_sources = [ColumnDataSource({'file_contents': [], 'file_name': []}),
                ColumnDataSource({'file_contents': [], 'file_name': []})]
colnames = dict(index=[], lfc=[], p=[], lfc_2=[], p_2=[])
main_source  = ColumnDataSource(colnames)

def file_callback(attr,old,new):

    # check two files have been loaded
    debugprint('file_callback() called')
    fnames = [file_sources[i].data['file_name'] for i in (0,1)]
    if not all(fnames):
        return

    dfs = []
    for file_source in file_sources:
        raw_contents = file_source.data['file_contents'][0]

        prefix, b64_contents = raw_contents.split(",", 1)
        file_contents = base64.b64decode(b64_contents)
        file_io = StringIO(file_contents.decode())
        dfs.append(open_table(file_io, file_source.data['file_name'][0]))

    # for i in len(dfs)-1 if more than 2
    dfs[1].columns = [c+'_2' for c in dfs[1].columns]
    main_source.data = ColumnDataSource(pd.concat(dfs, axis=1, sort=True)).data

    # # below doesn't fix the problem and results in piling up data
    # global main_source
    # main_source = ColumnDataSource(pd.concat(dfs, axis=1, sort=True))

    populate_plots()

# calls the func `file_callback` upon change of the specified attr: 'data'
# I think it's actually the button.callback that modifies file_source.data
# then the above func takes the raw data added to file_source
for fs in file_sources:
    fs.on_change('data', file_callback)

lbutton = Button(label="Upload", button_type="success")
rbutton = Button(label="Upload2", button_type="success")
lbutton.callback = CustomJS(args=dict(file_source=file_sources[0]), code = JSloadfile)
rbutton.callback = CustomJS(args=dict(file_source=file_sources[1]), code = JSloadfile)

TOOLS = 'box_select'
lp = figure(plot_width=400, plot_height=400, tools=TOOLS)
rp = figure(plot_width=400, plot_height=400, tools=TOOLS)

plots = blt.gridplot([[lp, rp],
                  [lbutton, rbutton]])

curdoc().add_root(plots)


def populate_plots():
    debugprint(main_source.data)

    # figures created in the global above, this should be called every time
    # main_source.data is changed by file_callback at least
    lkw = dict(x='lfc', y='p', source=main_source)

    rkw = dict(x='lfc_2', y='p_2', source=main_source)

    for p, kw in (lp, lkw), (rp, rkw):
        p.circle(alpha=0.4, **kw)

        # plot the text labels, the renderer object is captured so that it can be meddled with
        # alpha is set to zero as we want the labels to be invisible to start off with
        renderer = p.text(text='index', text_alpha=0.5, **kw)
        renderer.nonselection_glyph.text_alpha = 0
        # create the glyphs for selection; note direct call of Text(), not p.text()
        text_glyph = Text(text_alpha=1, text_color='red')
        renderer.selection_glyph = text_glyph


DEBUG = True

# # UNCOMMENTING BELOW RESULTS IN FUNCTIONAL CHART
# def fake_file_callback():
#     df1 = pd.read_csv('somedat.csv', index_col=0)
#     df2 = pd.read_csv('somedat2.csv', index_col=0)
#     df2.columns = [c+'_2' for c in df2.columns]
#     main_source.data = ColumnDataSource(pd.concat([df1, df2], axis=1, sort=True)).data
# fake_file_callback()
# populate_plots()


# # printing main_source.data results in the below, same thing whether not it comes from file_callback()
# # (this was different test data to what is written at the top)
# #{'lfc': array([0.2, 0.3, 2. , nan]), 'p': array([1.0e-03, 1.5e-03, 5.0e-06,     nan]), 'lfc_2': array([ 5,  6,  4, 11]), 'p_2': array([1, 2, 5, 1]), 'index': array(['a', 'b', 'c', 'd'], dtype=object)}
# #{'lfc': array([0.2, 0.3, 2. , nan]), 'p': array([1.0e-03, 1.5e-03, 5.0e-06,     nan]), 'lfc_2': array([ 5,  6,  4, 11]), 'p_2': array([1, 2, 5, 1]), 'index': array(['a', 'b', 'c', 'd'], dtype=object)}