import os
import platform
import re
import time
from datetime import datetime
import numpy as np
from numpy import log, abs, sqrt, exp
from scipy import interpolate
import pandas as pd
from matplotlib import pyplot as plt
from functools import lru_cache


from tklib.tkparams import tkParams

from tklib.tksci.tkFit_mxy_flex import tkFit_mxy
from tklib.tksci.tkFit_lib import conv_input, save_data, add_history
from tklib.tksci.tkFit_object import save_fit_config, read_fit_config_from_file

import tklib.tksci.tkoptimize_flex as opt
import peakfit as omodel


#get_cmdで使う変数をapp.config_cmdに設定
def initialize_minimize_func(app):
    globals()["app"] = app

    cfg = app.cfg
    if app.get("config_cmd", None) is None: app.config_cmd = tkParams()
    config = app.config_cmd

    os = platform.system()
    config.script   = 'mobility_polyOS240312.py'

    '''
    if os == 'Linux':
        config.atlas_cmd = cfg.atlas_cmd_linux          #'/home/share/sedatools/bin/deckbuild'
        config.atlas_options = cfg.atlas_options_linux  # '-run -ascii'
    elif os == 'Windows':
        config.atlas_cmd = cfg.atlas_cmd_win            # 'c:/sedatools/exe/deckbuild'
        config.atlas_options = cfg.atlas_options_win    # '-run'
    else:
        config.atlas_cmd = cfg.atlas_cmd_linux          #'/home/share/sedatools/bin/deckbuild'
        config.atlas_options = cfg.atlas_options_linux  # '-run -ascii'
#    config.atlas_options = '-V 5.30.0.R -run -ascii'
    '''

#外部コマンドを使う場合、コマンドラインを返す
def get_cmd(app, xk_all, fit):
    config = app.config_cmd
    config.mode     = 'fit'
    config.infile   = fit.infile
    config.datafile = fit.datafile
    config.fplot = 0

    head = f"python {config.script} {config.mode} {config.infile} {config.datafile}"

    s = ''
    for x in xk_all:
        s += f" {x}"

    return head + s

#iniファイル、フィッティング変数設定ファイルの読み込み
def initialize(app):
    app.cfg = tkParams()

    app.cfg.figsize = [8, 6]

    app.arg_config_file, app.fit_config_file = opt.get_default_config_paths(app, app.cfg)
    app.fit_config_file = None
    if app.arg_config_file: opt.read_default_config_file(app, app.arg_config_file)
    if app.fit_config_file: opt.read_default_fit_file(app, app.fit_config_file)

    return app.cfg

#fittingパラメータ以外をiniファイルに保存
def save_parameter_files(config_path, fitparam_path, fit, cfg, values = None, print_level = 1):
    flag_keys       = ["fhistory", "ffitfiles", "fdisconnect_backward_data"]
    files_keys      = ["templatepath", "datafile", "infile", "calfile", "outfile", "datafile", "historyfile", "fitfile"]
    condiction_keys = ["method", "jac", "nmaxiter", "nmaxcall", "tol", "xatol", "y_scale"]
    data_keys       = ["sample", "Tmin", "Tmax", "Nmin", "Nmax"]
    scan_keys       = ["target_var", "x0", "x1", "nx"]
    if fit.varname is None: fit.varname = []
    exclude_keys    = condiction_keys + flag_keys + fit.varname + files_keys + data_keys + scan_keys \
                      + ["infile", "logfile", "outfile", "parameterfile", "stopfile",
                       "fplot", "mode", "daemon", "nohup"]

    print(f"Save configuration parameters to [{config_path}]")
    cfg.save_parameters(config_path, section = "Preferences", exclude_keys = exclude_keys, IsPrint = print_level)
    cfg.save_parameters(config_path, section = "Files",      keys = files_keys,      IsPrint = print_level)
    cfg.save_parameters(config_path, section = "Data",       keys = data_keys,       IsPrint = print_level)
    cfg.save_parameters(config_path, section = "Flags",      keys = flag_keys,       IsPrint = print_level)
    cfg.save_parameters(config_path, section = "Condition",  keys = condiction_keys, IsPrint = print_level)
    cfg.save_parameters(config_path, section = "Scan",       keys = scan_keys,       IsPrint = print_level)
    
    if print_level:
        print(f"Save fitting parameters to [{fitparam_path}] (save_parameters)")
    fit.save_parameters(fitparam_path, section = "Parameters", keys = fit.varname, vars = fit, values = values, print_level = 0)
#    fit.print_variables(heading = "fitting parameters (save_parameters):")

#iniファイルから、フィッティングパラメータ以外の設定を読み込む。
def read_parameters(path, cfg):
#Parametersセクション以外のみ読み込む
    sectios     = ["Preferences", "Flags", "Files", "Scan", "Condition"]
    ignore_keys = ["logfile", "outfile", "parameterfile", "parameterbkfile", 
                   "file1", "file2", "file3", "file4", "file5",
                   "fplot", "mode", "daemon", "nohup"]
    print(f"Read parameters from [{path}]")
    for section in sectios:
        cfg.read_parameters(cfg.parameterfile, section = section, ignore_keys = ignore_keys)

#fitオブジェクトを構成する
def init_fit(app, cfg, print_level = 1):
#パラメータファイルの読み込み。Parametersセクション以外のみ
    print("In tkminimize_func.read_parameters():")
    read_parameters(cfg.parameterfile, cfg)
    app.force_given_args(cfg)

    print("mode                 : {}".format(cfg.mode))
    print("fplot                : {}".format(cfg.fplot))
    print("fhistory             : {}".format(cfg.fhistory))
    print("ffitfiles            : {}".format(cfg.ffitfiles))
    print("stop path            : {}".format(cfg.stopfile))
    print("infile               : {}".format(cfg.infile))
    print("outfile              : {}".format(cfg.outfile))

    fit = tkFit_mxy(app = app, method = cfg.method, jac = cfg.jac, tol = cfg.tol, xatol = cfg.get("xatol", 1.0e-4),
                nmaxiter = cfg.nmaxiter, nmaxcall = cfg.nmaxcall,
                fplot = cfg.fplot, fdisconnect_backward_data = cfg.get("fdisconnect_backward_data", False),
                print_interval = cfg.print_interval, plot_interval = cfg.plot_interval)
    app.fit = fit

    fit.fitfile_path = app.replace_path(cfg.infile, template = ["{dirname}", getattr(cfg, "fitfile_template", "{filebody}_parameters.csv")])

    print()
    print("config parameter file : {}".format(cfg.parameterfile))
#    print("parameter backup file: {}".format(cfg.parameterbkfile))
    print("fitting parameter file: {}".format(fit.fitfile_path))
    print("history file          : {}".format(cfg.historyfile))

    print()
    print(f"Fitting configuration")
    print(f"  method  : {cfg.method}")
    print(f"  jac     : {cfg.jac}")
    print(f"  tol     : {cfg.tol}")
    print(f"  nmaxiter: {cfg.nmaxiter}")
    print(f"  nmaxcall: {cfg.nmaxcall}")
    print(f"  y_scale  : {cfg.y_scale}")

    fit.configure(print_level = 0, cfg = cfg)
    fit.copy_attributes(cfg, ["T", "infile", "outfile", "historyfile", "fitfile", 
                              "templatepath", "datafile", "atlasfile", "logfile", "Cfile", 
                              "defectaccfile", "defectdonfile", "atlaslogfile", "calfile"])
    fit.copy_attributes(cfg, ["fmax_record", "y_scale"])
    fit.copy_attributes(cfg, ["initialize_method", "surrogate_model", "surrogate_method", 
                              "nparents", "ga_nmaxiter", "nmaxdata", "xatol",
                              "mutation_rate", "n_restarts_optimizer", "alpha"])

# appに定義したフィッティングパラメータ設定をfitにコピー
    fit.copy_attributes(app, ["varname", "unit", "pk_scale", "optid", "linid", "dx", "kmin", "kmax", "kpenalty"])

# フィッティングパラメーターの初期値をfit変数から設定
#    x0 = fit.build_parameter_list(fit.varname, source_obj = cfg)
    if hasattr(app, "x0"): fit.pk = app.x0.copy()
#    fit.print_variables(heading = f"Fitting parameters red from [{fit.fitfile_path}]:")
#    exit()

# Fittingパラメータファイルからフィッティング変数の初期値を設定
    fit.read_parameters(fit.fitfile_path, section = "Parameters", keys = fit.varname)
    nvars = len(fit.pk)
    fit.print_variables(heading = f"Fitting parameters red from [{fit.fitfile_path}]:")
#    exit()

#起動時引数で与えられたパラメータを再設定
    app.force_given_args(cfg)

    fit.initial_simplex = None
    if 'fit' in cfg.mode and (cfg.method == 'nelder-mead' or cfg.method == 'simplex'):
        if fit.dx:
            fit.initial_simplex = fit.build_initial_simplex()
            for i, splx in enumerate(fit.initial_simplex):
                print(f"  {i}: ", end = '')
                for v in splx:
                    print(f" {v:12.4g}", end = '')
                print()

# fitから呼び出される関数の定義
    if cfg.jac == 'func':
        fit.jac = lambda xk: diff1(xk, fit)
    else:
        fit.jac = cfg.jac

    fit.configure(cal_ylist     = lambda xk_all, x_list = fit.x_list, run = cfg.run, print_level = 1: \
                                        cal_ylist(app, xk_all, x_list = x_list, fit = fit, run = run, print_level = print_level),
                  minimize_func = lambda pk, run = cfg.run, print_level = print_level: \
                                        fit._minimize_func(pk, x_list = fit.x_list, y_list = fit.y_list, w_list = fit.w_list, 
                                                            run = cfg.run, print_level = print_level),
                  callback      = lambda pk, run = cfg.run, savefig_path = 'last_fit.png', use_so = False, print_level = 1: \
                                        fit._callback(pk, run = cfg.run, savefig_path = savefig_path, use_so = use_so, print_level = print_level),
                  cal_penalty   = lambda xk_all, run = cfg.run, print_level = 1: \
                                        fit._cal_penalty(xk_all, print_level),
                  cal_fmin      = lambda xk_all, x_list, y_list, w_list, run = cfg.run, print_level = 1: \
                                        fit._cal_fmin(xk_all, x_list, y_list, w_list, run, print_level = print_level),
                  save_parameter_files = lambda config_path, fitparam_path, cfg, values = None, print_level = 1: \
                                        save_parameter_files(config_path, fitparam_path, fit, cfg, values = values, print_level = print_level),
                 )

# fitのグラフ関連変数の定義
    fit.configure(fplot = fit.fplot, plt = plt, iter = 0, xiter = [], yfmin = [], ycal_list = None, yfin_list = None)

    return fit

def read_input_data(app, infile, cfg = None):
    print(f"minimize_func.read_input_data(): Read input data from [{infile}]")
    xlabels, ylabels, xlist, ylist, wlist = omodel.read_input_data(app, infile, cfg)
    if xlabels is None:
        print(f"Error in minimize_func.read_input_data(): Can not read [{infile}]")
        return None, None, None, None, None

    return xlabels, ylabels, xlist, ylist, wlist

#2次元リストの変数x_listから、2次元リストの関数値y_listを返す
def cal_ylist(app, xk_all, x_list, fit = None, run = True, print_level = 1):
    ylist = omodel.cal_ylist(app, xk_all, x_list, fit = fit, run = run, print_level = print_level)
    return ylist
