import os
import numpy as np
from scipy.interpolate import splprep, splev
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.pyplot import cm
import matplotlib.patches as mpatches

from DarkNews import const
from DarkNews import plot_tools as pt

from ToyAnalysis import analysis
from ToyAnalysis import analysis_decay
from ToyAnalysis import fourvec as fv
from ToyAnalysis import toy_logger

import importlib.resources as resources

###########################

def plot_all_rates(df, case_name, Nevents=None, truth_plots=False, title=None, path=None, loc=''):

    toy_logger.info("Plot MiniBooNE signal in {PATH_MB}")
    if path:
        PATH_MB = path
    else:
        PATH_MB = Path(f'plots/{case_name}_miniboone/')

    if not os.path.exists(PATH_MB):
        os.makedirs(PATH_MB)

    # plot titles
    if not title:
        title = case_name

    # get observables at MiniBooNE
    bag_reco_MB = analysis_decay.decay_selection(
                                                analysis.compute_spectrum(df, EVENT_TYPE='both'),
                                                experiment='miniboone',
                                                l_decay_proper_cm=df.attrs['N5_ctau0'])

    batch_plot_signalMB(bag_reco_MB, PATH_MB, BP=case_name, title=title, NEVENTS=Nevents, loc=loc)

    # plot true variables for MiniBooNE
    if truth_plots:
        batch_plot(df, Path(f'{PATH_MB}/truth_level_plots/'), title=title)

def batch_plot_signalMB(obs, PATH, title='Dark News', Nevents= None, loc='', prefix=''):

    if Nevents is not None:
        total_Nevent_MB = Nevents*(1/obs['reco_eff'][0])
    else:
        total_Nevent_MB = obs['reco_w'].sum()
    print(f"MB events: {total_Nevent_MB:.2g}")

    #################### HISTOGRAMS 1D - STACKED ####################################################
    histogram1D_data_stacked(Path(PATH)/f"{prefix}_1D_Enu_data_stacked", obs, r"$E_{\rm \nu}/$GeV", title,
        varplot='reco_Enu', tot_events=total_Nevent_MB, loc=loc)
    histogram1D_data_stacked(Path(PATH)/f"{prefix}_1D_Evis_data_stacked", obs, r"$E_{\rm vis}/$GeV", title,
        varplot='reco_Evis', tot_events=total_Nevent_MB, loc=loc)
    histogram1D_data_stacked(Path(PATH)/f"{prefix}_1D_costheta_data_stacked", obs, r"$\cos\theta$", title,
        varplot='reco_costheta_beam', tot_events=total_Nevent_MB, loc=loc)

def batch_plot_signalMB_bf(obs, PATH, title='Dark News', NEVENTS=1, kde=False, BP = "",loc=''):

    #################### HISTOGRAMS 1D - STACKED ####################################################
    histogram1D_data_stacked(PATH/BP/"1D_Enu_data_stacked", obs, r"$E_{\rm \nu}/$GeV", title,
        varplot='reco_Enu', tot_events=NEVENTS,loc=loc)
    histogram1D_data_stacked(PATH/BP/"1D_Evis_data_stacked", obs, r"$E_{\rm vis}/$GeV", title,
        varplot='reco_Evis', tot_events=NEVENTS,loc=loc)
    histogram1D_data_stacked(PATH/BP/"1D_costheta_data_stacked", obs, r"$\cos\theta$", title,
        varplot='reco_costheta_beam', tot_events=NEVENTS,loc=loc)

# Function for obtaining the histogram data for the simulation at MiniBooNE
def get_histogram1D(obs, NEVENTS=1, varplot='reco_Evis', get_bins=False,loc='../'):

    if varplot=='reco_Enu':
        TMIN, TMAX, nbins, tot_events = 0.2, 1.5, 10, NEVENTS*(obs['reco_eff'][0])
    elif varplot=='reco_Evis':
        TMIN, TMAX, nbins, tot_events = 0.1, 1.25, 10, NEVENTS*(obs['reco_eff'][0])
    elif varplot=='reco_angle':
        TMIN, TMAX, nbins, tot_events = -1.0, 1.0, 10, NEVENTS*(obs['reco_eff'][0])
    else:
        toy_logger.error('That is not a correct variable!')
        return 1

    coherent = (obs['scattering_regime'] == 'coherent')
    pel = (obs['scattering_regime'] == 'p-el')

    HC = (obs['helicity'] == 'conserving')
    HF = (obs['helicity'] == 'flipping')

    if varplot=='reco_Evis':

        # miniboone nu data for bins
        Enu_binc, _ = np.loadtxt(loc+"aux_data/miniboone_2020/Evis/data_Evis.dat", unpack=True)
        nbins=np.size(Enu_binc)
        Enu_binc *= 1e-3
        binw_enu = 0.05*np.ones((nbins))
        bin_e = np.append(0.1, binw_enu/2.0 + Enu_binc)

        hist_co = np.histogram(obs[varplot][coherent & HC], weights=obs['reco_w'][coherent & HC], bins=bin_e, density = False, range = (TMIN,TMAX) )
        hist_inco = np.histogram(obs[varplot][pel & HC], weights=obs['reco_w'][pel & HC], bins=bin_e, density = False, range = (TMIN,TMAX) )

        norm=np.sum(hist_inco[0]+hist_co[0])/tot_events

        h_co = hist_co[0]/norm
        h_inco = hist_inco[0]/norm
        h_tot = h_co + h_inco
        h_bins = hist_co[1]

    elif varplot=='reco_Enu':

        # miniboone nu data for bins
        bin_e = np.loadtxt(loc+"aux_data/miniboone_2020/Enu/bin_edges.dat")
        bin_w = (bin_e[1:] - bin_e[:-1])
        units = 1e3 # from GeV to MeV

        hist_co = np.histogram(obs[varplot][coherent & HC], weights=obs['reco_w'][coherent & HC], bins=bin_e, density = False, range = (TMIN,TMAX) )
        hist_inco = np.histogram(obs[varplot][pel & HC], weights=obs['reco_w'][pel & HC], bins=bin_e, density = False, range = (TMIN,TMAX) )

        norm = np.sum(hist_co[0]+hist_inco[0])/tot_events*bin_w*units

        h_co = hist_co[0]/norm
        h_inco = hist_inco[0]/norm
        h_tot = h_co + h_inco
        h_bins = hist_co[1]

    elif varplot=='reco_angle':

        # miniboone nu data for bins
        bincost_e = np.linspace(-1,1,21)

        hist_co = np.histogram(np.cos(obs['reco_theta_beam']*np.pi/180)[coherent & HC], weights=obs['reco_w'][coherent & HC], bins=bincost_e, density = False, range = (TMIN,TMAX) )
        hist_inco = np.histogram(np.cos(obs['reco_theta_beam']*np.pi/180)[pel & HC], weights=obs['reco_w'][pel & HC], bins=bincost_e, density = False, range = (TMIN,TMAX) )

        norm=np.sum(hist_inco[0]+hist_co[0])/tot_events

        h_co = hist_co[0]/norm
        h_inco = hist_inco[0]/norm
        h_tot = h_co + h_inco
        h_bins = hist_co[1]

    if get_bins:
        return [h_tot, h_co, h_inco, h_bins]
    else:
        return [h_tot, h_co, h_inco]

def get_data_MB(varplot='reco_Evis',loc='../'):

    if varplot=='reco_Evis':
        _, data = np.loadtxt(loc+"aux_data/miniboone_2020/Evis/data_Evis.dat", unpack=True)
        _, bkg = np.loadtxt(loc+"aux_data/miniboone_2020/Evis/bkg_Evis.dat", unpack=True)
        signal = data - bkg
        sys_signal = 0.1
        sys_bkg = 0.1

    elif varplot=='reco_Enu':
        # miniboone nu data 2020
        _, data = np.loadtxt(loc+"aux_data/miniboone_2020/Enu/data.dat", unpack=True)
        _, bkg = np.loadtxt(loc+"aux_data/miniboone_2020/Enu/constrained_bkg.dat", unpack=True)
        _, error_low = np.loadtxt(loc+"aux_data/miniboone_2020/Enu/lower_error_bar_constrained_bkg.dat", unpack=True)
        signal = data - bkg
        sys_bkg = (bkg - error_low)/bkg
        sys_signal = 0.1
        bin_e = np.loadtxt(loc+"aux_data/miniboone_2020/Enu/bin_edges.dat")
        bin_w = (bin_e[1:] - bin_e[:-1])
        signal *= bin_w*1e3
        bkg *= bin_w*1e3

    elif varplot=='reco_angle':
        _, data = np.loadtxt(loc+"aux_data/miniboone_2020/cos_Theta/data_cosTheta.dat", unpack=True)
        _, bkg = np.loadtxt(loc+"aux_data/miniboone_2020/cos_Theta/bkg_cosTheta.dat", unpack=True)
        signal = data - bkg
        sys_signal = 0.1
        sys_bkg = 0.1

    return [signal,bkg,sys_signal,sys_bkg]

# Main plotting function for signal at MiniBooNE (stacked histograms)
def histogram1D_data_stacked(plotname, df, XLABEL, TITLE, varplot='reco_costheta_beam', tot_events  = 1.0, rasterized=True,loc='../'):

    # Masks
    coherent = (df['scattering_regime'] == 'coherent')
    pel = (df['scattering_regime'] == 'p-el')
    HC = (df['helicity'] == 'conserving')
    HF = (df['helicity'] == 'flipping')

    # identifiers
    cases = [coherent & HC, pel & HC, coherent & HF, pel & HF]
    case_names = [r"coherent conserving", r"p-el conserving", r"coherent flipping", r"p-el flipping"]
    case_shorthands = [r"coh HC", r"incoh HC", r"coh HF", r"incoh HF"]
    colors=['dodgerblue','lightblue', 'violet', 'pink']

    nevents = []
    legends = []
    tot_samples = np.size(df['reco_w'])
    for i in range(4):
        this_n_events = int(round(np.sum(df['reco_w'][cases[i]])/np.sum(df['reco_w'])*tot_events))
        nevents.append(this_n_events)
        legends.append(f'{case_shorthands[i]} ({this_n_events} events)')

    fsize = 10
    fig = plt.figure()
    ax = fig.add_axes(pt.std_axes_form, rasterized=rasterized)
    ax.patch.set_alpha(0.0)

    #####################
    # MiniBooNE data
    if varplot=='reco_Evis':

        # miniboone nu data
        bin_c, data_MB_enu_nue = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "Evis_data.dat"), unpack=True)
        _, data_MB_bkg = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "Evis_bkg.dat"), unpack=True)
        bin_c *= 1e-3
        bin_w = 0.05*bin_c/bin_c
        bin_e = np.append(0.1, bin_w/2.0 + bin_c)
        units = 1

        data_plot(ax,\
                    bin_c,
                    bin_w,
                    (data_MB_enu_nue-data_MB_bkg),
                    (np.sqrt(data_MB_enu_nue)),
                    (np.sqrt(data_MB_enu_nue)))

    elif varplot=='reco_Enu':

        # miniboone nu data 2020
        _, data_MB = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "Enu_data.dat"), unpack=True)
        _, data_MB_bkg = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "Enu_constrained_bkg.dat"), unpack=True)
        _, MB_bkg_lower_error_bar = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "Enu_lower_error_bar_constrained_bkg.dat"), unpack=True)
        bin_e = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "Enu_bin_edges.dat"))

        data_MB = data_MB[:-1]
        bin_e = bin_e[:-1]
        data_MB_bkg = data_MB_bkg[:-1]
        MB_bkg_lower_error_bar = MB_bkg_lower_error_bar[:-1]
        bin_w = (bin_e[1:] - bin_e[:-1])
        bin_c = bin_e[:-1] + bin_w/2

        units = 1e3*bin_w # from GeV to MeV

        data_MB_enu_nue = (data_MB - data_MB_bkg)*units
        error_bar = np.sqrt( ((data_MB_bkg - MB_bkg_lower_error_bar)*units)**2
                                + np.sqrt(data_MB**2*units) )

        data_plot(ax,\
                    bin_c,
                    bin_w,
                    data_MB_enu_nue/units,
                    error_bar/units,
                    error_bar/units)

    elif varplot=='reco_costheta_beam':

        # miniboone nu data
        bin_c, data_MB_cost_nue = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "cosTheta_data.dat"), unpack=True)
        _, data_MB_bkg = np.genfromtxt(resources.open_text("ToyAnalysis.include.miniboone_2020", "cosTheta_bkg.dat"), unpack=True)
        bin_w = np.ones(len(bin_c))*0.1
        bin_e = np.linspace(-1,1,21)
        units = 1

        data_plot(ax,
                bin_c,
                bin_w,
                (data_MB_cost_nue-data_MB_bkg),
                np.sqrt(data_MB_cost_nue),
                np.sqrt(data_MB_cost_nue))

    df['reco_w'] = df['reco_w']/np.sum(df['reco_w'])*tot_events

    hists = []
    htotal = np.zeros(len(bin_w))
    handles =[]
    for i in range(4):
        # if nevents[i] > 1e-3*tot_events:
        case = cases[i]
        h, bins =  np.histogram(df[varplot][case], weights=df['reco_w'][case], bins=bin_e)
        h /= units
        ax.bar( bins[:-1], h, bottom=htotal, width=bin_w, label=legends[i],
                ec=None, fc=colors[i], alpha=0.8, align='edge', lw = 0.0, rasterized=rasterized)
        hists.append(h)
        htotal += h
        ax.step(np.append(bins[:-1],10e10),
                np.append(htotal, 0.0),
                where='post',
                c='black', lw = 0.5,rasterized=rasterized)
        handles.append(mpatches.Patch(facecolor=colors[i], edgecolor='black', lw=0.5, label=legends[i]))

    ax.set_title(TITLE, fontsize=0.8*fsize)
    # ax.legend(frameon=False, loc='best')
    ax.legend(handles=handles, frameon=False, loc='best')
    ax.set_xlabel(XLABEL,fontsize=fsize)
    ax.set_xlim(np.min(bin_e),np.max(bin_e))

    if varplot=='reco_Enu':
        ax.set_ylim(0,ax.get_ylim()[1]*1.1)
        ax.set_ylabel(r"Excess events/MeV",fontsize=fsize)
    else:
        ax.set_ylim(-20,ax.get_ylim()[1]*1.1)
        ax.set_ylabel(r"Excess events",fontsize=fsize)
    pt.std_savefig(fig, plotname, dpi=400)

def errorband_plot(ax, X, BINW, DATA, ERRORLOW, ERRORUP, band=False, **kwargs):
    # ax.step(X, DATA, where='mid', **kwargs)
    ax.fill_between(X, DATA-ERRORLOW, DATA+ERRORUP, step='mid', alpha=0.3, **kwargs)

def data_plot(ax, X, BINW, DATA, ERRORLOW, ERRORUP, band=False, **kwargs):
    ax.errorbar(X, DATA, yerr= np.array([ERRORLOW,ERRORUP]), xerr = BINW/2.0, \
                            marker="o", markeredgewidth=0.5, capsize=1.0,markerfacecolor="black",\
                            markeredgecolor="black",ms=2, color='black', lw = 0.0, elinewidth=0.8, zorder=10, **kwargs)

def histogram1D(plotname, obs, w, TMIN, TMAX,  XLABEL, TITLE, nbins, regime=None, colors=None, legends=None, rasterized = True):

    fsize = 10
    fig = plt.figure()
    ax = fig.add_axes(pt.std_axes_form, rasterized=rasterized)
    ax.patch.set_alpha(0.0)

    # normalize
    w = w/np.sum(w)
    # colors =
    nregimes = len(regime)
    bin_e = np.linspace(TMIN,TMAX, nbins+1, endpoint=True)
    bin_w = (bin_e[1:] - bin_e[:-1])
    if regime and not legends:
        legends = [f'case {i}' for i in range(nregimes)]
    if regime and not colors:
        color = cm.rainbow(np.linspace(0, 1, n))
        colors = [c for c in color]

    if regime:
        htotal = np.zeros((nbins))
        nregimes = len(regime)
        for i in range(np.shape(regime)[0]):
            case = regime[i]
            h, bins =  np.histogram(obs[case], weights=w[case], bins=bin_e)

            ax.bar( bins[:-1], h, bottom=htotal, label=legends[i], width=bin_w,
                    ec=None, facecolor=colors[i], alpha=0.8, align='edge', lw = 0.0, rasterized=rasterized)
            ax.step(np.append(bins[:-1],10e10),
                    np.append(htotal, 0.0),
                    where='post',
                    c='black', lw = 0.5,
                    rasterized=rasterized)
            htotal += h
    else:
        h, bins =  np.histogram(obs, weights=w, bins=nbins, range = (TMIN,TMAX))
        ax.bar( bins[:-1], h, width=bin_w,
                    ec=None, fc='indigo', alpha=0.8, align='edge', lw = 0.0, rasterized=rasterized)

    ax.set_title(TITLE, fontsize=0.8*fsize)
    ax.legend(frameon=False, loc='best')
    ax.set_xlabel(XLABEL,fontsize=fsize)
    ax.set_ylabel(r"PDF",fontsize=fsize)

    ax.set_xlim(TMIN,TMAX)
    ax.set_ylim(0.0,ax.get_ylim()[1]*1.1)
    pt.std_savefig(fig, plotname, dpi=400)
    plt.close()

def histogram2D(plotname, obsx, obsy, w,  xrange=None, yrange=None,  xlabel='x',  ylabel='y', title="Dark News", nbins=20, logx=False, logy=False):

    fsize = 11

    fig, ax = pt.std_fig(ax_form = [0.15,0.15,0.78,0.74])

    if logx:
        obsx = np.log10(obsx)
    if logy:
        obsy = np.log10(obsy)

    if not xrange:
        xrange = [np.min(obsx),np.max(obsx)]
    if not yrange:
        yrange = [np.min(obsy),np.max(obsy)]

    bar = ax.hist2d(obsx, obsy, bins=nbins, weights=w, range=[xrange,yrange],cmap="Blues",density=True)

    ax.set_title(title, fontsize=fsize)
    cbar_R = fig.colorbar(bar[3],ax=ax)
    cbar_R.ax.set_ylabel(r'a.u.', rotation=90)

    ax.set_xlabel(xlabel,fontsize=fsize)
    ax.set_ylabel(ylabel,fontsize=fsize)
    pt.std_savefig(fig, plotname, dpi=400)
    plt.close()

def batch_plot(df, PATH, title='Dark News'):

    # regimes
    coherent = (df['scattering_regime'] == 'coherent')
    pel = (df['scattering_regime'] == 'p-el')
    HC = (df['helicity'] == 'conserving')
    HF = (df['helicity'] == 'flipping')
    cases = [coherent & HC, pel & HC, coherent & HF, pel & HF]
    case_names = [r"coherent conserving", r"p-el conserving", r"coherent flipping", r"p-el flipping"]
    case_shorthands = [r"coh HC", r"incoh HC", r"coh HF", r"incoh HF"]
    colors=['dodgerblue','lightblue', 'violet', 'pink']
    regimes = cases
    args = {'regime': cases, 'colors': colors, 'legends': case_shorthands, 'rasterized': True, }

    if not os.path.exists(PATH):
        os.mkdir(PATH)

    # some useful definitions for four momenta
    for i in range(4):
        df['P_decay_ellell',i] = df['P_decay_ell_minus',f'{i}']+df['P_decay_ell_plus',f'{i}']

    # weights
    w     = df['w_event_rate','']
    w_pel = df['w_event_rate',''][pel]
    w_coh = df['w_event_rate',''][coherent]

    # variables
    df['E_N']   = df['P_decay_N_parent','0']
    df['E_Z']   = df['P_decay_ell_minus','0'] + df['P_decay_ell_plus','0']
    df['E_lp']  = df['P_decay_ell_plus','0']
    df['E_lm']  = df['P_decay_ell_minus','0']
    df['E_tot'] = df['E_lm'] + df['E_lp']
    df['E_asy'] = (df['E_lp'] - df['E_lm'])/(df['E_lp'] + df['E_lm'])
    df['E_Had'] = df['P_recoil','0']

    df['M_had'] = fv.df_inv_mass(df['P_recoil'], df['P_recoil'])
    df['Q2'] = -(2*df['M_had']**2-2*df['E_Had']*df['M_had'])

    df['costheta_N']   = fv.df_cos_azimuthal(df['P_decay_N_parent'])
    df['costheta_nu']  = fv.df_cos_azimuthal(df['P_decay_N_daughter'])
    df['costheta_Had'] = fv.df_cos_azimuthal(df['P_recoil'])
    df['inv_mass']     = fv.df_inv_mass(df['P_decay_ellell'], df['P_decay_ellell'])

    df['costheta_sum'] = fv.df_cos_azimuthal(df['P_decay_ellell'])
    df['costheta_lp'] = fv.df_cos_azimuthal(df['P_decay_ell_plus'])
    df['costheta_lm'] = fv.df_cos_azimuthal(df['P_decay_ell_minus'])

    df['costheta_sum_had'] = fv.df_cos_opening_angle(df['P_decay_ellell'], df['P_recoil'])
    df['theta_sum_had'] = np.arccos(df['costheta_sum_had'])*180/np.pi

    df['theta_sum'] = np.arccos(df['costheta_sum'])*180/np.pi
    df['theta_lp'] = np.arccos(df['costheta_lp'])*180/np.pi
    df['theta_lm'] = np.arccos(df['costheta_lm'])*180/np.pi
    df['theta_nu'] = np.arccos(df['costheta_nu'])*180/np.pi

    df['Delta_costheta'] = fv.df_cos_opening_angle(df['P_decay_ell_minus'],df['P_decay_ell_plus'])
    df['Delta_theta'] = np.arccos(df['Delta_costheta'])*180/np.pi

    df['theta_proton'] = np.arccos(df['costheta_Had'][pel])*180/np.pi
    df['theta_nucleus'] = np.arccos(df['costheta_Had'][coherent])*180/np.pi

    df['T_proton'] = (df['E_Had'] - df['M_had'])[pel]
    df['T_nucleus'] = (df['E_Had'] - df['M_had'])[coherent]

    minus_lead = (df['P_decay_ell_minus','0'] >= df['P_decay_ell_plus','0'])
    plus_lead  = (df['P_decay_ell_minus','0'] < df['P_decay_ell_plus','0'])

    df['E_subleading'] = np.minimum(df['P_decay_ell_minus','0'], df['P_decay_ell_plus','0'])
    df['E_leading'] = np.maximum(df['P_decay_ell_minus','0'],df['P_decay_ell_plus','0'])

    df['theta_subleading'] = df['theta_lp']*plus_lead + df['theta_lm']*minus_lead
    df['theta_leading'] = df['theta_lp']*(~plus_lead) + df['theta_lm']*(~minus_lead)

    # CCQE neutrino energy
    df['E_nu_reco'] = const.m_proton * (df['P_decay_ell_plus','0'] + df['P_decay_ell_minus','0']) / ( const.m_proton - (df['P_decay_ell_plus','0'] + df['P_decay_ell_minus','0'])*(1.0 - (df['costheta_lm']*df['P_decay_ell_minus','0'] + df['costheta_lp'] * df['P_decay_ell_plus','0'])/(df['P_decay_ell_plus','0'] + df['P_decay_ell_minus','0'])  ))

  ###################### HISTOGRAM 2D ##################################################
    n2D = 40
    args_2d = {"title": title, "nbins": n2D}

    histogram2D(PATH+"/2D_EN_Etot.pdf", df['E_N'], df['E_tot'], w,
                                xrange=[0.0, 2.0],
                                yrange=[0.0, 2.0],
                                xlabel=r"$E_{N}$ (GeV)",
                                ylabel=r"$E_{\ell^-}+E_{\ell^+}$ (GeV)",
                                **args_2d)

    histogram2D(PATH+"/2D_Ep_Em.pdf", df['E_lm'], df['E_lp'],w,
                                xrange=[0.0, 2.0],
                                yrange=[0.0, 2.0],
                                xlabel=r"$E_{\ell^-}$ (GeV)",
                                ylabel=r"$E_{\ell^+}$ (GeV)",
                                **args_2d)

    histogram2D(PATH+"/2D_dtheta_Etot.pdf", df['Delta_theta'], df['E_tot'], w, \
                                              xrange=[0.0, 90],
                                              yrange=[0.0, 2.0],
                                              xlabel=r"$\Delta \theta_{\ell \ell}$ ($^\circ$)",
                                              ylabel=r"$E_{\ell^+}+E_{\ell^-}$ (GeV)",
                                              **args_2d)

    histogram2D(PATH+"/2D_Easyabs_Etot.pdf", np.abs(df['E_asy']), df['Delta_costheta'], w,
                                xrange=[0.0, 1.0],
                                yrange=[0.0, 90.0],
                                xlabel=r"$|E_{\rm asy}|$",
                                ylabel=r"$\Delta \theta_{\ell \ell}$ ($^\circ$)",
                                **args_2d)

    histogram2D(PATH+"/2D_Easyabs_Etot.pdf", np.abs(df['E_asy']), df['E_tot'], w,
                                xrange=[0.0, 1.0],
                                yrange=[0.0, 2.0],
                                xlabel=r"$|E_{\rm asy}|$",
                                ylabel=r"$E_{\ell^+}+E_{\ell^-}$ (GeV)",
                                **args_2d)

    histogram2D(PATH+"/2D_Easyabs_Etot.pdf", np.abs(df['E_asy']), df['E_tot'], w,
                                xrange=[0.0, 1.0],
                                yrange=[0.0, 2.0],
                                xlabel=r"$|E_{\rm asy}|$",
                                ylabel=r"$E_{\ell^+}+E_{\ell^-}$ (GeV)",
                                **args_2d)

    histogram2D(PATH+"/2D_Ehad_Etot.pdf", df['T_proton'][pel]*1e3, df['E_tot'][pel], w_pel,
                                xrange=[0.0, 1000],
                                yrange=[0.0, 2.0],
                                xlabel=r"$T_{\rm proton}$ (MeV)",
                                ylabel=r'$E_{\ell^+} + E_{\ell^-}$ (GeV)',
                                title=title +' proton-elastic only', nbins=n2D)

    histogram2D(PATH+"/2D_thetaLead_dtheta.pdf", df['theta_subleading'], df['theta_leading'], w,
                                                xrange=[0.0, 40.0],
                                                yrange=[0.0, 40.0],
                                                xlabel=r"$\theta_{\nu_\mu \ell_{\rm lead}}$ ($^\circ$)",
                                                ylabel=r'$\Delta \theta$ ($^\circ$)',
                                                **args_2d)

    #################### HISTOGRAMS 1D ####################################################
    # momentum exchange
    histogram1D(PATH+"/1D_Q.pdf", np.sqrt(df['Q2']), w, 0.0, 1., r"$Q/$GeV", title, 10, **args)
    histogram1D(PATH+"/1D_Q2.pdf", df['Q2'], w, 0.0, 1.5, r"$Q^2/$GeV$^2$", title, 10, **args)

    histogram1D(PATH+"/1D_T_proton.pdf", df['T_proton'][pel]*1e3, w_pel, 0.0, 500.0, r"$T_{\rm p^+}$ (MeV)", 'el proton only', 50, **args)
    histogram1D(PATH+"/1D_theta_proton.pdf", df['theta_proton'][pel], w_pel, 0.0, 180, r"$\theta_{p^+}$ ($^\circ$)", 'el proton only', 50, **args)
    histogram1D(PATH+"/1D_T_nucleus.pdf", df['T_nucleus'][coherent]*1e3, w_coh, 0.0, 20, r"$T_{\rm Nucleus}$ (MeV)", 'coh nucleus only', 50, **args)
    histogram1D(PATH+"/1D_theta_nucleus.pdf", df['theta_nucleus'][coherent], w_coh, 0.0, 180, r"$\theta_{\rm Nucleus}$ ($^\circ$)", 'coh nucleus only', 50, **args)

    # energies
    histogram1D(PATH+"/1D_E_lp.pdf", df['E_lp'], w, 0.0, 2.0, r"$E_{\ell^+}$ GeV", title, 100, **args)
    histogram1D(PATH+"/1D_E_lm.pdf", df['E_lm'], w, 0.0, 2.0, r"$E_{\ell^-}$ GeV", title, 100, **args)
    histogram1D(PATH+"/1D_E_tot.pdf", df['E_tot'], w, 0.0, 2.0, r"$E_{\ell^-}+E_{\ell^+}$ GeV", title, 100, **args)

    histogram1D(PATH+"/1D_E_nu_truth.pdf", df['P_projectile','0'], w, 0.0, 2.0, r"$E_\nu^{\rm truth}/$GeV", title, 20, **args)
    histogram1D(PATH+"/1D_E_nu_QEreco.pdf", df['E_nu_reco'], w, 0.0, 2.0, r"$E_\nu^{\rm QE-reco}/$GeV", title, 20, **args)

    histogram1D(PATH+"/1D_E_N.pdf", df['E_N'], w, 0.0, 2.0, r"$E_N/$GeV", title, 20, **args)

    histogram1D(PATH+"/1D_E_leading.pdf", df['E_leading'], w, 0.0, 2.0, r"$E_{\rm leading}$ GeV", title, 100, **args)
    histogram1D(PATH+"/1D_E_subleading.pdf", df['E_subleading'], w, 0.0, 2.0, r"$E_{\rm subleading}$ GeV", title, 100, **args)

    # angles
    histogram1D(PATH+"/1D_costN.pdf", df['costheta_N'], w, -1.0, 1.0, r"$\cos(\theta_{\nu_\mu N})$", title, 20, **args)

    histogram1D(PATH+"/1D_cost_sum.pdf", df['costheta_sum'], w, -1.0, 1.0, r"$\cos(\theta_{(ee)\nu_\mu})$", title, 20, **args)
    histogram1D(PATH+"/1D_cost_sum_had.pdf", df['costheta_sum_had'], w, -1.0, 1.0, r"$\cos(\theta_{(ee) {\rm hadron}})$", title, 20, **args)

    histogram1D(PATH+"/1D_cost_nu.pdf", df['costheta_nu'], w, -1.0, 1.0, r"$\cos(\theta_{\nu_\mu \nu_{\rm out}})$", title, 40, **args)
    histogram1D(PATH+"/1D_theta_nu.pdf", df['theta_nu'], w, 0.0, 180.0, r"$\theta_{\nu_\mu \nu_{\rm out}}$", title, 40, **args)

    histogram1D(PATH+"/1D_cost_lp.pdf", df['costheta_lp'],  w, -1.0, 1.0, r"$\cos(\theta_{\nu_\mu \ell^+})$", title, 40, **args)
    histogram1D(PATH+"/1D_cost_lm.pdf", df['costheta_lm'], w, -1.0, 1.0, r"$\cos(\theta_{\nu_\mu \ell^-})$", title, 40, **args)

    histogram1D(PATH+"/1D_theta_lp.pdf", df['theta_lp'], w, 0.0, 180.0, r"$\theta_{\nu_\mu \ell^+}$", title, 40, **args)
    histogram1D(PATH+"/1D_theta_lm.pdf", df['theta_lm'], w, 0.0, 180.0, r"$\theta_{\nu_\mu \ell^-}$", title, 40, **args)

    histogram1D(PATH+"/1D_theta_lead.pdf", df['theta_leading'], w, 0.0, 180.0, r"$\theta_{\nu_\mu \ell_{\rm lead}}$ ($^\circ$)", title, 40, **args)
    histogram1D(PATH+"/1D_theta_sublead.pdf", df['theta_subleading'], w, 0.0, 180.0, r"$\theta_{\nu_\mu \ell_{\rm sublead}}$ ($^\circ$)", title, 40, **args)

    histogram1D(PATH+"/1D_deltacos.pdf", df['Delta_costheta'], w,  -1.0, 1.0, r"$\cos(\theta_{\ell^+ \ell^-})$", title, 40, **args)
    histogram1D(PATH+"/1D_deltatheta.pdf", df['Delta_theta'], w, 0, 180.0, r"$\theta_{\ell^+ \ell^-}$", title, 40, **args)

    # highe level vars
    histogram1D(PATH+"/1D_invmass.pdf", df['inv_mass'], w, 0.0, np.max(df['inv_mass']), r"$m_{\ell^+ \ell^-}$ [GeV]", title, 50, **args)

    histogram1D(PATH+"/1D_asym.pdf", df['E_asy'], w, -1.0, 1.0, r"$(E_{\ell^+}-E_{\ell^-})$/($E_{\ell^+}+E_{\ell^-}$)", title, 20, **args)
    histogram1D(PATH+"/1D_asym_abs.pdf", np.abs(df['E_asy']), w, 0.0, 1.0, r"$|E_{\ell^+}-E_{\ell^-}|$/($E_{\ell^+}+E_{\ell^-}$)", title, 20, **args)

def plot_closed_region(points, logx=False, logy=False):
    x,y = points
    if logy:
        if (y==0).any():
            raise ValueError("y values cannot contain any zeros in log mode.")
        sy = np.sign(y)
        ssy = ((np.abs(y)<1)*(-1) + (np.abs(y)>1)*(1))
        y  = ssy*np.log(y*sy)
    if logx:
        if (x==0).any():
            raise ValueError("x values cannot contain any zeros in log mode.")
        sx  = np.sign(x)
        ssx = ((x<1)*(-1) + (x>1)*(1))
        x  = ssx*np.log(x*sx)

    points = np.array([x,y]).T

    points_s     = (points - points.mean(0))
    angles       = np.angle((points_s[:,0] + 1j*points_s[:,1]))
    points_sort  = points_s[angles.argsort()]
    points_sort += points.mean(0)

    tck, u = splprep(points_sort.T, u=None, s=0.0, per=0, k=1)
    u_new = np.linspace(u.min(), u.max(), len(points[:,0]))
    x_new, y_new = splev(u_new, tck, der=0)

    if logx:
        x_new = sx*np.exp(ssx*x_new)
    if logy:
        y_new = sy*np.exp(ssy*y_new)

    return x_new, y_new


n = int(input())
arr = list(map(str, input().split()))
#시작 위치 초기화
a, b = 1, 1

#c, d를 이용하여 초기 위치를 옮겨다님
for i in arr:
    #l(left)이면 c는 a위치, d는 b-1 위치
    if i == 'l':
        c = a
        d = b - 1
    elif i == 'r':
        c = a
        d = b + 1
    elif i == 'u':
        c = a - 1
        d = b
    else:
        c = a + 1
        d = b

    #옮긴 c, d위치가 정사각형 공간 밖이면 최종 이동한 a, b위치를 이동하지 않는다.
    if c < 1 or d < 1 or c > n or d > n:
        continue

    a, b = c, d

print(a, b)

#문제 해설을 본 후 l,r,u,d도 리스트화 하여 구현하면 코드가 더 깔끔해졌을 것이라 느꼈다.


import copy
import math

class EnergyFunction( object ):
	aminoFile 	  = "amber99/aminoacids.rtp"
	bondedFile 	  = "amber99/ffbonded.itp"
	nonbondedFile = "amber99/ffnonbonded.itp"

	cutoff = 8.0
	constant = math.pow( 2, ( 1.0 / 6.0 ) )

	def __init__( self, pdb ):
		self.protein = []
		self.aminoacids = {}
		self.ffbonded = {}
		self.ffnonbonded = []
		self.pdb = pdb

		self.readProtein()
		self.readAmberFiles()

	def areBonded( self, aminoA, atomA, atomB ):
		bonded = False
		key = aminoA
		for i in range( len( self.aminoacids[key][1] ) ):
		    if ( self.aminoacids[key][1][i][0] == atomA and self.aminoacids[key][1][i][1] == atomB )\
		    	or ( self.aminoacids[key][1][i][0] == atomB and self.aminoacids[key][1][i][1] == atomA ):
		        bonded = True
		        break

		return bonded

	def distance( self, atomA, atomB ):
	    x = ( atomA[0] - atomB[0] )**2
	    y = ( atomA[1] - atomB[1] )**2
	    z = ( atomA[2] - atomB[2] )**2
	    distance = math.sqrt( x + y + z )

	    return distance

	def findCharge( self, atom, aminoAcid ):
		#print self.aminoacids[aminoAcid]

		for i in range( len( self.aminoacids[aminoAcid][0] ) ):
			if self.aminoacids[aminoAcid][0][i][0] == atom:
				return copy.deepcopy( self.aminoacids[aminoAcid][0][i][2] ), copy.deepcopy( self.aminoacids[aminoAcid][0][i][1] )

		for i in range( len( self.aminoacids[aminoAcid][0] ) ):
			if self.aminoacids[aminoAcid][0][i][0] == atom[0]:
				return copy.deepcopy( self.aminoacids[aminoAcid][0][i][2] ), copy.deepcopy( self.aminoacids[aminoAcid][0][i][1] )

		return None

	def renameAtom( self, atom ):
		if atom.strip() == "HB3":
			return "HB1"

		elif atom.strip() == "2H":
			return "H2"

		return atom.strip()

	def findNonbonded( self, atom ):
		for i in range( len( self.ffnonbonded ) ):
			if self.ffnonbonded[i][0] == atom:
				return copy.deepcopy( self.ffnonbonded[i] )

		return None

	def conversor( self, atom, aminoAcid ):
		key = aminoAcid
		for i in range( len( self.aminoacids[key][0] ) ):
			if self.aminoacids[key][0][i][0] == atom:
				return self.aminoacids[key][0][i][1]

	def findDihedral( self, atom1, atom2, atom3, atom4 ):
		for i in range( len( self.ffbonded['dihedraltypes'] ) ):
			if self.ffbonded['dihedraltypes'][i][0] == atom1 and self.ffbonded['dihedraltypes'][i][1] == atom2\
				and self.ffbonded['dihedraltypes'][i][2] == atom3 and self.ffbonded['dihedraltypes'][i][3] == atom4:
				return copy.deepcopy( self.ffbonded['dihedraltypes'][i] )

	def findAngle( self, atom1, atom2, atom3 ):
		for i in range( len( self.ffbonded['angletypes'] ) ):
			if self.ffbonded['angletypes'][i][0] == atom1 and self.ffbonded['angletypes'][i][1] == atom2 and self.ffbonded['angletypes'][i][2] == atom3:
				return copy.deepcopy( self.ffbonded['angletypes'][i] )

	def findBond( self, atom1, atom2 ):
		for i in range( len( self.ffbonded['bondtypes'] ) ):
			if ( self.ffbonded['bondtypes'][i][0] == atom1 and self.ffbonded['bondtypes'][i][1] == atom2 ) or ( self.ffbonded['bondtypes'][i][0] == atom2 and self.ffbonded['bondtypes'][i][1] == atom1 ):
				return copy.deepcopy( self.ffbonded['bondtypes'][i] )

	def covalentBond( self, bond, dist ):
		r = dist * 0.1
		value = bond[3] * ( r - bond[2] )**2
		return value ## kJ / mol

	def bondAngles( self, atom1, atom2, atom3, b ):
		#lig a1 - a2 - a3
		#b: informacao da ligacao [atom1, atom2, atom3, theta, kt]

		A = self.distance( atom1, atom2 )
		B = self.distance( atom2, atom3 )
		C = self.distance( atom1, atom3 )

		aux = ( A**2 + B**2 - C**2 ) / ( 2*A*B )
		theta = math.acos( aux ) # Calcula angulo de ligacao
		theta = math.degrees( theta )

		value = b[4] * ( theta - b[3] )**2
		return value ## kJ / mol

	def calcDihedralAngle( self, atom1, atom2, atom3, atom4 ):
		vector1 = [( atom2[0] - atom1[0] ), ( atom2[1] - atom1[1] ), ( atom2[2] - atom1[2] )]
		vector2 = [( atom3[0] - atom2[0] ), ( atom3[1] - atom2[1] ), ( atom3[2] - atom2[2] )]
		vector3 = [( atom4[0] - atom3[0] ), ( atom4[1] - atom3[1] ), ( atom4[2] - atom3[2] )]

		normal1 = [( vector1[1] * vector2[2] - vector1[2] * vector2[1] ), ( vector1[2] * vector2[0] - vector1[0] * vector2[2] ), ( vector1[0] * vector2[1] - vector1[1] * vector2[0] )]
		normal2 = [( vector2[1] * vector3[2] - vector2[2] * vector3[1] ), ( vector2[2] * vector3[0] - vector2[0] * vector3[2] ), ( vector2[0] * vector3[1] - vector2[1] * vector3[0] )]

		n1 = math.sqrt( ( normal1[0]**2) + ( normal1[1]**2 ) + ( normal1[2]**2 ) )
		n2 = math.sqrt( ( normal2[0]**2) + ( normal2[1]**2 ) + ( normal2[2]**2 ) )

		normal1 = [( normal1[0]/n1 ), ( normal1[1]/n1 ), ( normal1[2]/n1 )]
		normal2 = [( normal2[0]/n2 ), ( normal2[1]/n2 ), ( normal2[2]/n2 )]

		v2 = math.sqrt( ( vector2[0]**2 ) + ( vector2[1]**2 ) + ( vector2[2]**2 ) )
		vector2 = [( vector2[0]/v2 ), ( vector2[1]/v2 ), ( vector2[2]/v2 )]

		m1 = [( ( normal1[1] * normal2[2] ) - ( normal1[2] * normal2[1] ) ), ( ( normal1[2] * normal2[0] ) - ( normal1[0] * normal2[2] ) ), ( ( normal1[0] * normal2[1] ) - ( normal1[1] * normal2[0] ) )]

		x = ( normal1[0] * normal2[0] ) + ( normal1[1] * normal2[1] ) + ( normal1[2] * normal2[2] )
		y = ( m1[0] * vector2[0] ) + ( m1[1] * vector2[1] ) + ( m1[2] * vector2[2] )

		angle = round( math.degrees( math.atan2( y, x ) ), 2 )
		return angle

	def dihedralAngles( self, at1, at2, at3, at4, b ):
		a1 = [at1[3], at1[4], at1[5]]
		a2 = [at2[3], at2[4], at2[5]]
		a3 = [at3[3], at3[4], at3[5]]
		a4 = [at4[3], at4[4], at4[5]]
		angle = self.calcDihedralAngle( a1, a2, a3, a4 )
		phase = math.radians(b[4])

		Vda = ( b[5] / 2 ) * ( 1 + math.cos( ( b[6] * angle ) - phase ) )

		return Vda ## kJ / mol

	def areDihedral( self, i, j, k, m ):
		total = 0.0

		atomI = self.protein[i][0]
		atomJ = self.protein[j][0]
		aminoI = self.protein[i][1]
		aminoJ = self.protein[j][1]
		seqAminoI = self.protein[i][2]
		seqAminoJ = self.protein[j][2]
		posAtomI = self.protein[i][3:6]
		posAtomJ = self.protein[j][3:6]
		atomK = self.protein[k][0]
		aminoK = self.protein[k][1]
		seqAminoK = self.protein[k][2]
		posAtomK = self.protein[k][3:6]
		atomM = self.protein[m][0]
		aminoM = self.protein[m][1]
		seqAminoM = self.protein[m][2]
		posAtomM = self.protein[m][3:6]

		while 1:
			if self.areBonded( aminoK, atomK, atomM ) == True:
				a1 = self.conversor( atomI, aminoI )
				a2 = self.conversor( atomJ, aminoJ )
				a3 = self.conversor( atomK, aminoK )
				a4 = self.conversor( self.protein[m][0], self.protein[m][1] )
				die = self.findDihedral( a1, a2, a3, a4 )

				if die != None:
					Vda = self.dihedralAngles( self.protein[i], self.protein[j], self.protein[k], self.protein[m], die )
					total += Vda
				else:
					die = self.findDihedral( 'X', a2, a3, a4 )
					if die != None:
						Vda = self.dihedralAngles( self.protein[i], self.protein[j], self.protein[k], self.protein[m], die )
						total += Vda
					else:
						die = self.findDihedral( 'X', 'X', a3, a4, ffbonded )
						if die != None:
							Vda = self.dihedralAngles( self.protein[i], self.protein[j], self.protein[k], self.protein[m], die )
							total += Vda
						else:
							die = self.findDihedral( 'X', a2, a3, 'X', ffbonded )
							if die != None:
								Vda = self.dihedralAngles( self.protein[i], self.protein[j], self.protein[k], self.protein[m], die )
								total += Vda
			m += 1
			if m == ( len( self.protein ) - 1 ) or self.protein[k][2] == self.protein[m+1][2]:
				break

		return total

	def calcLennardJones( self, atomI, atomJ, distance ):
		rmini = atomI[1] * self.constant
		rminj = atomJ[1] * self.constant
		rminij = ( rmini + rminj ) / 2.0
		epsilon = math.sqrt( atomI[2] * atomJ[2] ) #media geometrica
		rij = distance * 0.1 ##transformar em nm

		lj = epsilon * ( math.pow( ( rminij / rij ), 12.0 ) - ( 2.0 * math.pow( ( rminij / rij ), 6.0 ) ) )
		return lj ##kJ / mol

	def calcCoulumb( self, chargeA, chargeB, distance ):
		eCharge = 1.6022e-19
		eConstant = 9e9
		avg = 6.022140857e23
		ke = eConstant * ( ( eCharge * eCharge ) / 1e-9 ) * avg / 1000.0
		rij = distance * 0.1 ##transformar em nm
		cl = ke * ( ( chargeA * chargeB ) / rij )
		return cl ##kJ/mol

	def getEnergy( self ):
		total = 0
		for i in range( len( self.protein ) - 1 ):
			for j in range( i + 1, len( self.protein ) ):
				#print i, j
				atomI = self.protein[i][0]
				atomJ = self.protein[j][0]
				aminoI = self.protein[i][1]
				aminoJ = self.protein[j][1]
				seqAminoI = self.protein[i][2]
				seqAminoJ = self.protein[j][2]
				posAtomI = self.protein[i][3:6]
				posAtomJ = self.protein[j][3:6]
				dist = self.distance( posAtomI, posAtomJ )

				if not( atomI == "C" and atomJ == "N" and seqAminoI - seqAminoJ == -1 ):
					if ( ( ( seqAminoI == seqAminoJ and self.areBonded( aminoI, atomI, atomJ ) == False ) or ( seqAminoI != seqAminoJ ) ) and dist <= self.cutoff ):
						atomI = self.renameAtom( atomI )
						atomJ = self.renameAtom( atomJ )

						res = self.findCharge( atomI, aminoI )
						if res is not None:
							charge1, a1 = res
						#print charge1, a1, aminoI
						res = self.findCharge( atomJ, aminoJ )
						if res is not None:
							charge2, a2 = res
						#print charge2, a2, aminoJ
						#print self.areBonded( aminoI, atomI, atomJ ), atomI, "(", aminoI, ")", atomJ, "(", aminoJ, ")"

						atom1 = self.findNonbonded( a1 )
						atom2 = self.findNonbonded( a2 )

						ljValue = self.calcLennardJones( atom1, atom2, dist )
						cValue = self.calcCoulumb( charge1, charge2, dist )

						total += ljValue + cValue

					if ( seqAminoI == seqAminoJ and self.areBonded( aminoI, atomI, atomJ ) == True ):
						a1 = self.conversor( atomI, aminoI )
						a2 = self.conversor( atomJ, aminoJ )

						bond = self.findBond( a1, a2 )

						if bond != None:
							bondValue = self.covalentBond( bond, dist )
							#total += bondValue

						k = j
						atomK = self.protein[k][0]
						aminoK = self.protein[k][1]
						posAtomK = self.protein[k][3:6]

						while 1:
							if self.areBonded( aminoJ, atomJ, atomK ) == True:
								a3 = self.conversor( atomK, aminoK )
								ang = self.findAngle( a1, a2, a3 )
								if ang != None:
									va = self.bondAngles( posAtomI, posAtomJ, posAtomK, ang )
									#total += va

								vda = self.areDihedral( i, j, k, k+1 )
								#total += vda

							k += 1
							#print k, len( self.protein )
							if k >= ( len( self.protein ) - 1 ) or self.protein[j][2] == self.protein[k][2]:
								break
				else:
					bond = self.findBond( atomI, atomJ )
					if bond != None:
						Vb = self.covalentBond( bond, dist )
						#total += Vb
					for l in range( 0, 10 ):
						if self.protein[l][0] == 'CA' or self.protein[l][0] == 'H':
							if self.areBonded( aminoJ, atomJ, self.protein[l][0] ) == True:
								a3 = self.conversor( self.protein[l][0], self.protein[l][1] )
								ang = self.findAngle( self.protein[i][0], self.protein[j][0], a3 )
								if ang != None:
									Va = self.bondAngles( posAtomI, posAtomJ, self.protein[l][3:6], ang )
									#total += Va
							Vda = Vda = self.areDihedral( i, j, l, l+1 )
							#total += Vda

		return total

	def readProtein( self ):
		'''file = open( self.filename, 'r' )
		content = file.readlines()
		file.close()

		lines = []
		for i in range( len( content ) ):
			lines.append( content[i].split() )

		for i in range( len( lines ) ):
			if lines[i][0] == 'ATOM':
				if lines[i][5] == '1':
					self.protein.append( [copy.deepcopy( lines[i][2] ), "N" + copy.deepcopy( lines[i][3] ), int( lines[i][4] ),float( lines[i][5] ), float( lines[i][6] ), float( lines[i][7] )] )
				else:
					if lines[i][5] == lines[len( lines )-2][5] :
						self.protein.append( [copy.deepcopy( lines[i][2]), "C" + copy.deepcopy( lines[i][3] ), int( lines[i][4] ),float( lines[i][5] ), float( lines[i][6] ), float( lines[i][7] )] )
					else:
						self.protein.append( [copy.deepcopy( lines[i][2]), copy.deepcopy( lines[i][3] ), int( lines[i][4] ),float( lines[i][5] ), float( lines[i][6] ), float( lines[i][7] )] )

		print self.protein'''
		#print len( self.pdb.atoms )
		#print self.pdb.posAtoms
		for i in range( len( self.pdb.posAtoms ) ):
			#print i
			if self.pdb.aminoAcids[i] == 1:
				self.protein.append( [copy.deepcopy( self.pdb.atoms[i].strip() ), "N" + copy.deepcopy( self.pdb.aAcids[i] ), self.pdb.aminoAcids[i], float( self.pdb.posAtoms[i][0] ), float( self.pdb.posAtoms[i][1] ), float( self.pdb.posAtoms[i][2] )] )
			else:
				if self.pdb.aminoAcids[i] == self.pdb.aminoAcids[len( self.pdb.aminoAcids )-2]:
					self.protein.append( [copy.deepcopy( self.pdb.atoms[i].strip() ), "C" + copy.deepcopy( self.pdb.aAcids[i] ), self.pdb.aminoAcids[i], float( self.pdb.posAtoms[i][0] ), float( self.pdb.posAtoms[i][1] ), float( self.pdb.posAtoms[i][2] )] )
				else:
					self.protein.append( [copy.deepcopy( self.pdb.atoms[i].strip() ), copy.deepcopy( self.pdb.aAcids[i] ), self.pdb.aminoAcids[i], float( self.pdb.posAtoms[i][0] ), float( self.pdb.posAtoms[i][1] ), float( self.pdb.posAtoms[i][2] )] )

		#print self.protein

	def readAmberFiles( self ):
		# aminoAcid:
		file = open( self.aminoFile, 'r' )
		content = file.readlines()
		file.close()

		index = 0
		while True:
			if content[index] == "; Next are non-terminal AA's\n":
				break
			else:
				index += 1
		index += 2

		atom = False
		bond = False

		key = ""

		for i in range( index, len( content ) ):
			if content[i].split() != []:
				if content[i][0] == '[':
					key = content[i].split()[1]
					self.aminoacids[key] = [[],[]] # [atoms, bonds]
				elif content[i].split()[1] == "atoms":
					atom = True
					bond = False
				elif content[i].split()[1] == "bonds":
					atom = False
					bond = True
				elif content[i].split()[1] == "impropers":
					atom = False
					bond = False
				elif atom == True:
					self.aminoacids[key][0].append( [content[i].split()[0], content[i].split()[1], float( content[i].split()[2] )] ) # atoms
				elif bond == True:
					self.aminoacids[key][1].append( [content[i].split()[0], content[i].split()[1]] ) # bonds

		#print self.aminoacids

		# bondend:
		file = open( self.bondedFile, 'r' )
		content = file.readlines()
		file.close()

		lines = []
		for i in range(len(content)):
			content[i] = content[i].split()
			if len(content[i]) != 0:
				lines.append(content[i])

		for i in range( len( lines ) ):
			if lines[i][0] == '[':
				key = lines[i][1]
				if key == 'bondtypes':
					self.ffbonded[key] = []
					while 1:
						i += 1
						if lines[i][0] != ';':
							if lines[i][0] == '[':
								break
							self.ffbonded[key].append( [lines[i][0], lines[i][1], float( lines[i][3] ), float( lines[i][4] )] )

				if key == 'angletypes':
					self.ffbonded[key] = []
					while 1:
						i += 1
						if lines[i][0] != ';':
							if lines[i][0] == '[':
								break
							self.ffbonded[key].append( [lines[i][0], lines[i][1], lines[i][2], float( lines[i][4] ), float( lines[i][5] )] )

				if key == 'dihedraltypes':
					if self.ffbonded.get( key ) is None:
						self.ffbonded[key] = []
					while 1:
						i += 1
						if i != len( lines )-1:
							if lines[i][0] != ';i':
								if lines[i][0] == '[':
									break
								self.ffbonded[key].append( [lines[i][0], lines[i][1], lines[i][2], lines[i][3], float( lines[i][5] ), float( lines[i][6] ), float( lines[i][7] )] )
						else:
							self.ffbonded[key].append( [lines[i][0], lines[i][1], lines[i][2], lines[i][3], float( lines[i][5] ), float( lines[i][6] ), float( lines[i][7] )] )
							break

		#print self.ffbonded

		# nonbonded:
		file = open( self.nonbondedFile, 'r' )
		content = file.readlines()
		file.close()

		lines = []
		for i in range( len( content ) ):
			lines.append( content[i].split() )

		for i in range( len( lines ) ):
			if lines[i][0][0] != '[' and lines[i][0][0] != ';' :
				self.ffnonbonded.append( [copy.deepcopy( lines[i][0] ), float( lines[i][5] ), float( lines[i][6] )] )

		#print self.ffnonbonded


from django.shortcuts import render
from blog.models import Books

def home(request):
    """ Использование Django шаблонов.  Метод обрабатывает запрос `/` """

    # Объект который будет передан в шаблон
    context = {
        'message': 'Добро пожаловать',
        'left': 'сообщение слева',
        'right': 'сообщение справа',
        'books': Books.objects.all()
    }

    # Рендеринг шаблона с последующим ответом клиенту
    return render(request, 'part_1/index.html', context)


import socket
import threading
import re

from helpers import *

threadLimiter = threading.BoundedSemaphore(8)

class request_handler(threading.Thread):

    def __init__(self, s, banned_words):
        threading.Thread.__init__(self)
        self.client_to_proxy_socket = s
        self.banned_words = banned_words

    def run(self):
        threadLimiter.acquire()
        try:
            self.get_request = self.client_to_proxy_socket.recv(2048)

            self.get_request_dict = header_to_dict(self.get_request.split('\r\n\r\n')[0])

            if not self.get_request_dict['first_line'][0:3] == "GET":
                #This is not a GET request, drop it
                self.client_to_proxy_socket.close()
                return

            if URL_contains_bad_words(self.get_request, self.banned_words):
                print("Bad URL for host {}: {}".format(self.get_request_dict['host'],
                                                       self.get_request_dict['first_line']))
                self.http_response = "HTTP/1.1 302 Found\r\nLocation: http://www.ida.liu.se/~TDTS04/labs/2011/ass2/error1.html\r\nHost: http://www.ida.liu.se\r\nConnection: close\r\n\r\n"
            else:
                # Remove the host from get request first line
                self.get_request_dict['first_line'] = \
                re.sub('(?P<get>GET) .*' + self.get_request_dict['host'] + \
                       '(?P<path>.+) ', '\g<get> \g<path> ', self.get_request_dict['first_line'])

                self.get_request_dict['connection'] = 'close'
                #self.get_request_dict['accept-encoding'] = ""

                self.proxy_to_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.proxy_to_server_socket.connect((self.get_request_dict['host'], 80))

                self.proxy_to_server_socket.sendall(serialize_header_dict(self.get_request_dict))

                self.http_response = receive_http_response(self.proxy_to_server_socket)

                self.proxy_to_server_socket.close()

            if self.http_response is not None:
                if content_contains_bad_words(self.http_response, self.banned_words):
                    print("Bad content for host {}".format(self.get_request_dict['host']))
                    self.http_response = "HTTP/1.1 302 Found\r\nLocation: http://www.ida.liu.se/~TDTS04/labs/2011/ass2/error2.html\r\nHost: http://www.ida.liu.se\r\nConnection: close\r\n\r\n"

                self.client_to_proxy_socket.sendall(self.http_response)

            self.client_to_proxy_socket.close()
        finally:
         threadLimiter.release()
         return


# -*-coding:latin-1 -*
import os # On importe le module os

lstVide=[]
lstFlottant=[0.0]*5

print('lstVide=',lstVide,'\n','lstFlottant=',lstFlottant,'\n')
rien=input('Return')
lstVide += range(0,1000,200)
print('lstVide=',lstVide,'\n','lstFlottant=',lstFlottant,'\n')
rien=input('Return')
print('index 0 à 3 : ', range(4))

# ... placez votre programme ici AVANT les lignes suivantes
# On met le programme en pause pour éviter qu'il ne se referme (Windows)
os.system("pause")


import os
from ml import datasets_path, MNISTDownloader, Trainer
from sklearn.neighbors import KNeighborsClassifier

data_path = os.path.join(datasets_path, 'mnist')
mnist = MNISTDownloader(data_path)
dataset = mnist.fetch()
trainer = Trainer(dataset)
trainer.train_test_split()

knn_clf = KNeighborsClassifier()
trainer.fit(knn_clf)
predictions = trainer.predict()
breakpoint()


import atexit
from collections import namedtuple, defaultdict
import datetime
import os
import subprocess

import feedgenerator
from jinja2 import Environment, FileSystemLoader, select_autoescape
import watchdog
import watchdog.events
import watchdog.observers

WEBSITE_URL = 'https://www.moderndescartes.com'
# Where to look for various files
ESSAY_DIR = "essays"
STATIC_DIR = "static"
STAGING_DIR = "staging"
TEMPLATE_DIR = "templates"

ESSAY_WEBDIR = "essays"

ALLOWED_EXTENSIONS = ('.md', '.txt', '.html')

BASIC_PAGES = (
    '404.html',
    'index.html',
)

JINJA_ENV = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=select_autoescape(['html', 'xml']))

Essay = namedtuple('Essay', ['slug', 'title', 'date', 'content', 'tags'])

def is_public_essay(essay_path):
    dirpath, filename = os.path.split(essay_path)
    (essay_shortname, extension) = os.path.splitext(filename)
    if extension not in ALLOWED_EXTENSIONS:
        return False
    if filename[0] == '_':
        return False
    return True

def make_rss(compiled_essays: list[Essay]):

    feed = feedgenerator.Atom1Feed(
        title="Modern Descartes - Essays by Brian Lee",
        link=os.path.join(WEBSITE_URL, ESSAY_WEBDIR),
        description="I seek, therefore I am")
    for essay in compiled_essays[:10]:
        feed.add_item(
            title=essay.title,
            link=os.path.join(WEBSITE_URL, ESSAY_WEBDIR, essay.slug),
            description=compile_html('rss_item.txt', essay=essay),
            pubdate=essay.date)
    with open(os.path.join(STAGING_DIR, ESSAY_WEBDIR, 'rss.xml'), 'w') as f:
        feed.write(f, 'utf-8')

def compile_html(template_name, **context):
    default_context = {"ESSAY_WEBDIR": ESSAY_WEBDIR}
    default_context.update(**context)
    template = JINJA_ENV.get_template(template_name)
    compiled = template.render(**default_context)
    return compiled

def compile_and_write_html(template_name: str, output_file: str, **context):
    compiled = compile_html(template_name, **context)
    output_path = os.path.join(STAGING_DIR, output_file)
    os.makedirs(os.path.dirname(output_path), exist_ok=True)
    with open(output_path, 'w') as f:
        f.write(compiled)

def parse_essay(essay_path):
    essay_shortname = os.path.splitext(os.path.basename(essay_path))[0]
    with open(essay_path, 'r') as f:
        essay_longname = f.readline().rstrip('\n')
        year, month, day = f.readline().rstrip('\n').split('/')
        tags = list(filter(bool, f.readline().rstrip('\n').split(',')))
        essay_date = datetime.date(year=int(year), month=int(month),
            day=int(day))
        essay_content = f.read()
    result = subprocess.run(
        "pandoc -f markdown -t html --mathjax --shift-heading-level-by=1",
        stdout=subprocess.PIPE,
        input=essay_content.encode('utf8'),
        shell=True)
    essay_content = result.stdout.decode('utf8')

    return Essay(slug=essay_shortname, title=essay_longname,
                 date=essay_date, content=essay_content, tags=tags)

def compile_essay(essay_path) -> list[Essay]:
    if not is_public_essay(essay_path):
        return []

    try:
        essay = parse_essay(essay_path)
        compile_and_write_html('essay_detailed.html',
            'essays/{}/index.html'.format(essay.slug), essay=essay)
        return [essay]
    except Exception as e:
        print('Failed to process {}'.format(essay_path))
        print(type(e), e)
    return []

def compile_essays() -> list[Essay]:
    print("Processing essays...")
    all_essays = []
    for dirpath, _, filenames in os.walk(ESSAY_DIR):
        for filename in filenames:
            all_essays.extend(compile_essay(os.path.join(dirpath, filename)))

    print("Compiled {} essays".format(len(all_essays)))
    return all_essays

def compile_all():
    assert STAGING_DIR not in ('/', '.', '..', '')
    subprocess.run('[ -d {} ] && rm -r {}'.format(STAGING_DIR, STAGING_DIR), shell=True, stderr=subprocess.STDOUT)
    compile_and_write_html('404.html', '404.html')
    compile_and_write_html('index.html', 'index.html')
    all_essays = compile_essays()
    essays_sorted = sorted(all_essays, key=lambda e: e.date, reverse=True)
    essays_by_tag = defaultdict(list)
    for essay in essays_sorted:
        for tag in essay.tags:
            essays_by_tag[tag].append(essay)
    compile_and_write_html('essay_index.html', 'essays/index.html',
        essays_sorted=essays_sorted, tags=essays_by_tag)
    for tag, essays_with_tag in essays_by_tag.items():
        compile_and_write_html('essay_tag.html', 'essays/tags/{}/index.html'.format(tag),
            tag=tag, essays_with_tag=essays_with_tag)
    make_rss(essays_sorted)
    subprocess.run('cp -r -p {static} {staging}/{static}'.format(
        static=STATIC_DIR, staging=STAGING_DIR), shell=True)

class RecompileEssayHandler(watchdog.events.LoggingEventHandler):
    def dispatch(self, event):
        essay_path = os.fsdecode(event.src_path)
        if is_public_essay(essay_path):
            print(f'Recompiling {essay_path}...', end="")
            compile_essay(essay_path)
            print('done.')
        else:
            print(f"Ignoring {essay_path}")

if __name__ == '__main__':
    compile_all()
    webserver = subprocess.Popen(['python', '-m', 'http.server', '8888', '-d', 'staging'])
    atexit.register(webserver.kill)
    event_handler = RecompileEssayHandler()
    observer = watchdog.observers.Observer()
    observer.schedule(event_handler, ESSAY_DIR, recursive=True)
    observer.start()
    try:
        while observer.is_alive():
            observer.join(1)
    finally:
        observer.stop()
        observer.join()


import os

path ="D:\School\Year 4\LP4\Statistical learning for big data"
#we shall store all the file names in this list
filelist = []

for root, dirs, files in os.walk(path):
	for file in files:
        #append the file name to the list
		filelist.append(os.path.join(root,file))

for name in filelist:
	print(name)

filelist_new = []
for name in filelist:
	name_new = name.split('\\')
	for i,name in enumerate(name_new):
		if name == 'LP4':
			name_new[i] = 'LP4a'
	filelist_new.append('\\'.join(name_new))

for name in filelist_new:
	print(name)


#!/usr/bin/python
"""
    Starting for usage at the computer
    Do not delete the except Typer error. GPS data error, added when include GPS
"""

import time
import sys
import signal
import inspect

from core.bebop import *
drone=Bebop()

dX = 3 # 3 mts Forward
dY = 1 # 1 mts Right
dZ = 0 # 0 mts Down
dPsi = 0 # 0 rad

def main():
    signal.signal(signal.SIGINT, signal_handler)
    try:
        drone.takeoff()
        time.sleep(2)
        drone.moveBy( dX, dY, dZ, dPsi)
        time.sleep(3)
        drone.hover()
        drone.land()
        sys.exit(0)
    except (TypeError) as e:
        pass

def signal_handler(signal, frame):
    drone.update( cmd=navigateHomeCmd( 0 ) )
    print('You pressed Ctrl+C!')
    print('Landing')
    drone.hover()
    time.sleep(1)
    if drone.flyingState is None or drone.flyingState == 1: # taking off
        drone.emergency()
    drone.land()
    sys.exit(0)

if __name__ == "__main__":
    main()

"""
    moveBy

    Draft: this command is not implemented yet by the firmware Move the drone to a relative position and rotate heading by a given angle The frame is horizontal and relative to the current drone orientation: - X is front - Y is right - Z is down The movement settings of the device are those set for the autonomous flight.

    dX Wanted displacement along the front axis [m]
    dY Wanted displacement along the right axis [m]
    dZ Wanted displacement along the down axis [m]
    dPsi Wanted rotation of heading [rad]

    """


# -*- coding: utf-8 -*-
__author__ = "yangtao"

import sys
import datetime
import os

from PySide2 import QtWidgets
from PySide2 import QtGui

class Unpack_Widget(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Unpack_Widget, self).__init__(parent=parent)
        self.setWindowTitle("释放")
        self.setMinimumWidth(400)

        self.pack_dir_edit = QtWidgets.QLineEdit()
        self.pack_dir_edit.setPlaceholderText("输入“包文件”夹路径")
        self.resolve_button = QtWidgets.QPushButton("解析")
        self.pack_dir_layout = QtWidgets.QHBoxLayout()
        self.pack_dir_layout.addWidget(self.pack_dir_edit)
        self.pack_dir_layout.addWidget(self.resolve_button)

        self.map_layout = QtWidgets.QFormLayout()

        # 开始
        self.start_button = QtWidgets.QPushButton("释放")
        self.start_button.setEnabled(False)

        self.main_layout = QtWidgets.QVBoxLayout(self)
        self.main_layout.addLayout(self.pack_dir_layout)
        self.main_layout.addLayout(self.map_layout)
        self.main_layout.addWidget(self.start_button)

        self.start_button.clicked.connect(lambda: self.close())

    def get_map(self, map_label):
        drives_map = {}
        for l in map_label:
            value = self.findChild(QtWidgets.QLineEdit, l).text()
            drives_map[l] = value
        return drives_map

    def add_map(self, map_label):
        self.remove_map()
        if map_label:
            for l in map_label:
                label = QtWidgets.QLabel("%s >>> "%l)
                edit = QtWidgets.QLineEdit()
                edit.setObjectName(l)
                self.map_layout.addRow(label, edit)
            self.start_button.setEnabled(True)

    def remove_map(self):
        for row in range(self.map_layout.rowCount()):
            self.map_layout.removeRow(0)
        self.start_button.setEnabled(False)

class Pack_Widget(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Pack_Widget, self).__init__(parent=parent)
        self.setWindowTitle("打包")
        self.setMinimumWidth(400)

        file_name_label = QtWidgets.QLabel("Nuke工程文件")
        self.file_name_edit = QtWidgets.QLineEdit()
        export_dir_label = QtWidgets.QLabel("输出路径")
        self.export_dir_edit = QtWidgets.QLineEdit()

        # 开始
        self.start_button = QtWidgets.QPushButton("打包")

        self.main_layout = QtWidgets.QFormLayout(self)
        self.main_layout.addRow(file_name_label, self.file_name_edit)
        self.main_layout.addRow(export_dir_label, self.export_dir_edit)
        self.main_layout.addRow(self.start_button)

        self.start_button.clicked.connect(lambda :self.close())

class Mian_Widget(QtWidgets.QWidget):
    def __init__(self):
        super(Mian_Widget, self).__init__()
        self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(QtWidgets.QStyle.SP_TitleBarCloseButton))
        self.setWindowTitle("DZL Nuke 工程打包工具 by yangtao")
        self.setMinimumWidth(485)

        # 信息框
        self.info_edit = QtWidgets.QTextEdit()
        self.info_edit.setWordWrapMode(QtGui.QTextOption.NoWrap)

        # 打包，释放按钮
        self.pack_button = QtWidgets.QPushButton("打包")
        self.pack_button.setObjectName("pack_project")
        self.pack_button.setIcon(QtWidgets.QApplication.style().standardIcon(QtWidgets.QStyle.SP_ArrowDown))
        self.unpack_button = QtWidgets.QPushButton("释放")
        self.unpack_button.setObjectName("unpack_project")
        self.unpack_button.setIcon(QtWidgets.QApplication.style().standardIcon(QtWidgets.QStyle.SP_ArrowUp))

        expand_button_layout = QtWidgets.QHBoxLayout()
        expand_button_layout.addWidget(self.pack_button)
        expand_button_layout.addWidget(self.unpack_button)

        # 打包，释放 UI
        self.pack_widget = Pack_Widget(self)
        self.unpack_widget = Unpack_Widget(self)
        #self.unpack_widget.add_map(["a", "b", "c", "d"])
        #self.unpack_widget.setHidden(True)

        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.addWidget(self.info_edit)
        main_layout.addLayout(expand_button_layout)

        # connect
        self.pack_button.clicked.connect(self.show_pack_widget)
        self.unpack_button.clicked.connect(self.show_pack_widget)

    def set_button_enabled(self, status):
        self.pack_button.setEnabled(status)
        self.unpack_button.setEnabled(status)

    def add_info(self, text, add_time_line=True):
        current_info = self.info_edit.toPlainText()
        if add_time_line:
            time_line = "%s:\n"%datetime.datetime.now().strftime("%H:%M:%S")
        else:
            time_line = ""
        new_info = current_info + time_line + text
        self.info_edit.setText("%s\n"%new_info)
        self.info_edit.moveCursor(QtGui.QTextCursor.End)

    def show_pack_widget(self):
        if self.sender().objectName() == "pack_project":
            self.pack_widget.show()
            self.unpack_widget.close()

        if self.sender().objectName() == "unpack_project":
            self.pack_widget.close()
            self.unpack_widget.show()

if __name__ == "__main__":
    app = QtWidgets.QApplication()
    mw = Mian_Widget()
    mw.show()
    sys.exit(app.exec_())


import sys

start = 291 * 512
fileSize = int(sys.argv[1])

bytesPerRead = 512

readFile = open("Files/image.dat", "rb")
readFile.seek(start)
writeFile = open("out.zip", "wb")

bytesRemaining = fileSize
while bytesRemaining > 0:
    bytesToRead = bytesPerRead if (bytesPerRead < bytesRemaining) else bytesRemaining
    bytes = readFile.read(bytesToRead)
    writeFile.write(bytes)
    bytesRemaining -= bytesToRead

readFile.close()
writeFile.close()


import ctypes
import time

import pandas as pd  # 가져온 채팅내용 DF로 쓸거라서
import win32api
import win32con
import win32gui
from pywinauto import clipboard  # 채팅창내용 가져오기 위해

# 카톡창 이름
# kakao_opentalk_name = '♥ 디아블로2레저렉션 대장방 ♥'
kakao_opentalk_name = '이인우'
# 체크할 커맨드
# chat_command = '계약번호'
chat_command = 'ㅋ'

PBYTE256 = ctypes.c_ubyte * 256
_user32 = ctypes.WinDLL("user32")
GetKeyboardState = _user32.GetKeyboardState
SetKeyboardState = _user32.SetKeyboardState
PostMessage = win32api.PostMessage
SendMessage = win32gui.SendMessage
FindWindow = win32gui.FindWindow
IsWindow = win32gui.IsWindow
GetCurrentThreadId = win32api.GetCurrentThreadId
GetWindowThreadProcessId = _user32.GetWindowThreadProcessId
AttachThreadInput = _user32.AttachThreadInput

MapVirtualKeyA = _user32.MapVirtualKeyA
MapVirtualKeyW = _user32.MapVirtualKeyW

MakeLong = win32api.MAKELONG
w = win32con

def kakao_sendtext(chatroom_name, text):
    """ 채팅방 메시지 전송 """
    # # 핸들 _ 채팅방
    hwndMain = win32gui.FindWindow(None, chatroom_name)
    hwndEdit = win32gui.FindWindowEx(hwndMain, None, "RICHEDIT50W", None)

    win32api.SendMessage(hwndEdit, win32con.WM_SETTEXT, 0, text)
    SendReturn(hwndEdit)

def copy_chatroom(chatroom_name):
    """ 채팅방 내용 복사 """
    # # 핸들 _ 채팅방
    hwndMain = win32gui.FindWindow(None, chatroom_name)
    hwndListControl = win32gui.FindWindowEx(hwndMain, None, "EVA_VH_ListControl_Dblclk", None)

    # #조합키, 본문을 클립보드에 복사 ( ctl + c , v )
    time.sleep(0.5)
    PostKeyEx(hwndListControl, ord('A'), [w.VK_CONTROL], False)
    time.sleep(0.5)
    PostKeyEx(hwndListControl, ord('C'), [w.VK_CONTROL], False)
    time.sleep(1)
    ctext = clipboard.GetData()
    # print(ctext)
    return ctext

def PostKeyEx(hwnd, key, shift, specialkey):
    """ 조합키 """
    if IsWindow(hwnd):

        ThreadId = GetWindowThreadProcessId(hwnd, None)

        lparam = MakeLong(0, MapVirtualKeyA(key, 0))
        msg_down = w.WM_KEYDOWN
        msg_up = w.WM_KEYUP

        if specialkey:
            lparam = lparam | 0x1000000

        if len(shift) > 0:
            pKeyBuffers = PBYTE256()
            pKeyBuffers_old = PBYTE256()

            SendMessage(hwnd, w.WM_ACTIVATE, w.WA_ACTIVE, 0)
            AttachThreadInput(GetCurrentThreadId(), ThreadId, True)
            GetKeyboardState(ctypes.byref(pKeyBuffers_old))

            for modkey in shift:
                if modkey == w.VK_MENU:
                    lparam = lparam | 0x20000000
                    msg_down = w.WM_SYSKEYDOWN
                    msg_up = w.WM_SYSKEYUP
                pKeyBuffers[modkey] |= 128

            SetKeyboardState(ctypes.byref(pKeyBuffers))
            time.sleep(0.01)
            PostMessage(hwnd, msg_down, key, lparam)
            time.sleep(0.01)
            PostMessage(hwnd, msg_up, key, lparam | 0xC0000000)
            time.sleep(0.01)
            SetKeyboardState(ctypes.byref(pKeyBuffers_old))
            time.sleep(0.01)
            AttachThreadInput(GetCurrentThreadId(), ThreadId, False)

        else:
            SendMessage(hwnd, msg_down, key, lparam)
            SendMessage(hwnd, msg_up, key, lparam | 0xC0000000)

def SendReturn(hwnd):
    """ 엔터키 입력 """
    win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0)
    time.sleep(0.01)
    win32api.PostMessage(hwnd, win32con.WM_KEYUP, win32con.VK_RETURN, 0)

def open_chatroom(chatroom_name):
    """ 채팅방 오픈 """
    # 채팅방 목록 검색하는 Edit (채팅방이 열려있지 않아도 전송 가능하기 위하여)
    hwndkakao = win32gui.FindWindow(None, "카카오톡")
    hwndkakao_edit1 = win32gui.FindWindowEx(hwndkakao, None, "EVA_ChildWindow", None)
    hwndkakao_edit2_1 = win32gui.FindWindowEx(hwndkakao_edit1, None, "EVA_Window", None)
    hwndkakao_edit2_2 = win32gui.FindWindowEx(hwndkakao_edit1, hwndkakao_edit2_1, "EVA_Window",
                                              None)  # ㄴ시작핸들을 첫번째 자식 핸들(친구목록) 을 줌(hwndkakao_edit2_1)
    hwndkakao_edit3 = win32gui.FindWindowEx(hwndkakao_edit2_2, None, "Edit", None)

    # # Edit에 검색 _ 입력되어있는 텍스트가 있어도 덮어쓰기됨
    win32api.SendMessage(hwndkakao_edit3, win32con.WM_SETTEXT, 0, chatroom_name)
    time.sleep(0.5)  # 안정성 위해 필요
    SendReturn(hwndkakao_edit3)
    time.sleep(0.5)

def save_last_chat():
    """ 채팅방 오픈, 마지막 채팅정보 리턴 """
    print("최초 세팅 시작")
    open_chatroom(kakao_opentalk_name)  # 채팅방 열기
    copied_chat_text = copy_chatroom(kakao_opentalk_name)  # 채팅내용 가져오기
    copied_chat_array = copied_chat_text.split('\r\n')  # \r\n 으로 스플릿 (대화내용에 개행이 포함뙨 경우 \r 때문에 스플릿 안됨)
    copied_chat_df = pd.DataFrame(copied_chat_array)  # DF 으로 바꾸기
    copied_chat_df[0] = copied_chat_df[0].str.replace('\[([\S\s]+)\] \[(오전|오후)([0-9:\s]+)\] ',
                                                      '')  # 정규식으로 이름, 시간 지우고 채팅내용만 남기기

    # 마지막 채팅의 인덱스, 채팅내용 리턴
    return copied_chat_df.index[-2], copied_chat_df.iloc[-2, 0]

# 채팅방 커멘드 체크
def check_command_chat(cls, clst):
    print("채팅방 내용을 탐색합니다")
    open_chatroom(kakao_opentalk_name)  # 채팅방 열기
    copied_chat_text = copy_chatroom(kakao_opentalk_name)  # 채팅내용 가져오기
    copied_chat_array = copied_chat_text.split('\r\n')  # \r\n 으로 스플릿 (대화내용에 개행이 포함뙨 경우 \r 때문에 스플릿 안됨)
    copied_chat_df = pd.DataFrame(copied_chat_array)  # DF 으로 바꾸기
    copied_chat_df[0] = copied_chat_df[0].str.replace('\[([\S\s]+)\] \[(오전|오후)([0-9:\s]+)\] ',
                                                      '')  # 정규식으로 이름, 시간 지우고 채팅내용만 남기기

    # 초기 세팅 시 마지막 채팅정보와 현재 시점의 마지막 채팅정보가 같을 때
    if copied_chat_df.iloc[-2, 0] == clst:
        print("새로운 채팅 없음")
        return copied_chat_df.index[-2], copied_chat_df.iloc[-2, 0]
    else:
        print("새로운 채팅 있음")
        copied_new_chat_df = copied_chat_df.iloc[cls + 1:, 0]  # 초기 세팅 시 채팅을 제외한 신규 채팅내용만 남김
        is_command_found = copied_new_chat_df[copied_new_chat_df.str.contains(chat_command)]  # 커맨드가 있는지 확인
        if 1 <= int(is_command_found.count()):
            print("-------커멘드 확인--------")
            # message = str(copied_new_chat_df).split(chat_command)[1].replace('[', '').replace(']', '')[:7]
            message = copied_chat_df.iloc[-2, 0]
            kakao_sendtext(kakao_opentalk_name, message)

            # 명령어 여러개 쓸경우 리턴값으로 각각 빼서 쓰면 될듯. 일단 테스트용으로 위에 하드코딩 해둠
            return copied_chat_df.index[-2], copied_chat_df.iloc[-2, 0]

        else:
            print("-------커멘드 미확인--------")
            return copied_chat_df.index[-2], copied_chat_df.iloc[-2, 0]

# # 네이버 실검 상위 20개, 리턴
def get_message_to_send():
    s = "계약번호가 있네요"
    return s

def main():
    last_chat_index, last_chat_text = save_last_chat()  # 초기 채팅방 열기, 마지막 채팅 정보 저장

    tryCount = 0
    while True:
        tryCount = tryCount + 1
        print("시도횟수: " + str(tryCount))
        last_chat_index, last_chat_text = check_command_chat(last_chat_index, last_chat_text)  # 커멘드 체크
        time.sleep(3)

if __name__ == '__main__':
    main()


import re
import os
import csv
from itertools import islice

import numpy as np

def parse_inputs(filename, label_dict):
    """Read input file and convert into inputs, labels for training

    Args:
      filename: text file - has content format as
          image_path, x1, x2, y1, y2, label1
          image_path, x1, x2, y1, y2, label2
      label_dict:  an encoding dictionary -
        mapping class names to indices

    Returns:
      inputs :  a list of all image paths to dataset
      labels :  a dictionary,
        key : image_path
        value: all objects in that image

    @TODO: when dataset is large, we should consider this method a generator
    """
    training_instances = dict()
    with open(filename, "r") as f:
        reader = csv.reader(f)
        for line in islice(reader, 1, None):
            if not line:
                continue  # Ignore empty line

            img_path = line[0]
            cls_name = line[-1]
            x1, y1, x2, y2 = [float(x) for x in line[1:-1]]
            an_object = [y1, x1, y2, x2, label_dict[cls_name]]

            if img_path in training_instances:
                training_instances[img_path].append(an_object)
            else:
                training_instances[img_path] = [an_object]
    inputs = training_instances.keys()
    labels = {k: np.stack(v).flatten() for k, v in training_instances.items()}

    return inputs, labels

def parse_label_map(label_map_path):
    """Parse label map file into a dictionary
    Args:
      label_map_path:

    Returns:
      a dictionary : key: obj_id value: obj-name
    """
    # match any group having language of {id:[number] .. name:'name'}
    parser = re.compile(r'id:[^\d]*(?P<id>[0-9]+)\s+name:[^\']*\'(?P<name>[\w_-]+)\'')

    with open(label_map_path, 'r') as f:
        lines = f.read().splitlines()
        lines = ''.join(lines)

        # a tuple (id, name)
        result = parser.findall(lines)
        label_map_dict = {int(item[0]): item[1] for item in result}

        return label_map_dict

def load_data(starting_path, file_extensions=['jpg', 'png', 'jpeg'], load_gt=False):
    data = {'': {}}
    for dir_path, subdir_list, file_names in os.walk(starting_path):
        d = data
        dir_path = dir_path[len(starting_path):]
        for sub_dir in dir_path.split(os.sep):
            based = d
            d = d[sub_dir]
        if subdir_list:
            for dn in subdir_list:
                d[dn] = {}
        else:
            label_path = os.path.join(starting_path + dir_path, 'labels.csv')
            labels = None
            if os.path.isfile(label_path):
                labels = parse_detection_file(label_path) if not load_gt else parse_ground_truth_file(label_path)

            based[sub_dir] = {'images': [os.path.join(dir_path, f)
                                         for f in file_names if f.split('.')[-1] in file_extensions],
                              'detections':  labels if labels else None}

    return data['']

def parse_ground_truth_file(gt_file):
    ground_truths = {}
    with open(gt_file, 'r') as f:
        lines = f.readlines()
        splitlines = [x.strip().split(' ') for x in lines][1:]

        for line in splitlines:
            img_id = line[0]
            obj_bbox = np.array(line[1:5], dtype=np.float)
            obj_idx = float(line[-1])
            if img_id not in ground_truths.keys():
                ground_truths[img_id] = {
                    'scores': [obj_idx],
                    'bboxes': [obj_bbox]
                }
            else:
                ground_truths[img_id]['scores'].append(obj_idx)
                ground_truths[img_id]['bboxes'].append(obj_bbox)
    return ground_truths

def parse_detection_file(detection_file):

    detections = {}
    with open(detection_file, 'r') as f:
        lines      = f.readlines()
        splitlines = [x.strip().split(' ') for x in lines]

        for line in splitlines:
            img_id    = line[0]
            obj_score = float(line[1])
            obj_bbox  = np.array(line[2:], dtype=np.float)

            if img_id not in detections.keys():
                detections[img_id] = {
                    'scores': [obj_score],
                    'bboxes': [obj_bbox]
                }
            else:
                detections[img_id]['scores'].append(obj_score)
                detections[img_id]['bboxes'].append(obj_bbox)

    return detections

def flatten_dict(d, current_level, max_level):
    def expand(key, value, curr_level, max):
        if isinstance(value, dict) and current_level < max_level:
            return [(key + '::' + k, v) for k, v in flatten_dict(value, curr_level+1, max).items()]
        else:
            return [(key, value)]
    items = [item for k, v in d.items() for item in expand(k, v, curr_level=current_level, max=max_level)]
    return dict(items)


"""
This module implements a SRS File Reader.
"""
import re
import datetime
from collections import OrderedDict

import numpy as np

import astropy.io.ascii
import astropy.units as u
from astropy.table import Column, MaskedColumn, QTable, vstack

__all__ = ['read_srs']

def read_srs(filepath):
    """
    Parse a SRS table from NOAA SWPC.

    Parameters
    ----------
    filepath : `str`
        The full path to a SRS table.

    Returns
    -------
    table : `astropy.table.QTable`
        Table containing a stacked table from all the tables in the SRS file.
        The header information is stored in the ``.meta`` attribute.
    """
    with open(filepath) as srs:
        file_lines = srs.readlines()

    header, section_lines, supplementary_lines = split_lines(file_lines)

    return make_table(header, section_lines, supplementary_lines)

def make_table(header, section_lines, supplementary_lines):
    """
    From the separated section lines and the header, clean up the data and
    convert to a `~astropy.table.QTable`.
    """
    meta_data = get_meta_data(header, supplementary_lines)

    tables = []
    for i, lines in enumerate(section_lines):
        if lines:
            key = list(meta_data['id'].keys())[i]
            t1 = astropy.io.ascii.read(lines)

            # Change column names into titlecase
            column_names = list(t1.columns)
            t1.rename_columns(column_names, new_names=[col.title() for col in column_names])

            if len(t1) == 0:
                col_data_types = {
                    # ID : <class 'str'>
                    'Nmbr': np.dtype('i4'),
                    'Location': np.dtype('U6'),
                    'Lo': np.dtype('i8'),
                    'Area': np.dtype('i8'),
                    'Z': np.dtype('U3'),
                    'Ll': np.dtype('i8'),
                    'Nn': np.dtype('i8'),
                    'Magtype': np.dtype('S4'),
                    'Lat': np.dtype('i8'),
                }
                for c in t1.itercols():
                    # Put data types of columns in empty table to correct types,
                    # or else vstack will fail.
                    c.dtype = col_data_types[c._name]
                t1.add_column(
                    Column(data=None, name="ID", dtype=('S2')), index=0)
            else:
                t1.add_column(Column(data=[key] * len(t1), name="ID"), index=0)

            tables.append(t1)

    out_table = vstack(tables)

    # Parse the Location column in Table 1
    if 'Location' in out_table.columns:
        col_lat, col_lon = parse_location(out_table['Location'])
        del out_table['Location']
        out_table.add_column(col_lat)
        out_table.add_column(col_lon)

    # Parse the Lat column in Table 3
    if 'Lat' in out_table.columns:
        parse_lat_col(out_table['Lat'], out_table['Latitude'])
        del out_table['Lat']

    # Give columns more sensible names
    column_mapping = {
        'Nmbr': 'Number',
        'Nn': 'Number of Sunspots',
        'Lo': 'Carrington Longitude',
        'Magtype': 'Mag Type',
        'Ll': 'Longitudinal Extent',
    }

    for old_name, new_name in column_mapping.items():
        out_table.rename_column(old_name, new_name)

    # Define a Solar Hemispere Unit
    a = {}
    u.def_unit(
        "SH",
        represents=(2 * np.pi * u.solRad**2),
        prefixes=True,
        namespace=a,
        doc="A solar hemisphere is the area of the visible solar disk.")

    # Set units on the table
    out_table['Carrington Longitude'].unit = u.deg
    out_table['Area'].unit = a['uSH']
    out_table['Longitudinal Extent'].unit = u.deg

    out_table.meta = meta_data

    # Number should be formatted in 10000 after 2002-06-15.
    if out_table.meta['issued'] > datetime.datetime(2002, 6, 15):
        out_table['Number'] += 10000

    return QTable(out_table)

def split_lines(file_lines):
    """
    Given all the lines in the file split based on the three sections and
    return the lines for the header, a list of lines for each section that
    is not 'None', and a list of supplementary lines after the main sections
    if not 'None'.
    """
    section_lines = []
    final_section_lines = []
    for i, line in enumerate(file_lines):
        if re.match(r'^(I\.|IA\.|II\.)', line):
            section_lines.append(i)
        if re.match(r'^(III|COMMENT|EFFECTIVE 2 OCT 2000|PLAIN|This message is for users of the NOAA/SEC Space|NNN)', line, re.IGNORECASE):
            final_section_lines.append(i)

    if final_section_lines and final_section_lines[0] > section_lines[-1]:
        section_lines.append(final_section_lines[0])

    header = file_lines[:section_lines[0]]
    header += [file_lines[s] for s in section_lines]

    # Append comments to the comment lines
    for line in section_lines:
        file_lines[line] = '# ' + file_lines[line]
    t1_lines = file_lines[section_lines[0]:section_lines[1]]
    # Remove the space so table reads it correctly
    t1_lines[1] = re.sub(r'Mag\s*Type', r'Magtype', t1_lines[1], flags=re.IGNORECASE)
    t2_lines = file_lines[section_lines[1]:section_lines[2]]

    # SRS files before 2000-10-02 files may have an empty `COMMENT` column in ``t2_lines``
    if "COMMENT" in t2_lines[1].split():
        expected_pattern_dict = {
            'Nmbr': r'^\d+$',
            'Location': r'^(?:[NESW](?:\d{2})){1,2}$',
            'Lo': r'^\d+$',
        }
        # Try to drop the comment column and return in original format
        t2_lines[1:] = _try_drop_empty_column("COMMENT", t2_lines[1:], expected_pattern_dict)

    if len(section_lines) > 3:
        t3_lines = file_lines[section_lines[2]:section_lines[3]]
        supplementary_lines = file_lines[section_lines[3]:]
    else:
        t3_lines = file_lines[section_lines[2]:]
        supplementary_lines = None

    lines = [t1_lines, t2_lines, t3_lines]
    for i, ll in enumerate(lines):
        if len(ll) > 2 and ll[2].strip().title() == 'None':
            del ll[2]

    return header, lines, supplementary_lines

def get_meta_data(header, supplementary_lines):
    """
    Convert a list of header lines and a list of supplementary lines (if not 'None') into a meta data dict.
    """
    meta_lines = []
    for line in header:
        if line.startswith(':'):
            meta_lines.append(line)

    meta_data = {}
    for m in meta_lines:
        if re.search(r'Corrected\s*Copy', m, re.IGNORECASE):
            meta_data['corrected'] = True
            continue
        k, v = m.strip().split(':')[1:]
        meta_data[k.lower()] = v.strip()
    meta_data['issued'] = datetime.datetime.strptime(meta_data['issued'],
                                                     "%Y %b %d %H%M UTC")

    # Get ID descriptions
    meta_data['id'] = OrderedDict()
    for h in header:
        if h.startswith(("I.", "IA.", "II.")):
            i = h.find('.')
            k = h[:i]
            v = h[i + 2:]
            meta_data['id'][k] = v.strip()

    meta_data['header'] = [h.strip() for h in header]

    if supplementary_lines:
        meta_data['supplementary_lines'] = [sl.strip() for sl in supplementary_lines]

    return meta_data

def parse_longitude(value):
    """
    Parse longitude in the form "W10" or "E10".
    """
    lonsign = {'W': 1, 'E': -1}
    if "W" in value or "E" in value:
        return lonsign[value[3]] * float(value[4:])

def parse_latitude(value):
    """
    Parse latitude in the form "S10" or "N10".
    """
    latsign = {'N': 1, 'S': -1}
    if "N" in value or "S" in value:
        return latsign[value[0]] * float(value[1:3])

def parse_location(column):
    """
    Given a column of location data in the form "S10E10" convert to two columns
    of angles.
    """
    latitude = MaskedColumn(name="Latitude", unit=u.deg)
    longitude = MaskedColumn(name="Longitude", unit=u.deg)

    for i, loc in enumerate(column):
        if loc:
            lati = parse_latitude(loc)
            longi = parse_longitude(loc)
            latitude = latitude.insert(i, lati)
            longitude = longitude.insert(i, longi)
        else:
            latitude = latitude.insert(i, None, mask=True)
            longitude = longitude.insert(i, None, mask=True)
    return latitude, longitude

def parse_lat_col(column, latitude_column):
    """
    Given an input column of "latitudes" in the form "S10" parse them and add
    them to an existing column of "latitudes".
    """
    for i, loc in enumerate(column):
        if loc:
            latitude_column.mask[i] = False
            latitude_column[i] = parse_latitude(loc)
    return latitude_column

def _try_drop_empty_column(column_name_to_drop, data_lines, pattern_dict):
    """
    Try dropping an empty ``column_name_to_drop`` from ``data_lines``.

    Parameters
    ----------
    column_name_to_drop : `str`
        Name of the empty column to be dropped.
    data_lines : `list[str]`
        List of lines extracted from a file (each line is a string)
        corresponding to the header (e.g. ``header = data_lines[0]``)
        and the data (``data = data_lines[1:]``)
    pattern_dict : `dict`
        A dictionary specifying the patterns to match for each column

    Returns
    -------
    `list[str]`
        The modified ``data_lines`` in titlecase with the specified column dropped, if all validations pass.

    """
    # Create a lowercase pattern dict
    pattern_dict_lower = {key.lower(): value for key, value in pattern_dict.items()}

    # Extract columns and rows
    header_line, *row_lines = data_lines
    column_list = [column.strip().lower() for column in header_line.split()]

    # Drop ``column_name_to_drop`` if exists
    try:
        column_index = column_list.index(column_name_to_drop.strip().lower())
        column_list.pop(column_index)
    except ValueError:
        raise ValueError(f"The column '{column_name_to_drop}' does not exist.")

    # Remove the dropped column from pattern_dict
    pattern_dict_lower.pop(column_name_to_drop.strip().lower(), None)

    # If the data is `None`, just return the header/data
    if row_lines[0].strip().title() == 'None':
        # Return as titlecase
        column_list = [col.title() for col in column_list]
        return [" ".join(column_list)] + row_lines

    # Check if the remaining columns are a subset of the columns in pattern_dict
    remaining_columns_set = set(column_list)
    pattern_columns_set = set(pattern_dict_lower.keys())
    if not remaining_columns_set.issubset(pattern_columns_set):
        raise ValueError("The remaining columns are not a subset of the columns in ``pattern_dict``.")

    # Check if all rows have the same length as the remaining columns
    row_lengths_equal = all(len(row.split()) == len(column_list) for row in row_lines)
    if not row_lengths_equal:
        raise ValueError("not all rows have the same number of values as the remaining columns.")

    # Check that the row values are consistent with the provided pattern dictionary
    matching_pattern = all(all(re.match(pattern_dict_lower[column], value) for column, value in zip(column_list, row.split())) for row in row_lines)
    if not matching_pattern:
        raise ValueError("not all rows match the provided pattern.")

    # Return as titlecase
    column_list = [col.title() for col in column_list]
    return [" ".join(column_list)] + row_lines


import asyncio
import logging
from functools import wraps
from typing import Generator, Tuple

from fastapi import HTTPException
from google.auth.exceptions import InvalidValue, MalformedError
from google.auth.transport import requests
from google.oauth2 import id_token
from requests import status_codes

from app.config import get_settings
from app.models import GoogleAuth

log = logging
log.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s",
    handlers=[logging.FileHandler("logs.txt"), logging.StreamHandler()],
)

def chunkify(lst: list, size: int) -> Tuple[Generator[list, None, None], int]:
    """Chunks lst into unique subsets of length chunk_size"""
    chunks = 0
    for i in range(len(lst)):
        if not (i % size):
            chunks += 1
    return (lst[i : i + size] for i in range(0, len(lst), size)), chunks

def coroutine(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        return asyncio.run(f(*args, **kwargs))

    return wrapper

def decode_jwt(encoded_jwt: str):
    try:
        idinfo = id_token.verify_oauth2_token(
            encoded_jwt, requests.Request(), get_settings().google_client_id
        )
        return GoogleAuth(**idinfo)

    # Error for malformed jwt's
    except MalformedError:
        raise HTTPException(status_code=498, detail="Could not parse JWT")

    # Error for f.x expiration
    except InvalidValue as e:
        # Here we would refresh the token
        raise HTTPException(status_code=498, detail=str(e))


#!python
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import task, stdio
from common import unpack_datagramm_args
from prompt import ColoredPrompt
import socket

class BaseServer(DatagramProtocol, object):
    def __init__(self, prompt=None):
        self.clients = {}
        self.prompt = prompt or ColoredPrompt(prompt='>')

    def startProtocol(self):
        self.loop = task.LoopingCall(self.sendHeartbeat)
        self.loop.start(0.5)

    def stopProtocol(self):
        self.loop.stop()

    def datagramReceived(self, data, addr):
        if data.startswith('CONNECT'):
            self.handleConnect(addr, data)
        elif data.startswith('BEAT'):
            self.handleHeartbeat(addr)
        else:
            super(BaseServer, self).datagramReceived(data, addr)

    @unpack_datagramm_args
    def handleConnect(self, addr, private_ip, private_port, name):
        if addr in self.clients:
            return

        ep = {
            'name': name,
            'private_endpoint': (private_ip, private_port)
        }
        self.clients[addr] = ep
        self.transport.write('RESP|SUCCESS|', addr)
        self.prompt.log("connected %s - %s" % (addr, ep))

    def sendHeartbeat(self):
        for endpoint, client in self.clients.copy().iteritems():
            missed = client.get('missed_beats', 1)
            if missed > 30:
                del self.clients[endpoint]
                self.prompt.log("disconnected %s" % str(endpoint))
            else:
                client['missed_beats'] = missed + 1
                self.transport.write('HEART', endpoint)

    def handleHeartbeat(self, addr):
        if addr not in self.clients:
            return

        client = self.clients[addr]
        client['missed_beats'] = 0

class BaseClient(DatagramProtocol, object):
    def datagramReceived(self, data, addr):
        if data.startswith('RESP'):
            self.handleResponse(addr, data)
        elif data.startswith('HEART'):
            self.transport.write('BEAT', addr)
        else:
            super(BaseClient, self).datagramReceived(data, addr)

    def connectToServer(self, server_endpoint, private_endpoint):
        private_ip, private_port = private_endpoint
        name = BaseClient.getClientName()
        msg = b'CONNECT|%s|%s|%s' % (private_ip, private_port, name)
        self.transport.write(msg, server_endpoint)

    @staticmethod
    def getClientName():
        return socket.gethostname() or 'Unknown'

    @unpack_datagramm_args
    def handleResponse(self, addr, status, detailed_message):
        if status == 'ERROR':
            self.prompt.logError(detailed_message)