import sys
import os
import configparser
import csv
from pprint import pprint
from math import sqrt
import numpy as np
from scipy import optimize
from tkinter import *
from tkinter import ttk
from tkinter import filedialog, messagebox
import matplotlib
matplotlib.use('TkAgg')
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg



#=============================
# 大域変数の定義
#=============================
# フィッティングパラメータ初期値。線形最小二乗の場合は適当
ai0 = [0, 0, 0]
xrange = [0, 10]

# グラフ設定
font_size = 24

# 全体設定
window_size = "500x200"
ini_dir = os.path.abspath(os.path.dirname(__file__))
file_type = [('CSV file', '*.csv')]


# 起動時引数でデータ／設定ファイル名を受け取る
datafile = None
inifile  = None

argv = sys.argv
if len(argv) >= 2:
    path = argv[1]
    datafile = os.path.splitext(path)[0] + '.csv'
    inifile  = os.path.splitext(path)[0] + '.prm'

fit_order = 2


#=============================
# functions
#=============================
def eprint(textbox, *args):
    print(*args, end = '')
    textbox.insert('end', *args)

#=============================
# csvファイルの読み込み
#=============================
def read_ini(path, obj):
    config = configparser.ConfigParser()
    if config.read(path):
        obj['x0'] = config.get('data', 'x0')
        obj['x1'] = config.get('data', 'x1')
        obj['inipath'] = path
        return config

    return None


#=============================
# csvファイルの読み込み
#=============================
def read_csv(path, obj):
    i = 0
    x = []
    y = []
    with open(path, "r") as f:
        reader = csv.reader(f)

        for row in reader:
            if i == 0:
                header = row
            else:
                xi = float(row[0])
                x.append(xi)
                y.append(float(row[1]))
            i += 1
        obj['datapath'].set(path)
        obj['savepath'].set(os.path.splitext(path)[0] + '-fit.csv')
        obj['header'] = header
        obj['x'] = x
        obj['y'] = y
        obj['title'].set(path)
        obj['xlabel'].set(header[0])
        obj['ylabel'].set(header[1])

    textbox = obj['outputtext']
    eprint(textbox, "\n")
    eprint(textbox, "CSV data:\n")
    eprint(textbox, "  header: {}\n".format(header))
    eprint(textbox, "  x: {}\n".format(x))
    eprint(textbox, "  y: {}\n".format(y))
    return header, x, y


#=============================
# File=>openメニュー、pathボタン
#=============================
def path_button_click(obj):
    selpath = filedialog.askopenfilename(filetypes = file_type, initialdir = ini_dir)
    obj['datapath'].set(selpath)
    header, x, y = read_csv(selpath, obj)
    obj['x0'].set(min(x))
    obj['x1'].set(max(x))



#=============================
# scipy.optimize()による最小化
#=============================
def lsq_button_click(obj):
    eprint(obj['outputtext'], "\n")
    eprint(obj['outputtext'], "polynomial fit by scipy.optimize() start:\n")

    x = obj['x']
    y = obj['y']
    x0 = obj['x0'].get()
    x1 = obj['x1'].get()
    xf = []
    yf = []
    for i in range(len(x)):
        if x0 <= x[i] <= x1:
            xf.append(x[i])
            yf.append(y[i])

# leastsqの戻り値は、最適化したパラメータのリストと、最適化の結果
    ret = np.polyfit(x, y, deg = obj['fit_order'].get(), full = True)
    ai = ret[0]
    res = sqrt(ret[1][0] / len(x))

    eprint(obj['outputtext'], "  lsq result: ai={}\n".format(ai))
    eprint(obj['outputtext'], "              residual={}\n".format(res))


#=============================
# グラフの表示
#=============================
#表示データの作成
    ncal = 100
    xmin = min(x)
    xmax = max(x)
    xstep = (xmax - xmin) / (ncal - 1)
    xc = []
    yc = []
    for i in range(ncal):
        xi = xmin + i * xstep
        yi = np.poly1d(ai)(xi)
        xc.append(xi)
        yc.append(yi)

#グラフの作成、表示
    fontsize = obj['font_size'].get()
    
    canvas = obj['canvas']
    figure = obj['figure']
    figure.clf()
    ax = figure.add_subplot(111)
    ax.plot(x,  y,  label = 'raw data', marker = 'o', linestyle = 'None')
    ax.plot(xc, yc, label = 'fitted', linestyle = 'dashed')
    ax.set_title(obj['title'].get(), fontsize = fontsize)
    ax.set_xlabel(obj['xlabel'].get(), fontsize = fontsize)
    ax.set_ylabel(obj['ylabel'].get(), fontsize = fontsize)
    ax.legend(fontsize = fontsize)
    ax.tick_params(labelsize = fontsize)
    figure.tight_layout()
    canvas.draw()

def main():
    print('Second-order polynomial lsq')

# Base variables
    obj = {}
    root = Tk()
    root.option_add('*font', ('FixedSys', 14))

    pw = PanedWindow(root, sashwidth = 4)
    pw.pack(expand = True, fill = BOTH)
# Left frame
    left_frame = ttk.Frame(root)
    pw.add(left_frame)
# Right frame
    right_frame = ttk.Frame(root)
    pw.add(right_frame)

# tkinter variabls
    obj['inipath']  = StringVar()
    obj['datapath'] = StringVar()
    obj['savepath'] = StringVar()
    obj['x0']       = DoubleVar(value = xrange[0])
    obj['x1']       = DoubleVar(value = xrange[1])
    obj['title']    = StringVar()
    obj['xlabel']    = StringVar()
    obj['ylabel']    = StringVar()
    obj['font_size'] = IntVar(value = font_size)
    obj['fit_order'] = IntVar(value = fit_order)
    obj['output']    = StringVar()

    root.title('Second-order polynomial lsq')
#    root.resizable(False, False)
#    root.geometry(window_size)
    root.minsize(200, 200)

# Menu
    menu_bar = Menu(root)
    menu_file = Menu(menu_bar, tearoff = 0)
    menu_file.add_command(label='Open', accelerator='Ctrl+O',
                    command = lambda: path_button_click(obj))
    menu_file.add_command(label='exit', accelerator='Alt+E',
                    command = lambda: exit())
    menu_bar.add_cascade(label = 'File', menu = menu_file)
    root.config(menu = menu_bar)
    root.grid()

# Canvas (right)
#    figure = plt.subplots()
    figure = plt.Figure()
#    figure = plt.Figure(figsize=(10, 3), dpi=100)
    canvas = FigureCanvasTkAgg(figure, master = right_frame)
    canvas.get_tk_widget().pack(side = 'bottom', expand = 1)
    canvas._tkcanvas.pack(side = 'bottom', expand = 1)
    obj['figure'] = figure
    obj['canvas'] = canvas

# Ini path frame
    inipath_frame = ttk.Frame(left_frame)
    inipath_label = ttk.Label(inipath_frame, text = 'Ini file path:', padding = (5,2))
    inipath_label.pack(side = 'left')
    inipath_entry = ttk.Entry(
        inipath_frame,
        textvariable = obj['inipath'],
        width = 30
        )
    inipath_entry.pack(side = 'left', expand = True)

    inipath_button = ttk.Button(inipath_frame, text = 'path', 
                    command = lambda: inipath_button_click(obj))
    inipath_button.pack(side = 'left')
    inipath_frame.pack(side = 'top', anchor = 'w')

# Data path frame
    datapath_frame = ttk.Frame(left_frame)
    datapath_label = ttk.Label(datapath_frame, text = 'Data file path:', padding = (5,2))
    datapath_label.pack(side = 'left')
    datapath_entry = ttk.Entry(
        datapath_frame,
        textvariable = obj['datapath'],
        width = 30
        )
    datapath_entry.pack(side = 'left', expand = True)

    datapath_button = ttk.Button(datapath_frame, text = 'path', 
                    command = lambda: path_button_click(obj))
    datapath_button.pack(side = 'left')
    datapath_frame.pack(side = 'top', anchor = 'w')

# Save path frame
    savepath_frame = ttk.Frame(left_frame)
    savepath_label = ttk.Label(savepath_frame, text = 'Save path:', padding = (5,2))
    savepath_label.pack(side = 'left')
    savepath_entry = ttk.Entry(
        savepath_frame,
        textvariable = obj['savepath'],
        width = 30
        )
    savepath_entry.pack(side = 'left', expand = True)

    savepath_button = ttk.Button(savepath_frame, text = 'save', 
                    command = lambda: savepath_button_click(obj))
    savepath_button.pack(side = 'left')
    savepath_frame.pack(side = 'top', anchor = 'w')

# Range frame
    range_frame = ttk.Frame(left_frame)
    range_label = ttk.Label(range_frame, text = 'x range:', padding = (5,2))
    range_label.grid(row = 1, column = 0, sticky = 'w')
    x0_entry = ttk.Entry(
        range_frame,
        textvariable = obj['x0'],
        width = 10)
    x0_entry.grid(row = 1, column = 1)

    range_label2 = ttk.Label(range_frame, text = '-', padding = (5,2))
    range_label2.grid(row = 1, column = 2, sticky = E)
    x1_entry = ttk.Entry(
        range_frame,
        textvariable = obj['x1'],
        width = 10)
    x1_entry.grid(row = 1, column = 3)
    range_frame.pack(side = 'top', anchor = 'w')

# Title / XY label frame
    xylabel_frame = ttk.Frame(left_frame)
    title_label = ttk.Label(xylabel_frame, text = 'Title:')
    title_label.grid(row = 0, column = 0, sticky = 'w')
    title_entry = ttk.Entry(
        xylabel_frame,
        textvariable = obj['title'],
        width = 50)
    title_entry.grid(row = 0, column = 1)

    xlabel_label = ttk.Label(xylabel_frame, text = 'x label:')
    xlabel_label.grid(row = 1, column = 0, sticky = 'w')
    xlabel_entry = ttk.Entry(
        xylabel_frame,
        textvariable = obj['xlabel'],
        width = 50)
    xlabel_entry.grid(row = 1, column = 1)

    ylabel_label = ttk.Label(xylabel_frame, text = 'y label:')
    ylabel_label.grid(row = 2, column = 0, sticky = 'w')
    ylabel_entry = ttk.Entry(
        xylabel_frame,
        textvariable = obj['ylabel'],
        width = 50)
    ylabel_entry.grid(row = 2, column = 1)

    xylabel_frame.pack(side = 'top', anchor = 'w')

# Order frame
    order_frame = ttk.Frame(left_frame)
    order_label = ttk.Label(order_frame, text = 'fit order:', padding = (5,2))
    order_label.pack(side = 'left')
    order_entry = ttk.Entry(
        order_frame,
        textvariable = obj['fit_order'],
        width = 10)
    order_entry.pack(side = 'top')
    order_frame.pack(side = 'top', anchor = 'w')

# Button frame
    button_frame = ttk.Frame(left_frame)
    lsq_button = ttk.Button(button_frame, text = 'lsq',
        command = lambda: lsq_button_click(obj))
    lsq_button.pack(side = 'left')
    exit_button = ttk.Button(button_frame, text = 'exit', command = lambda: exit())
    exit_button.pack(side = 'left')
    button_frame.pack(side = 'top', anchor = 'w')
    
# Font size frame
    fontsize_frame = ttk.Frame(left_frame)
    fontsize_label = ttk.Label(fontsize_frame, text = 'Font size:')
    fontsize_label.pack(side = 'left')
    fontsize_entry = ttk.Entry(
        fontsize_frame,
        textvariable = obj['font_size'],
        width = 10)
    fontsize_entry.pack(side = 'left')
    fontsize_frame.pack(side = 'top', anchor = 'w')
    
# Output text frame
    output_frame = ttk.Frame(left_frame)
    output_text = Text(
        output_frame,
#        textvariable = obj['output'],
        width = 60,
        height = 10)
    output_text.pack(side = 'left')
    output_frame.pack(side = 'top', anchor = 'w', expand = True)
    obj['outputtext'] = output_text


    if inifile is not None:
        if read_ini(inifile, obj):
#            read_csv(datafile, obj)
            pass
    if datafile is not None:
        read_csv(datafile, obj)


    root.mainloop()


if __name__ == '__main__':
    main()
    