# -*- coding: utf-8 -*-
"""
Created on Wed Jul 22 15:11:07 2020

@author: hca
"""
import numpy as np
import numpy.ma as ma
import pandas as pd
import os
import pyemma
import matplotlib.pyplot as plt
from matplotlib import colors
import matplotlib.pylab as pl
from matplotlib.ticker import FormatStrFormatter, AutoLocator, MaxNLocator, ScalarFormatter, LogLocator, AutoMinorLocator
import matplotlib.gridspec as gridspec

import Discretize

def plot_scheme(states_scheme):

    color_rect = ['white', 'dimgrey']
    for idx_combination, (label,combination) in enumerate(states_scheme.items()):
        fig, ax_scheme = plt.subplots(1,1, figsize=(5,1))
        print(label)

        #ax_scheme.s
        for idx,color_idx in enumerate(combination):
            color = color_rect[color_idx]
            if idx == 0 and color_idx == 1:
                color = 'darkorange'
            rect = plt.Rectangle((idx,0), 1, 1, facecolor=color, edgecolor='black', linewidth=5)
            #print(idx, color, rect)
            ax_scheme.add_patch(rect)
        #ax_scheme.set_title(label)
        ax_scheme.set_xlim([0,5])
        ax_scheme.axis('off')
        #ax_scheme.set_ylim([0,9])
        fig.show()
        fig.savefig(f'/media/dataHog/hca/msAcT-acylOctamer/results/figures/comb_states/{label}.png', dpi=300)
            #(idx, idx+1), 2, 1, linewidth=1, edgecolor='r', facecolor='none')
    #ax_scheme.set_xticks(ax_scheme.get_xticks()-0.5)
    #ax_scheme.set_xticklabels(['A', 'T', 'E', 'S', 'B'])

    #ax_scheme.set_ylim([0,9])
    #ax_scheme.axis('scaled')

class Plots:

    loc1, loc2 =0, 350
    plt.rc('font', size=10)
    p_type = {'normal' : {'grid_layout' : (3,1), 'bar_width' : 0.8, 'outer_grid_heights' : [8,4,8], 'figsize' : (11,11)},
          'inhibition' : {'grid_layout' : (2,1), 'bar_width' : 0.4,  'outer_grid_heights' : [6,4], 'figsize' :(10,11)},
          'water' : {'grid_layout' : (3,3), 'bar_width' : 0.4,  'outer_grid_heights' : [6,2,6], 'figsize' :(10,11)}}

    def __init__(self,
                 supra_project : dict,
                    dG : pd.DataFrame,
                    combinatorial : pd.DataFrame,
                    plot_specs :dict,
                    input_states : dict,
                    subset_states : dict,
                    labels_regions = ['A', 'T', 'E', 'S', 'B'],
                    mol_water={} ,
                    color2='darkorange',
                    mol2={'BuOH' : 'ViAc', 'BeOH' : 'BeAc'},
                    discretized_matrixes={},
                    discretized_trajectories={},
                    metrics= False,
                    metric_method='Mutual Information (normalized)'):

        self.supra_project : dict = supra_project
        self.dG : pd.DataFrame = dG
        self.combinatorial : pd.DataFrame = combinatorial
        self.plot_specs : dict = plot_specs
        self.input_states : dict = input_states
        self.subset_states : dict = subset_states
        self.mol2 :dict = mol2
        self.color2 : str = color2
        self.mol_water : dict = mol_water
        self.metrics :bool = metrics
        self.metric_method : str = metric_method
        if self.metrics:
            self.discretized_trajectories : dict = discretized_trajectories
            self.discretized_matrixes : dict = discretized_matrixes

    @classmethod
    def MsAcTPublicationFigure(cls):
        print(cls)
        return cls.plotBindingProfileAndCombinatorial(cls)

        #return cls.plotBindingProfileAndCombinatorial()

    def plot_dg(self, input_df):

        it = self.it

        dg, dg_m, dg_p = self.getDeltaG(input_df, it, self.idx_it)

        ax = self.ax_dG
        loc1, loc2 = self.loc1, self.loc2
        ranges = dg.iloc[loc1:loc2].index.values
        if it.split('_')[-1] == '5mM':
            it = f'{it.split("_")[0]} + {" ".join(it.split("_")[1:])}'

        if self.project_type == 'water' and self.mol != 'H2O':
            #loc2 = loc2 - 60
            ranges = dg.iloc[loc1:loc2].index.values
            dg_diff = dg.iloc[loc1:loc2].values - self.dG_base_df.iloc[loc1:loc2].values

            ln = ax.plot(ranges,
                         dg_diff,
                         linestyle=self.linestyle,
                         color= self.color_mol[self.idx_it],
                         label=it)
            if self.idx_mol == 0:
                ax.set_ylabel(r'$\Delta$$\Delta$G ($\it{k}$$_B$T)')
            else:
                ax.set_yticklabels([])
            ax.set_ylim(-3, 1)
            ax.legend(loc='lower right')
        else:
            ln = ax.plot(ranges,
                         dg.iloc[loc1:loc2],
                         '.',
                         color=self.color_mol[self.idx_it],
                         label=it) #, marker=marker, fillstyle=fillstyle)
            ax.fill_between(ranges, dg_m.iloc[loc1:loc2], dg_p.iloc[loc1:loc2], alpha=0.5, color=self.color_mol[self.idx_it])
            ax.set_ylabel(r'$\Delta$G ($\it{k}$$_B$T)')
            ax.legend(loc='upper right')
        #ax.set_ylim(-1,14)
        ax.set_xticks([])

        ax.set_xscale('log')
        ax.axhline(y=0, ls='--', color='darkgray')

        ax.set_title(self.mol)
        ax.yaxis.set_minor_locator(AutoMinorLocator())
        #ax.xaxis.set_major_formatter(FormatStrFormatter("%d")) #ScalarFormatter())
        ax.spines["right"].set_visible(False)
        ax.spines["top"].set_visible(False)
        ax.grid(which='major', axis='y', linestyle='--')

        self.lns.append(ln)

        print(self.idx_it, self.it, self.mol)
        if self.project_type == 'water' and self.mol == 'H2O':
            self.dG_base_df = dg

    #TODO: merge with plot_dg
    def plot_dg_inhib(self, input_df):

        ax = self.ax_dG
        ax2 = self.ax2
        mol = self.mol
        mol2 = self.mol2
        lns = self.lns

        dg2, dg2_m, dg2_p = self.getDeltaG(input_df, self.it, self.idx_it)
        loc1, loc2 = self.loc1, self.loc2
        ranges = dg2.iloc[loc1:loc2].index.values
        ln2 = ax2.plot(ranges, dg2.iloc[loc1:loc2],'.', fillstyle='none', color=self.color2, label=f'5mM {mol2[mol]}')
        ax2.fill_between(ranges, dg2_m.iloc[loc1:loc2], dg2_p.iloc[loc1:loc2], alpha=0.2, color=self.color2)
        ax2.set_xlabel(r'$\itd$$_5$ ($\AA$)') #, color=self.color2)
        ax2.set_xlim(1.5, ax.get_xlim()[1])
        ax.set_xlim(1.5, ax.get_xlim()[1])
        ax2.set_xscale('log')
        #ax2.tick_params(axis='x', labelcolor=self.color2)
        ax2.set_ylim(-4, self.ax_dG.get_ylim()[1])
        lns.append(ln2)
        labs = [p[0].get_label() for p in lns]
        self.ax_dG.legend([ln[0] for ln in lns], labs,loc='upper right')
        #ax.set_title(mol)

        #ax2.set_ylim(-4, 6)

    def plot_regions(self, df):

        ax = self.ax_regions
        steps = self.steps
        count_total = df.size
        state_sampling_fraction = self.getStateSamplingFraction(df)

        steps_to_plot = [self.ax_dG.get_xlim()[0]]
        for i in steps:

            steps_to_plot.append(i)
        steps_to_plot.append(self.ax_dG.get_xlim()[1])

        sampling_fraction = [c for c in state_sampling_fraction]
        sampling_fraction.append(1.0)

        ax.step(steps_to_plot, sampling_fraction, where='post', linestyle=self.linestyle, color=self.color_mol[self.idx_it], label=self.it)

        if self.project_type == 'normal':
            ax.set_yscale('log')
            ax.set_yticks([1e-4, 1e-2, 1])

        ax.set_xlim(self.ax_dG.get_xlim())
        ax.set_xscale('log')
        ax.yaxis.set_minor_locator(AutoMinorLocator())
        ax.spines["right"].set_visible(False)
        ax.spines["top"].set_visible(False)

        if self.mol == 'ViAc':
            ax.set_xlabel(r'$\itd$$_{NAC,donor}$ ($\AA$)')
        elif self.project_type == 'water':
           ax.set_xlabel(r'$\itd$$_{NAC,hydrolysis}$ ($\AA$)')
           ax.set_ylim(0.8,1.05)

        else:
            ax.set_xlabel(r'$\itd$$_{NAC,acceptor}$ ($\AA$)')

        if (self.idx_mol == 0 and self.project_type == 'inhibition') or (self.idx_mol == 0 and self.mol == 'H2O'):
            ax.set_ylabel('Sampling\nfraction')

        if self.idx_it == len(self.iterables)-1:

            count_regions = self.count_regions[self.idx_it]
            label_ticks = []
            for i in self.steps:
                label_ticks.append(i)
            label_ticks.append(self.ax_regions.axes.get_xlim()[1])
            label_centers = np.mean(np.array([self.steps, label_ticks[1:]]), axis=0)

            if self.project_type == 'normal':
                label_location = 5e-4
            elif self.project_type == 'inhibition':
                label_location = 1e-1
            else:
                label_location = 0.85

            for idx_labels, x in enumerate(label_ticks): #
                if idx_labels >= 0:
                    ax.text(x, label_location, self.labels_regions[idx_labels], ha='right')
                if idx_labels < len(label_ticks)-1:
                    ax.vlines(x, 0, count_regions[idx_labels], linestyle='dotted', color='grey')

    def plot_combinatorial(self, df, opt_color=None):

        if isinstance(opt_color, np.ndarray):
            color = opt_color
        else:
            color = self.color_mol
        ax=self.ax_comb
        states = self.states

        #color = self.color_mol
        if self.project_type != 'normal':
            df.plot.barh(ax=ax,
                          color=color,
                          label=False),
                          #width=Plots.p_type[self.project_type]['bar_width'])
            #ax.set_xscale('log')
            ax.set_yticks(list(states.keys()))
            ax.set_xscale('log')
            ax.invert_yaxis()
            ax.set_xlabel('Sampling fraction')
            print(self.idx_mol)

            #plt.setp(ax.get_xtick(), visible=True)
            if self.idx_mol == 2:

                plt.setp(ax.get_yticklabels(), visible=False)
            else:
                ax.set_yticklabels(list(states.values())) #, rotation=0)
                ax.set_ylabel('States')
        else:
            df.plot.bar(ax=ax,
                        color=color,
                        label=False,
                        width=Plots.p_type[self.project_type]['bar_width'])

            ax.set_yscale('log')
            ax.set_xticks(list(states.keys()))
            ax.set_yticks([1e-6, 1e-4, 1e-2, 1])
            ax.set_xticklabels(list(states.values()), rotation=0)

            if self.project_type == 'normal':
                if self.idx_mol == 1:
                    ax.set_ylabel('Sampling fraction')
                    plt.setp(ax.get_xticklabels(), visible=False)
                elif self.idx_mol == len(self.projects)-1:
                    ax.set_xlabel('States')
                    ax.set_xticklabels(list(states.values()), rotation=0)

                else:
                    plt.setp(ax.get_xticklabels(), visible=False)
            elif self.project_type == 'inhibition':
                ax.set_ylabel('Sampling fraction')
                ax.set_xlabel('States')

        ax.grid(linestyle='--', axis='y')

        ax.legend().remove()

    def plot_wrapper(self,
                     dG_df : pd.DataFrame,
                     comb_df : pd.DataFrame):

        idx = self.idx_it
        list_its_ = comb_df.columns.get_level_values('l3').unique().to_list()

        if self.project_type == 'inhibition':
            list_its_.remove('5mM')

        comb_it_df = comb_df.loc[:, comb_df.columns.get_level_values('l3') == list_its_[idx]]

        self.counts_states[idx] =  np.asarray([np.count_nonzero(comb_it_df == i) for i in self.states]) / comb_it_df.size
        self.count_regions[idx] = self.getStateSamplingFraction(comb_it_df)

        self.plot_dg(dG_df)
        self.plot_regions(comb_it_df)

    def plot_metrics(self, correlation):

        #correlation = correlation_table[0]

        ax = self.ax_metric
        cmap= plt.get_cmap('YlGn') #'RdYlGn')
        cmap.set_bad('black')

        (state_labels, states) = list(self.states.values()), list(self.states.keys()),
        #cmap.set_bad('grey')

        if self.project_type == 'inhibition':
            label1, label2 = self.mol2[self.mol], self.mol
            ticks1, ticks2 = self.states_mol, states
            ticklabels1, ticklabels2 = self.state_labels_mol, state_labels
            vmin, _vmax = 0, 0.006156262816740886

        else:

            label2, label1 = 'water', self.mol
            ticks2, ticks1 = states, list(self.states_mol)
            ticklabels2, ticklabels1 = state_labels, list(self.state_labels_mol)
            correlation = correlation.T
            vmin, _vmax = 0, 0.008432989270410602

        print(ticks1, ticks2)
        print(ticklabels1, ticklabels2)

        vmin, vmax = 0, 1
        print(self.mol2[self.mol], self.mol, np.shape(correlation))
        corr_img = ax.imshow(correlation / _vmax, cmap=cmap, vmin=vmin, vmax=vmax, aspect='auto')

        ax.set_xticks(ticks1)
        ax.set_yticks(ticks2)
        ax.set_xticklabels(ticklabels1, rotation=90)
        ax.set_yticklabels(ticklabels2)

        ax.invert_yaxis()
        if self.project_type == 'inhibition':
            ax.set_xlabel(label1)
            ax.set_ylabel(label2)
            #ax.set_xticks(ticks1)
# =============================================================================
#
#             if self.idx_mol == len(self.projects)-1 :
#                 cbar = plt.colorbar(corr_img, ax=ax)
#                 cbar.set_label(self.metric_method)
# =============================================================================

        elif self.project_type == 'water':

            if self.it.split('_')[-1] == '5mM':
                it = f'{self.it.split("_")[0]}\n+\n{" ".join(self.it.split("_")[1:])}'
            else:
                it = self.it

            ax.set_ylabel(it, rotation='horizontal', va='center', ha='right')

            if self.idx_it != len(self.iterables)-1:
                plt.setp(ax.get_xticklabels(), visible=False)

            else:
                ax.set_xlabel(label1)

            if self.idx_mol == 1:
                plt.setp(ax.get_yticklabels(), visible=False)

        return corr_img

    def setSubplots(self, grid_dG, grid_comb):

        idx_mol = self.idx_mol
        project_type = self.project_type

        if idx_mol == 0:
            if project_type == 'normal':
                self.ax_dG = plt.subplot(grid_dG[0, idx_mol])
                self.ax_comb = plt.subplot(grid_comb[idx_mol,0])
                self.ax_regions = plt.subplot(grid_dG[1, idx_mol])
            elif project_type == 'inhibition':
                self.ax_dG = plt.subplot(grid_dG[0, idx_mol])
                self.ax_regions = plt.subplot(grid_dG[1, idx_mol])
            else:
                self.ax_dG = plt.subplot(grid_dG[0, 0])
                #self.ax_comb = plt.subplot(grid_comb[0,idx_mol])
                self.ax_regions = plt.subplot(grid_dG[1, 0])
        else:
            #ax = plt.subplot(grid_dG[0, idx_mol], sharey=ax)
            if project_type == 'normal':
                self.ax_dG = plt.subplot(grid_dG[0, idx_mol], sharey=self.ax_dG)
                self.ax_comb = plt.subplot(grid_comb[idx_mol,0], sharex=self.ax_comb, sharey=self.ax_comb)
            elif project_type == 'inhibition':
                self.ax_dG = plt.subplot(grid_dG[0, idx_mol], sharey=self.ax_dG)
                #self.ax_comb = plt.subplot(grid_comb[0, idx_mol], sharey=self.ax_comb)
            elif project_type == 'water':
                self.ax_comb = plt.subplot(grid_comb[0, idx_mol])

            self.ax_regions = plt.subplot(grid_dG[1, idx_mol], sharey=self.ax_regions)

        if project_type == 'inhibition':
            if self.metrics:
                sub_grid = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec = grid_comb[0, idx_mol], hspace=0, wspace=0, height_ratios=[4,10], width_ratios=[10,4])
                self.ax_metric = plt.subplot(sub_grid[1,0])
                self.ax_comb = plt.subplot(sub_grid[1,1], sharey=self.ax_metric)
                self.ax_comb2 = plt.subplot(sub_grid[0,0], sharex=self.ax_metric)

            else:
                self.ax_comb = plt.subplot(grid_comb[0,idx_mol])
                self.ax_comb2 = plt.subplot(grid_comb[0,idx_mol])

    def plotBindingProfileAndCombinatorial(self):
        figures = {}
        for project_type, projects in self.supra_project.items():
            print(project_type)
            input_states = self.input_states
            p_type = self.p_type
            dG = self.dG
            combinatorial = self.combinatorial
            plot_specs = self.plot_specs
            loc1, loc2 = self.loc1, self.loc2
            mol2 = self.mol2
            color2 = self.color2

            fig=plt.figure(figsize=p_type[project_type]['figsize'], constrained_layout=True) #
            (nrows, ncols) = p_type[project_type]['grid_layout']
            outer_grid = gridspec.GridSpec(nrows, ncols, hspace=0.3, height_ratios = p_type[project_type]['outer_grid_heights']) #, width_ratios = [3,1])

            if project_type == 'normal':
                grid_dG = gridspec.GridSpecFromSubplotSpec(2, len(projects), subplot_spec = outer_grid[0,:], hspace=0.3, wspace=0.1, height_ratios=[4,2])
                grid_comb = gridspec.GridSpecFromSubplotSpec(len(projects), 1, subplot_spec = outer_grid[2,:], hspace=0.2) #, wspace=0.1) #wspace=0.2)
                grid_figure = gridspec.GridSpecFromSubplotSpec(1, len(projects), subplot_spec = outer_grid[1,:], wspace=0.1)
            elif project_type == 'inhibition':
                grid_dG = gridspec.GridSpecFromSubplotSpec(2, len(projects), subplot_spec = outer_grid[0,:], hspace=0.3, wspace=0.1, height_ratios=[4,2])
                grid_comb = gridspec.GridSpecFromSubplotSpec(1, len(projects), subplot_spec = outer_grid[1,:], hspace=0.2, wspace=0.1)
                #grid_figure = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec = outer_grid[2,:], wspace=0.1)
            elif project_type == 'water':
                grid_dG = gridspec.GridSpecFromSubplotSpec(2, 3, subplot_spec = outer_grid[0,:], height_ratios=[2,1], hspace=0.2, wspace=0.2)
                grid_comb = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec = outer_grid[1,1:], wspace=0.2)
                grid_figure = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec = outer_grid[2,1:], wspace=0.3, height_ratios=[10,2])
                #grid_corr = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec = outer_grid[2,:], wspace=0.1)

            supra_df = pd.DataFrame()

            self.projects : dict = projects
            self.project_type : str = project_type
            self.labels_regions = list(self.subset_states[project_type].keys())

            for idx_mol, (mol, project) in enumerate(projects.items()):
                print(mol)

                if project_type != 'water':
                    dG_df = dG[project_type][mol]
                else:
                    base_df = dG[project_type][mol]
                    dG_df = base_df.iloc[:, base_df.columns.get_level_values(0) == mol].droplevel(0, axis=1)

                #TODOTODOTODOTODOTODOTODOTODOT for inhib and water. make function maybe
                combinatorial_df = combinatorial[project_type][mol]
                _states = np.unique(combinatorial_df.values.flat)
                self.states = {idx : input_states[project_type][k] for idx, k in enumerate(_states)}
                print(self.states)

                combinatorial_df.replace(to_replace=_states, value=self.states.keys())

                self.iterables = project.parameter
                self.steps = plot_specs[project_type][mol][0]
                self.color_mol = plot_specs[project_type][mol][1]
                self.linestyle = plot_specs[project_type]['linestyle']

                self.idx_mol = idx_mol
                self.mol = mol

                self.setSubplots(grid_dG, grid_comb)

                self.lns = []

                #ax_comb = plt.subplot(grid_comb[idx_mol])
                self.counts_states = np.empty([len(self.iterables), len(self.states)])
                self.count_regions = np.empty([len(self.iterables), len(self.steps)+1])

                for idx, it in enumerate(self.iterables):
                    self.it : str = it
                    self.idx_it = idx
                    self.plot_wrapper(dG_df, combinatorial_df)

                mol_df = pd.DataFrame(self.counts_states.T, index=list(self.states.keys()), columns=self.iterables)
                #mol_df = pd.DataFrame(counts_states, columns=list(states.keys()), index=iterables)
                supra_df = pd.concat([supra_df, mol_df], axis=1)
                if project_type != 'water':
                    self.plot_combinatorial(mol_df)

                #NOTE: loading of mol1 and mol1 + mol2 is done above. Here is only to handle mol2
                if project_type == 'inhibition':
                    print(mol)

                    self.ax2 = self.ax_dG.twiny()
                    self.color_mol = [color2]
                    self.linestyle = 'dashed'
                    self.idx_it = 0
                    self.it = '5mM'
                    inib_mol = mol2[mol]

                    inib_df = pd.read_csv(f'{project.results}/binding_profile/binding_profile_acylSer-His_{inib_mol}_mean_b0_e-1_s1.csv', index_col=0)
                    df_comb_inhib = combinatorial_df.loc[:, combinatorial_df.columns.get_level_values('l3') == self.it]

                    #TODO: fix dims
                    _states_mol = np.unique(df_comb_inhib.values.flat)
                    self.state_labels_mol = [v for k, v in self.states.items() if k in self.states_mol]

                    if self.metrics:
                        self.plot_metrics(self.discretized_matrixes[project_type][mol])
                    self.plot_dg_inhib(inib_df)
                    self.plot_regions(df_comb_inhib)
                    state_sampling_fraction = self.getStateSamplingFraction(df_comb_inhib, states= self.states_mol, w_subset=False)
                    x_values = self.ax_comb.get_xticks().flatten() + 0.35

                    #plot_combinatorial
                    print(inib_mol, self.states_mol, state_sampling_fraction)
                    self.ax_comb2.bar(self.states_mol, state_sampling_fraction, color=color2)
                    self.ax_comb2.set_yscale('log')

            #NOTE: This is only loading the other _water dfs. The df of water has been handled before
            if project_type == 'water':

                sub_w_grid_dG = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec = grid_dG[:,1:], height_ratios=[2,1], hspace=0.2, wspace=0.1)

                for idx_mol_w, (mol_w, iterables_mol_w2) in enumerate(self.mol_water.items()):
                    print(mol_w, iterables_mol_w2)

                        #self.plot_correlations(self.discretized_trajectories[project_type][mol_w])

                    base_df_mol = dG[project_type]['H2O']
                    dG_mol_df = base_df_mol.iloc[:, base_df_mol.columns.get_level_values(0) == mol_w].droplevel(0, axis=1)

                    color_mol = plot_specs['normal'][mol_w][1]
                    color_mol = np.vstack([color_mol, pl.cm.Oranges(np.linspace(0.5,0.5,1))])
                    color_mol_comb = np.vstack([pl.cm.Greys(np.linspace(1,1,1)), color_mol, pl.cm.Oranges(np.linspace(0.5,0.5,1))])

                    self.color_mol = color_mol
                    self.mol = mol_w
                    self.idx_mol = idx_mol_w

                    self.ax_dG = plt.subplot(sub_w_grid_dG[0, idx_mol_w])
                    self.ax_regions = plt.subplot(sub_w_grid_dG[1, idx_mol_w], sharey=self.ax_regions)
                    self.iterables = iterables_mol_w2[0] + [f'100mM_{iterables_mol_w2[1]}_5mM']
                    self.counts_states = np.empty([len(self.iterables), len(self.states)])
                    self.count_regions = np.empty([len(self.iterables), len(self.steps)+1])
                    if idx_mol_w == 0:
                        self.ax_comb = plt.subplot(grid_comb[0, idx_mol_w])
                    else:
                        self.ax_comb = plt.subplot(grid_comb[0, idx_mol_w], sharey=self.ax_comb)

                    _base_states_mol = np.unique(combinatorial_df.values.flat) #input_states['normal']
                    base_states_mol = {idx : input_states[project_type][k] for idx, k in enumerate(_base_states_mol)}
                    comb_df = combinatorial[project_type][mol]
                    comb_df = comb_df.iloc[:, comb_df.columns.get_level_values('l2') == mol_w]

                    _states = np.unique(comb_df.values.flat)

                    self.states = {idx : input_states[project_type][k] for idx, k in enumerate(_states)}
                    print(self.states)
                    comb_df.replace(to_replace=_states, value=self.states.keys())
                    self.states_mol = base_states_mol.keys()
                    self.state_labels_mol = base_states_mol.values()

                    if self.metrics:
                        sub_w_grid_metrics = gridspec.GridSpecFromSubplotSpec(len(self.iterables),1, subplot_spec = grid_figure[0, idx_mol_w])
                    for idx_w, it_w in enumerate(self.iterables):
                        self.it = it_w
                        self.idx_it = idx_w

                        #WARNING! This is assuming mol2 is the last iterable! make auto
                        if idx_w < len(self.iterables) -1 :
                            self.linestyle = plot_specs[project_type]['linestyle']
                        else:
                            self.linestyle = 'dashed'
                        #combinatorial_mol_df = comb_df.iloc[:, comb_df.columns.get_level_values('l2') == mol_w]

                        self.plot_wrapper(dG_mol_df, comb_df)

                        if self.metrics:
                            self.ax_corr = plt.subplot(sub_w_grid_metrics[idx_w,0])

                            corr_image = self.plot_metrics(self.discretized_matrixes[project_type][mol_w][it_w])

                    w_count_states = np.vstack([mol_df.values.T, self.counts_states])
                    mol_water_df = pd.DataFrame(w_count_states.T, index=list(self.states.keys()), columns=['0 mM'] + self.iterables)
                    self.plot_combinatorial(mol_water_df, opt_color=color_mol_comb)

                if self.metrics :

                    _colorbar = plt.subplot(grid_figure[1,:])
                    _colorbar.axis('off')
                    cbar = plt.colorbar(corr_image, ax=_colorbar, orientation='horizontal')
                    cbar.set_label(self.metric_method)
    # =============================================================================
    #             else:
    #                 ax_figure = fig.add_subplot(grid_figure[idx_mol])
    #                 if mol == 'BuOH':
    #                     image_mol = plt.imread(f'{project.results}/figures/msAcT_BuOH_100mM_activeSite_ATESB.png')
    #                 elif mol =='BeOH':
    #                     image_mol = plt.imread(f'{project.results}/figures/msAcT_BeOH_100mM_activeSite_ATESB.png')
    #                 else:
    #                     image_mol = plt.imread(f'{project.results}/figures/msAcT_ViAc_50mM_activeSite_ATESB.png')
    #
    #                 ax_figure.imshow(image_mol)
    #                 ax_figure.axis('off')
    # =============================================================================

            #plot_combinatorial(supra_df)
            #fig.tight_layout()
            fig.show()
            figures[project_type] = fig
            fig.savefig(f'{project.results}/figures/BindingProfile_combinatorial_{project_type}.png', dpi=600, bbox_inches='tight')

        return figures

    def getDeltaG(self, input_df, it, idx):

        if self.project_type == 'inhibition':
            #print(input_df)
            print(it, self.mol2[self.mol])
            if it == '100mM_{self.mol2[self.mol]}_5mM' or it == self.mol2[self.mol]:
                it = '5mM'

        dg = input_df[f'$\Delta$G {it}']
        if not isinstance(dg, pd.Series):
            dg = dg.iloc[:, idx]
            dg_index = [i for i, x in enumerate(input_df.columns.get_loc(dg.name)) if x][idx]
        else:
            dg_index = input_df.columns.get_loc(dg.name)
        dg_m = input_df.iloc[:, dg_index+1]
        dg_p = input_df.iloc[:, dg_index+2]

        return dg, dg_m, dg_p

    def getStateSamplingFraction(self, df, states=None, w_subset=True) -> list:

        count_total = df.size

        if states == None:
            states = self.states

        state_sampling_fraction = []
        if w_subset:
            for idx_regions, (label_state, subset) in enumerate(self.subset_states[self.project_type].items()):
                state_sampling_fraction.append(np.asarray([np.count_nonzero(df == i) / count_total for i in states if i in subset]).sum())
        else:
            state_sampling_fraction = np.asarray([np.count_nonzero(df == i) / count_total for i in states])

        return state_sampling_fraction

    def deprecated_plot_StateSampling_vs_scalars(self, df, ax):
        if self.project_type == 'water':
            iterable_scalars = [0]
            #WARNING! this is assuming mol2 is the last one on the list. make auto
            for it in self.iterables[:-1]:
                try:
                    bulk_value=float(str(it).split('M')[0])
                    unit='M'
                except:
                    bulk_value=float(str(it).split('mM')[0])
                    unit='mM'
                iterable_scalars.append(bulk_value)
            print(iterable_scalars)
            for state_col in df.columns:
                ax.plot(iterable_scalars, df[state_col].values[:-1])

def plot_layout(parameters):
    """Function to optimize plot layout based on number of subplots generated."""

    length=len(parameters)

    if length < 4:
        rows=1
        columns=length
    else:
        rows=2

        if not length % 2 == 0:
            columns=int(np.rint((length/2)+0.1))

        else:
            columns=int(length/2)

    if not length % 2 == 0 and length > 4:

        fix_layout=True
    else:
        fix_layout=False

    return rows, columns, fix_layout

def plot_feature_histogram(feature_dict):
    """Function to plot the histogram of raw features."""

    rows, columns, fix_layout=plot_layout(parameters)
    fig,axes=plt.subplots(rows, columns, sharex=True, constrained_layout=True)

    plt.suptitle(f'Feature: {name}')

    for plot, feature in zip(axes.flat, features.items()):
        for f in feature[1]:
            if not os.path.exists(f):
                feature[1].remove(f)
                print(f'\tCould not find data for {f}')

        data=np.asarray(pyemma.coordinates.load(feature[1]))
        plot.hist(data.flatten()) #, label=feature[0])
        plot.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
        plot.set_xlabel(r'{} ($\AA$)'.format(name))
        plot.set_ylabel('Counts')
        #plot.set_title(feature[0])

        data=[]

        fix_layout

    plt.savefig(f'{results}/histogram_{name}.png', bbox_inches="tight", dpi=600)

    return plt.show()

def plot_MFPT(mfpt_df, scheme, feature, parameters, error, regions=None, labels=None):
    """Function to plot heatmap of MFPTS between all states."""

    # Taken from matplotlib documentation. Make images respond to changes in the norm of other images (e.g. via the
    # "edit axis, curves and images parameters" GUI on Qt), but be careful not to recurse infinitely!
    def update(changed_image):
        for im in images:
            if (changed_image.get_cmap() != im.get_cmap() or changed_image.get_clim() != im.get_clim()):
                im.set_cmap(changed_image.get_cmap())
                im.set_clim(changed_image.get_clim())

    images = []

    rows, columns, fix_layout=plot_layout(parameters)
    fig, axes = plt.subplots(rows, columns, constrained_layout=True, figsize=(9,6))
    fig.suptitle(f'Discretization: {scheme}\nFeature: {feature} (error tolerance {error:.1%})', fontsize=14)

    cmap_plot=plt.cm.get_cmap("gist_rainbow")
    #cmap_plot.set_under(color='red')

    #cmap_plot.set_over(color='yellow')
    cmap_plot.set_bad(color='white')

    vmins, vmaxs=[], []

    for plot, parameter in zip(axes.flat, parameters):

        means=base.MSM.mfpt_filter(mfpt_df, scheme, feature, parameter, error)

        try:
            if scheme == 'combinatorial':
                contour_plot=plot.pcolormesh(means, edgecolors='k', linewidths=1, cmap=cmap_plot)
                label_names=base.Functions.sampledStateLabels(regions, sampled_states=means.index.values, labels=labels)
                positions=np.arange(0.5, len(label_names)+0.5)
                plot.set_xticks(positions)
                plot.set_xticklabels(label_names, fontsize=7, rotation=70)
                plot.set_yticks(positions)
                plot.set_yticklabels(label_names, fontsize=7)
            else:
                contour_plot=plot.pcolormesh(means, cmap=cmap_plot)
                ticks=plot.get_xticks()+0.5
                plot.set_xticks(ticks)
                plot.set_xticklabels((ticks+0.5).astype(int))
                plot.set_yticks(ticks[:-1])
                plot.set_yticklabels((ticks+0.5).astype(int)[:-1])

            plot.set_facecolor('white')
            plot.set_title(parameter) #, fontsize=8)
            plot.set_xlabel('From state', fontsize=10)
            plot.set_ylabel('To state', fontsize=10)
            images.append(contour_plot)

        except:
            print('No values to plot')

    # Find the min and max of all colors for use in setting the color scale.
    vmins=[]
    vmaxs=[]
    for image in images:
        array=image.get_array()
        try:
            vmin_i=np.min(array[np.nonzero(array)])
        except:
            vmin_i=1
        try:
            vmax_i=np.max(array[np.nonzero(array)])
        except:
            vmax_i=1e12
        vmins.append(vmin_i)
        vmaxs.append(vmax_i)

    vmin=min(vmins)
    vmax=max(vmaxs)
    #vmax = max(image.get_array().max() for image in images)

    norm = colors.LogNorm(vmin=vmin, vmax=vmax)

    for im in images:
        im.set_norm(norm)

    print(f'limits: {vmin:e}, {vmax:e}')

    cbar=fig.colorbar(images[-1], ax=axes)
    cbar.set_label(label=r'MFPT (ps)', size='large')
    cbar.ax.tick_params(labelsize=12)
    for im in images:
        im.callbacksSM.connect('changed', update)

    return images

def plot_flux(flux_df, ligand, results):
    """Function to plot fluxes from a dataframe of fluxes."""

    schemes=flux_df.index.unique(level='Scheme')
    features=flux_df.index.unique(level='feature')

    for scheme in schemes:
        for feature in features:

            properties=flux_df.columns

            fig, axes = plt.subplots(1,len(properties), sharex=True, constrained_layout=True)
            fig.suptitle(f'Discretization: {scheme}\n Feature: {feature}')

            for ax, prop in zip(axes, properties):

                flux_df.loc[(scheme, feature), prop].plot(linestyle='-', marker='o', ax=ax, title=prop)
                ax.set_xlabel(f'[{ligand}] (M)')
                ax.set_ylabel(prop+r' ($s^{-1}$)')
            plt.savefig(f'{results}/netFlux_{scheme}-{feature}.png', dpi=600)
            plt.show()

def plot_pathways(pathway_df, ligand, results):
    """Function to plot pathways from a dataframe of pathways."""

    schemes=pathway_df.index.unique(level='Scheme')
    features=pathway_df.index.unique(level='feature')

    for scheme in schemes:
        for feature in features:

            print(scheme, feature)

            pathway_df.loc[(scheme, feature)].dropna(axis=1, how='all').plot(linestyle='-', marker='o')
            plt.title(f'Discretization: {scheme}\n Feature: {feature}')
            plt.xlabel(f'[{ligand}] (M)')
            plt.ylabel(r'Pathway flux ($s^{-1}$)')
            plt.savefig(f'{results}/pathways_{scheme}-{feature}.png', dpi=600)
            plt.show()

def plot_committor(committor_df, ligand, results, regions, labels):
    """Function to plot fluxes from a dataframe of fluxes."""

    schemes=committor_df.index.unique(level='Scheme')
    features=committor_df.index.unique(level='feature')
    parameters=committor_df.index.unique(level='parameter')

    committors=('Forward', 'Backward')

    for scheme in schemes:
        for feature in features:

            fig, axes = plt.subplots(1,len(committors), sharey=True, constrained_layout=True)
            for parameter in parameters:
                for ax, committor in zip(axes, committors):
                    try:
                        df_c=committor_df.loc[(scheme, feature, parameter), committor].dropna()

                        if scheme == 'combinatorial':

                            df_c.plot(linestyle='-', marker='o', ax=ax, label=parameter, title=committor)

                            sampled_states=committor_df.unstack(level=2).loc[(scheme, feature)][committor].dropna().index.unique(level='states').values
                            label_names=base.Functions.sampledStateLabels(regions, sampled_states=sampled_states, labels=labels)

                            ax.set_xticks(sampled_states)
                            ax.set_xticklabels(label_names, rotation=70)

                        else:
                            df_c.plot(ax=ax, label=parameter, title=committor)
                        ax.set_ylabel('Committor Probability')
                    except:
                        print('no plot at ', parameter)

            plt.suptitle(f'Discretization: {scheme}\n Feature: {feature}')

            plt.legend(title=f'[{ligand}] (M)')
            plt.savefig(f'{results}/committors_{scheme}-{feature}.png', dpi=600)
            plt.show()


from __future__ import division
from collections import Counter
import math, random

# -*- coding: cp1252 -*-
#***************************************************************************************************
# **********************Chapter 6. Probability***************************************************

# We could also ask about the probability of the event 'both children are girls' conditional on the event 'at least one
# of the children is a girl' (L). Surprisingly, the answer is different from before!
# As before, the event B and L ('both children are girls and at least one of the children is a girl') is just the event B.
# This means we have:
# P(B|L)=P(B,L)/P(L)= P(B)/P(L)=1/3
# My solution:
# P(B/L)=P(L/B)*P(B)/P(L)
# P(L/B)=1 probability at least 1 is girl given both are girls 1.
# P(B)=(1/2)*(1/2)=1/4 Probability that both are girls.
# P(L)=GirlGirl+BoyGirl+GirlBoy=1/4+1/4+1/4=3/4
# So P(B)/P(L)=(1/4)*(4/3)=1/3

#Prove through program
def randon_kid():
    return random.choice(['boy','girl'])
'''
older_girl=0
both_girls=0
either_girl=0
random.seed(0)
for _ in xrange(10000):
    older=randon_kid()
    younger=randon_kid()
    if older=='girl':
        older_girl+=1
    if older=='girl' or younger=='girl':
        either_girl+=1
    if older=='girl' and younger=='girl':
        both_girls+=1
print 'P(both girls/older girl ', both_girls/older_girl
print 'P(both girls/either girl ', both_girls/either_girl
'''

# Expected value:
# We will sometimes talk about the expected value of a random variable, which is the average of its values weighted
# The coin flip variable has an expected value of 1/2 (= 0 * 1/2 + 1 * 1/2),
# by their probabilities. P1*outcome+P2*outcome...Pn*outcome
# and the range(10) variable has an expected value of 4.5=(.1*0+.1*1...+.1*9)

def uniform_pdf(x):
    return 1 if x >= 0 and x < 1 else 0

def uniform_cdf(x):
    "returns the probability that a uniform random variable is <= x"
    if x < 0: return 0 	        # uniform random is never less than 0
    elif x < 1: return x 	# e.g. P(X <= 0.4) = 0.4
    else: return 1 		# uniform random is always less than 1

# It tells probability at a particular point. Max p should be at x=0 for mu=0 and sigma=1 as 0 will be mean.
def normal_pdf(x, mu=0, sigma=1):
    sqrt_two_pi = math.sqrt(2 * math.pi)
    return (math.exp(-(x-mu) ** 2 / 2 / sigma ** 2) / (sqrt_two_pi * sigma))

# It tells probability from start to a particular point. Probability lies with some standard deviations.
# The cumulative distribution function for the normal distribution cannot be written in an
# using Python's math.erf:
def normal_cdf(x, mu=0,sigma=1):
    return (1 + math.erf((x - mu) / math.sqrt(2) / sigma)) / 2

# Sometimes we'll need to invert normal_cdf to find the value corresponding to a specified probability.
# There's no simple way to compute its inverse, but normal_cdf is continuous and strictly increasing, so we can use a binary search:
# The function repeatedly bisects intervals until it narrows in on a Z that's close enough to the desired probability.
# To do ????
def inverse_normal_cdf(p, mu=0, sigma=1, tolerance=0.00001):
    """find approximate inverse using binary search"""
    # if not standard, compute standard and rescale
    if mu != 0 or sigma != 1:
        return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)
    low_z, low_p = -10.0, 0          # normal_cdf(-10) is (very close to) 0
    hi_z, hi_p = 10.0, 1             # normal_cdf(10) is (very close to) 1
    while hi_z - low_z > tolerance:
        mid_z = (low_z + hi_z) / 2   # consider the midpoint
        mid_p = normal_cdf(mid_z)    # and the cdf's value there
        if mid_p < p:
            # midpoint is still too low, search above it
            low_z, low_p = mid_z, mid_p
        elif mid_p > p:
            # midpoint is still too high, search below it
            hi_z, hi_p = mid_z, mid_p
        else:
            break
    return mid_z

# An easy way to illustrate this is by looking at binomial random variables, which have two parameters n and p.
# A Binomial(n,p) random variable is simply the sum of n independent Bernoulli(p) random variables,
# each of which equals 1 with probability p and 0 with probability 1-p:
def bernoulli_trial(p):
    return 1 if random.random() < p else 0

def binomial(n, p):
    return sum(bernoulli_trial(p) for _ in range(n))

# 75 should have most value.
'''
data=Counter([binomial(100, .75) for _ in range(100)])
print data
'''

# ???? Later
def make_hist(p, n, num_points):
    data = [binomial(n, p) for _ in range(num_points)]
    # use a bar chart to show the actual binomial samples
    histogram = Counter(data)
    plt.bar([x - 0.4 for x in histogram.keys()],
            [v / num_points for v in histogram.values()],
            0.8,
            color='0.75')
    mu = p * n
    sigma = math.sqrt(n * p * (1 - p))
    # use a line chart to show the normal approximation
    xs = range(min(data), max(data) + 1)
    ys = [normal_cdf(i + 0.5, mu, sigma) - normal_cdf(i - 0.5, mu, sigma)
            for i in xs]
    plt.plot(xs,ys)
    plt.title("Binomial Distribution vs. Normal Approximation")
    plt.show()


import re
import requests
from bs4 import BeautifulSoup

MP_LIST_URL = "https://www.tbmm.gov.tr/develop/owa/milletvekillerimiz_sd.liste"

def get_mp_list_response(is_verify:bool=False):
    response = requests.get(MP_LIST_URL, verify=is_verify)
    return response

def get_mp_page_response(url:str, is_verify:bool=False):
    response = requests.get(url, verify=is_verify)
    return response

def create_parser(response):
    bs = BeautifulSoup(response.text, 'html.parser')
    return bs

def get_title(bs) -> str:
    title = bs.find('span', {'class':'AltKonuBaslikB16Gri'}).text.strip()
    return title

def parse_mp_list(bs, is_verbose:bool=False):
    rows = bs.find('table')
    cities = []
    for i, row in enumerate(rows):

        if is_verbose:
            print(i)
            print(row)

        is_city = row.find('span')

        if is_verbose:
            print('is city', is_city)

        if is_city is not None and is_city != -1:
            city = is_city.text
            city_dict = {'city': city, 'city_mps': []}
            cities.append(city_dict)

            if is_verbose:
                print("CITY: {}".format(city))

        else:
            is_mp = row.find('a')

            if is_verbose:
                print('is_mp', is_mp)

            if is_mp is not None and is_mp != -1:
                mp_name = is_mp.text
                mp_url = is_mp.attrs['href']
                mp_party = row.findAll('td')[-1].text

                if is_verbose:
                    print("MP: {}, Party: {}".format(mp_name, mp_party))
                    print(mp_url)

                m = re.match("https://www.tbmm.gov.tr/develop/owa/milletvekillerimiz_sd.bilgi\?p_donem=(\d+)&p_sicil=(\d+)", mp_url)
                mp_term = m.group(1) # p_donem
                mp_registery = m.group(2) # p_sicil
                mp_dict = {
                            'name': mp_name,
                            'term': mp_term,
                            'party': mp_party,
                            'registery': mp_registery,
                            'url': mp_url,
                        }
                cities[-1]['city_mps'].append(mp_dict)

    return cities

def parse_mp_page(bs):
    mp_name = bs.find('div', {'id': 'mv_isim'}).text.strip()
    mp_term = bs.find('div', {'id': 'mv_ili'}).text.strip().split(' ')[0][:-1]
    mp_city = bs.find('div', {'id': 'mv_ili'}).text.strip().split(' ')[2]
    mp_task = bs.find('div', {'id': 'mv_gorev'}).text.strip().split('\xa0')
    mp_photo_url = bs.find('div', {'id': 'fotograf_alani'}).find('img')['src']
    mp_party_logo_url = bs.find('div', {'id': 'parti_logo'}).find('img')['src']
    mp_contact = [m.text.strip() for m in bs.find('div', {'id': 'iletisim_bilgi'}).findAll('tr') if len(m.text.strip()) > 0]
    mp_cv = bs.find('div', {'id': 'ozgecmis_icerik'})
    mp_dict = {
        'name': mp_name,
        'term': mp_term,
        'city': mp_city,
        'tasks': mp_task,
        'photo_url': mp_photo_url,
        'party_logo_url': mp_party_logo_url,
        'contact': mp_contact,
        # 'mp_cv': mp_cv,
    }
    return mp_dict

def get_mp_list():
    r = get_mp_list_response()
    parser = create_parser(r)
    title = get_title(parser)
    cities = parse_mp_list(parser)
    return title, cities

def get_mp_page(url:str):
    mp_page_response = get_mp_page_response(url)
    parser = create_parser(mp_page_response)
    mp_dict = parse_mp_page(parser)
    return mp_dict

def get_city_list(cities):
    return list(map(lambda x: x['city'], cities))

def get_party_list(cities):
    return list(set([item for lst in list(map(lambda x: list(set(map(lambda y: y['party'], x['city_mps']))), cities)) for item in lst]))

if __name__ == '__main__':
    # Return title and cities
    title, cities = get_mp_list()
    print(cities)

    # Get mp page
    url = "https://www.tbmm.gov.tr/develop/owa/milletvekillerimiz_sd.bilgi?p_donem=27&p_sicil=7542"
    print(get_mp_page(url))

    # Get city list
    print(get_city_list(cities))

    # Get party list
    print(get_party_list(cities))


import os
import sys
import datetime

import cfg
import db

def set_standard_config():
    global board_count, board_components_count_max
    global mech_number_of_components
    global mech_num_assemblies, mech_assy_components_count_max
    global mech_num_level
    global top_code_count, top_code_components_count_max
    global spare_part_count, spare_part_components_count_max
    global prototype_count, changes_count

    # number of boards
    board_count = 40
    # the board components count is
    # board_components_count_max / 10 + rnd % board_components_count_max
    board_components_count_max = 200

    # number of mechanical components
    mech_number_of_components = 1000

    # number of subassemblies
    mech_num_assemblies = 200
    # the assembly components count is
    # mech_assy_components_count_max / 10 + rnd % mech_assy_components_count_max
    mech_assy_components_count_max = 20
    # maximum depth of bom
    mech_num_level = 7

    # number of top code
    top_code_count = 120
    # the top code components count is
    # top_code_components_count_max / 10 + rnd % top_code_components_count_max
    top_code_components_count_max = 12

    # number of spare parts
    spare_part_count = 120
    # the spare part components count is
    # spare_part_components_count_max / 10 + rnd % spare_part_components_count_max
    spare_part_components_count_max = 12

    # number of prototypes
    prototype_count = 200

    # number of revisions
    changes_count = 200

def set_advance_config():
    global board_count, board_components_count_max
    global mech_number_of_components
    global mech_num_assemblies, mech_assy_components_count_max
    global mech_num_level
    global top_code_count, top_code_components_count_max
    global spare_part_count, spare_part_components_count_max
    global prototype_count, changes_count

    # number of boards
    board_count = 600
    # the board components count is
    # board_components_count_max / 10 + rnd % board_components_count_max
    board_components_count_max = 200

    # number of mechanical components
    mech_number_of_components = 9000

    # number of subassemblies
    mech_num_assemblies = 9000
    # the assembly components count is
    # mech_assy_components_count_max / 10 + rnd % mech_assy_components_count_max
    mech_assy_components_count_max = 20 # 30 ->3500
    # maximum depth of bom
    mech_num_level = 10

    # number of top code
    top_code_count = 800
    # the top code components count is
    # top_code_components_count_max / 10 + rnd % top_code_components_count_max
    top_code_components_count_max = 12

    # number of spare parts
    spare_part_count = 800
    # the spare part components count is
    # spare_part_components_count_max / 10 + rnd % spare_part_components_count_max
    spare_part_components_count_max = 12

    # number of prototypes
    prototype_count = 200

    # number of revisions
    changes_count = 200

    # number of revisions
    changes_count = 3000

cfg.init()

date0 = "2000-01-01"

class MyRandom:
    def __init__(self, seed = 3):
        self._seed = seed
    def get(self):
        # glibc (used by GCC) -- from wikipedia https://en.wikipedia.org/wiki/Linear_congruential_generator
        n = (self._seed *1103515245 +  12345) % (2<<31)
        self._seed = n
        return n

rnd = MyRandom()

dbname="database.sqlite"

def insert_code(c, descr, code, ver='0', iter_=0, default_unit='NR',
                gval1='', gval2='', date=None, date_to=None, drawings=[],
                gval3='', gval4=''):

    if date is None:
        date = date0
    if date_to is None:
        date_to = db.days_to_iso(db.end_of_the_world)

    c.execute("INSERT INTO items(code) VALUES (?)", (
        code,)
    )
    c.execute("SELECT MAX(id) FROM items")
    code_id = c.fetchone()[0]

    c.execute("""INSERT INTO item_revisions(
        descr, code_id, ver,
        iter, default_unit,
        gval1, gval2, gval3, gval4,
        date_from, date_from_days,
        date_to, date_to_days) VALUES (
        ?, ?, ?,
        ?, ?,
        ?, ?, ?, ?,
        ?, ?,
        ?, ?)""",
        (descr, code_id, ver,
         iter_, default_unit,
         gval1, gval2, gval3, gval4,
         date, db.iso_to_days(date),
         date_to, db.iso_to_days(date_to))
    )

    c.execute("SELECT MAX(id) FROM item_revisions")
    rev_id = c.fetchone()[0]

    if len(drawings) > 0:
        for fn in drawings:
            c.execute("""INSERT INTO drawings(
                            code, revision_id, filename, fullpath
                         ) VALUES ( ?, ?, ?, ? )
                      """, (code, rev_id, os.path.basename(fn), os.path.abspath(fn))
            )

    return rev_id, code_id

# create screws
def insert_screws(c):
    cnt=1
    for l in [3, 4, 5, 6, 8, 10, 12, 14, 16, 20, 15, 30, 35]:
        for d in [2, 3, 4, 5, 6, 8, 10, 12]:

            insert_code(c, "SCREW M%dX%d"%(d, l), "710%03d"%(cnt), 0,
                    0, "NR", "M%dX%d"%(d, l), "SCREWS SUPPLIER")
            cnt += 1

# create washers
def insert_washer(c):
    cnt=1
    for d in [2, 3, 4, 5, 6, 8, 10, 12]:
        insert_code(c, "WASHER D%d"%(d), "720%03d"%(cnt), 0,
                0, "NR", "W D%d"%(d, ), "WASHERS SUPPLIER")
        cnt += 1

# create elastic washers
def insert_elastic_washer(c):
    cnt=1
    for d in [2, 3, 4, 5, 6, 8, 10, 12]:
        insert_code(c, "ELASTIC WASHER D%d"%(d, ), "730%03d"%(cnt), 0,
                0, "NR", "EW D%d"%(d, ), "WASHERS SUPPLIER")
        cnt += 1

# create resistors
def insert_resistor(c):
    # from https://www.eeweb.com/tools/resistor-tables/
    # 0.1%, 0.25%, and 0.5% Resistor Table (E192)
    table="""
100 	101 	102 	104 	105 	106 	107 	109 	110 	111 	113 	114
115 	117 	118 	120 	121 	123 	124 	126 	127 	129 	130 	132
133 	135 	137 	138 	140 	142 	143 	145 	147 	149 	150 	152
154 	156 	158 	160 	162 	164 	165 	167 	169 	172 	174 	176
178 	180 	182 	184 	187 	189 	191 	193 	196 	198 	200 	203
205 	208 	210 	213 	215 	218 	221 	223 	226 	229 	232 	234
237 	240 	243 	246 	249 	252 	255 	258 	261 	264 	267 	271
274 	277 	280 	284 	287 	291 	294 	298 	301 	305 	309 	312
316 	320 	324 	328 	332 	336 	340 	344 	348 	352 	357 	361
365 	370 	374 	379 	383 	388 	392 	397 	402 	407 	412 	417
422 	427 	432 	437 	442 	448 	453 	459 	464 	470 	475 	481
487 	493 	499 	505 	511 	517 	523 	530 	536 	542 	549 	556
562 	569 	576 	583 	590 	597 	604 	612 	619 	626 	634 	642
649 	657 	665 	673 	681 	690 	698 	706 	715 	723 	732 	741
750 	759 	768 	777 	787 	796 	806 	816 	825 	835 	845 	856
866 	876 	887 	898 	909 	920 	931 	942 	953 	965 	976 	988
"""
    rows = table.split("\n")
    values = []
    for row in rows:
        row = row.strip()
        if len(row) == 0:
            continue
        values.extend(map(float, row.split("\t")))

    cnt = 1
    for r in values:
        for (k, suffix) in [(100, ""), (10, ""), (1, ""),
                    (100, "k"), (10, "k"), (1, "k"), (100, "M")]:
            insert_code(c,
                    "RESISTOR %.2f%s"%(r / k, suffix), "51%04d"%(cnt), 0,
                    0, "NR", "R%.2f%s"%(r / k, suffix), "RESISTORS SUPPLIER")
            cnt += 1

# create capacitors
def insert_capacitor(c):
# from https://www.rfcafe.com/references/electrical/capacitor-values.htm
#pF 	pF 	pF 	    pF 	    µF 	    µF 	    µF 	    µF 	    µF 	µF  	µF
    table="""
1.0 	10 	100 	1000 	0.01 	0.1 	1.0 	10 	100 	1000 	10,000
1.1 	11 	110 	1100
1.2 	12 	120 	1200
1.3 	13 	130 	1300
1.5 	15 	150 	1500 	0.015 	0.15 	1.5 	15 	150 	1500
1.6 	16 	160 	1600
1.8 	18 	180 	1800
2.0 	20 	200 	2000
2.2 	22 	220 	2200 	0.022 	0.22 	2.2 	22 	220 	2200
2.4 	24 	240 	2400
2.7 	27 	270 	2700
3.0 	30 	300 	3000
3.3 	33 	330 	3300 	0.033 	0.33 	3.3 	33 	330 	3300
3.6 	36 	360 	3600
3.9 	39 	390 	3900
4.3 	43 	430 	4300
4.7 	47 	470 	4700 	0.047 	0.47 	4.7 	47 	470 	4700
5.1 	51 	510 	5100
5.6 	56 	560 	5600
6.2 	62 	620 	6200
6.8 	68 	680 	6800 	0.068 	0.68 	6.8 	68 	680 	6800
7.5 	75 	750 	7500
8.2 	82 	820 	8200
9.1 	91 	910 	9100"""

    cnt = 1
    for row in table.split("\n"):
        row = row.strip()
        if len(row) == 0:
            continue
        col = 0
        for v in row.split("\t"):
            if col > 3:
                suffix = "uF"
            else:
                suffix = "pf"
            v = v.strip()

            insert_code(c, "CAPACITOR %s%s"%(v, suffix), "520%03d"%(cnt), 0,
                    0, "NR", "C%s%s"%(v, suffix), "CAPACITORS SUPPLIER"
            )
            cnt += 1
            col += 1

# create microprocessors
def insert_microprocessor(c):
# from https://en.wikipedia.org/wiki/Microprocessor_chronology
    table = """
1971 	4004 	Intel 	740 kHz 	4 	10 μm 	1 	2,250 	pMOS 	[1]
1972 	PPS-25 	Fairchild 	400 kHz 	4 	  	2 		pMOS 	[2][a]
1972 	μPD700 	NEC 	  	4 	  	1 			[3]
1972 	8008 	Intel 	500 kHz 	8 	10 μm 	1 	3,500 	pMOS
1972 	PPS-4 	Rockwell 	200 kHz 	4 	  	1 		pMOS 	[4][5]
1973 	μCOM-4 	NEC 	2 MHz 	4 	7.5 μm 	1 	2,500 	NMOS 	[6][7][3][1]
1973 	TLCS-12 	Toshiba 	1 MHz 	12 	6 μm 	1 	2,800 silicon gates 	pMOS 	[8][9][1]
1973 	Mini-D 	Burroughs 	1 MHz 	8 	  	1 		pMOS 	[10]
1974 	IMP-8 	National 	715 kHz 	8 	  	3 		pMOS 	[8]
1974 	8080 	Intel 	2 MHz 	8 	6 μm 	1 	6,000 	NMOS
1974 	μCOM-8 	NEC 	2 MHz 	8 	  	1 		NMOS 	[3][1]
1974 	5065 	Mostek 	1.4 MHz 	8 	  	1 		pMOS 	[11]
1974 	μCOM-16 	NEC 	2 MHz 	16 	  	2 		NMOS 	[3][1]
1974 	IMP-4 	National 	500 kHz 	4 	  	3 		pMOS 	[8]
1974 	4040 	Intel 	740 kHz 	4 	10 μm 	1 	3,000 	pMOS
1974 	6800 	Motorola 	1 MHz 	8 	- 	1 	4,100 	NMOS 	[8]
1974 	TMS 1000 	Texas Instruments 	400 kHz 	4 	8 μm 	1 	8,000
1974 	PACE 	National 	  	16 	  	1 		pMOS 	[12][13]
1974 	ISP-8A/500 (SC/MP) 	National 	1 MHz 	8 	  	1 		pMOS
1975 	6100 	Intersil 	4 MHz 	12 	- 	1 	4,000 	CMOS 	[14][15]
1975 	TLCS-12A 	Toshiba 	1.2 MHz 	12 	- 	1 		pMOS 	[1]
1975 	2650 	Signetics 	1.2 MHz 	8 	  	1 		NMOS 	[8]
1975 	PPS-8 	Rockwell 	256 kHz 	8 	  	1 		pMOS 	[8]
1975 	F-8 	Fairchild 	2 MHz 	8 	  	1 		NMOS 	[8]
1975 	CDP 1801 	RCA 	2 MHz 	8 	5 μm 	2 	5,000 	CMOS 	[16][17]
1975 	6502 	MOS Technology 	1 MHz 	8 	- 	1 	3,510 	NMOS (dynamic)
1975 	IMP-16 	National 	715 kHz 	16 	  	5 		pMOS 	[18][1][19]
1975 	PFL-16A (MN 1610) 	Panafacom 	2 MHz 	16 	- 	1 		NMOS 	[1]
1975 	BPC 	Hewlett Packard 	10 MHz 	16 	- 	1 	6,000 (+ ROM) 	NMOS 	[20][21]
1975 	MCP-1600 	Western Digital 	3.3 MHz 	16 	- 	3 		NMOS
1975 	CP1600 	General Instrument 	3.3 MHz 	16 	  	1 		NMOS 	[12][22][23][1]
1976 	CDP 1802 	RCA 	6.4 MHz 	8 	  	1 		CMOS 	[24][25]
1976 	Z-80 	Zilog 	2.5 MHz 	8 	4 μm 	1 	8,500 	NMOS
1976 	TMS9900 	Texas Instruments 	3.3 MHz 	16 	- 	1 	8,000
1976 	8x300 	Signetics 	8 MHz 	8 	  	1 		Bipolar 	[26][27]
1977 	Bellmac-8 (WE212) 	Bell Labs 	2.0 MHz 	8 	5 μm 	1 	7,000 	CMOS
1977 	8085 	Intel 	3.0 MHz 	8 	3 μm 	1 	6,500
1977 	MC14500B 	Motorola 	1.0 MHz 	1 		1 		CMOS
1978 	6809 	Motorola 	1 MHz 	8 	5 μm 	1 	9,000
1978 	8086 	Intel 	5 MHz 	16 	3 μm 	1 	29,000
1978 	6801 	Motorola 	- 	8 	5 μm 	1 	35,000
1979 	Z8000 	Zilog 	- 	16 	- 	1 	17,500
1979 	8088 	Intel 	5 MHz 	8/16[b] 	3 μm 	1 	29,000 	NMOS (HMOS)
1979 	68000 	Motorola 	8 MHz
"""

    cnt = 1
    for row in table.split("\n"):
        row = row.strip()
        if len(row) == 0:
            continue

        (ucode, manufacturer, clock) = row.split("\t")[1:4]

        insert_code(c,
                "MICROPROCESSOR %s %s %s"%(ucode, manufacturer, clock),
                "530%03d"%(cnt), 0,
                0, "NR", "%s AT %s"%(ucode, clock), manufacturer
        )
        cnt += 1

def get_max_by_code(c, pattern):
    c.execute("""SELECT MAX(id)
                 FROM items
                 WHERE code LIKE ?""",
                 (pattern,))
    return c.fetchone()[0]

def get_min_by_code(c, pattern):
    c.execute("""SELECT MIN(id)
                 FROM items
                 WHERE code LIKE ?""",
                 (pattern,))
    return c.fetchone()[0]

def make_assembly(c, ass_rid, children):
    try:
        a = children[0][0]
    except:
        children =[[x, ] for x in children]

    for row in children:
        if len(row) == 1:
            row = list(row) + [1, '', '', '']
        else:
            row = list(row) + ['', '', '']
            row = row[:5]
        child_id, qty, gaval1, gaval2, gaval3 = row
        c.execute("""INSERT INTO assemblies (
                        unit, child_id, revision_id,
                        qty, each, ref,
                        gaval1, gaval2, gaval3
                     ) VALUES (
                        ?, ?, ?,
                        ?, ?, ?,
                        ?, ?, ?
                     )""", (
                        "NR", child_id, ass_rid,
                        qty, 1, '//',
                        gaval1, gaval2, gaval3
                     )
        )

# create boards with drawings
def insert_board(c):

    min_id = get_min_by_code(c, '5%')
    max_id = get_max_by_code(c, '5%')

    # TODO build revision

    # make 15 boards
    for cnt in range(1, board_count):

        ncomponents = rnd.get() % board_components_count_max + \
            board_components_count_max / 10

        code = "61%04d"%(cnt)

        fn1 = "documents/boards/%s_(el)_rev0.txt"%(code)
        open(fn1, "w").write("Type: board drawing\nCode: %s\nNr components: %d\n"%(
            code, ncomponents))

        board_id, _ = insert_code(c,
                "BOARD %d"%(cnt), code, 0,
                0, "NR", "BOARD %d"%(cnt), "BOARDS MANUFACTURER %d"%(cnt %3, ),
                drawings = [fn1]
        )

        # create the board assy
        components_table = []

        #resistor
        did = set()
        for i in range(rnd.get() % 120 + 5):
            while True:
                cid = rnd.get() % (max_id - min_id) + min_id
                if not cid in did:
                    break
            did.add(cid)

            make_assembly(c, board_id, [(cid, (i * cnt % 10) + 1),])

# create mechanical componets with drawings
def insert_mechanical_components(c):
    cnt=1
    for d in range(mech_number_of_components):
        code = "81%04d"%(cnt)

        fn1 = "documents/drawings/%s_(drw)_rev0.txt"%(code)
        open(fn1, "w").write("Type: mechanical drawing\nCode:%s\n"%(
            code, ))

        insert_code(c,
                "MECHANICAL COMPONENT NR.%d"%(d, ), code, 0,
                0, "NR", "", "MECHANICAL SUPPLIER %d"%(d % 4,),
                drawings = [fn1]
        )

        cnt += 1

# create assemblies with drawings and assembling procedure
def insert_mechanical_assemblies(c):

    boards_min_id = get_min_by_code(c, '7%')
    mech_comp_max_id = get_max_by_code(c, '81%')

    # TODO build revision

    # make 120 assemblies
    for cnt in range(mech_num_assemblies):

        if (cnt % int(mech_num_assemblies / mech_num_level)) == 0:
            assy_max_id = get_max_by_code(c, '8%')

        # each components between [boards_min_id, old_mech_max_id] are a
        # valid child

        ncomponents = rnd.get() % mech_assy_components_count_max + \
            mech_assy_components_count_max // 10

        code = "82%04d"%(cnt)

        fn1 = "documents/drawings/%s_(drw)_rev0.txt"%(code)
        open(fn1, "w").write("Type: mechanical drawing\nCode: %s\nNr components: %d\n"%(
            code, ncomponents))
        fn2 = "documents/assembling-procedures/%s_(asm)_rev0.txt"%(code)
        open(fn2, "w").write("Type: assembling procedure\nCode: %s\nNr components: %d\n"%(
            code, ncomponents))

        mech_id, _ = insert_code(c,
                "MECHANICAL ASSEMBLIES %d - LEVEL %d"%(cnt,
                    cnt/(mech_num_assemblies / mech_num_level) + 1), code, 0,
                0, "NR", "", "INTERNAL SUPPLIER",
                drawings = [fn1, fn2]
        )

        did =set()
        for i in range(ncomponents):
            if (i % 2) or (assy_max_id - mech_comp_max_id) < 10:
                while True:
                    cid = (rnd.get() %
                        (mech_comp_max_id - boards_min_id) + boards_min_id)
                    if not cid in did:
                        break
            else:
                # in the begin we have few assemblies
                n = 10
                while n > 0:
                    cid = (rnd.get() %
                        (assy_max_id - mech_comp_max_id) + mech_comp_max_id)
                    if not cid in did:
                        break
                    n -= 1
                if cid in did:
                    continue

            did.add(cid)

            make_assembly(c, mech_id, ((cid, (i * cnt % 10) + 1),))

# create top assemblies with packaging procedure
def insert_top_codes(c):

    mech_min = get_min_by_code(c,'82%')
    mech_max = get_max_by_code(c,'82%')

    # TODO build revision

    for cnt in range(top_code_count):

        ncomponents = rnd.get() % top_code_components_count_max + \
            top_code_components_count_max // 10

        code = "100%03d"%(cnt)

        fn1 = "documents/packaging-procedure/%s_rev0.txt"%(code)
        open(fn1, "w").write("Type: packaging procedure\nCode: %s\nNr components: %d\n"%(
            code, ncomponents))

        top_id, _ = insert_code(c,
                "TOP ASSEMBLY %d"%(cnt, ), code, 0,
                0, "NR", "", "INTERNAL SUPPLIER",
                drawings = [fn1]
        )

        #up to 127 sub assemblies
        did = set()
        for i in range(ncomponents):
            while True:
                cid = (rnd.get() %
                    (mech_max - mech_min + 1) + mech_min)
                if not cid in did:
                    break
            did.add(cid)

            make_assembly(c, top_id, ((cid, (i * cnt % 10) + 1),))

# create spare part with packaging procedure
def insert_spare_parts(c):

    mech_min = get_min_by_code(c,'6%')
    mech_max = get_max_by_code(c,'81%')
    # TODO build revision

    # make 120 top code
    for cnt in range(spare_part_count):

        ncomponents = rnd.get() % spare_part_components_count_max + \
            spare_part_components_count_max // 10

        code = "101%03d"%(cnt)

        fn1 = "documents/packaging-procedure/%s_packaging.txt"%(code)
        open(fn1, "w").write("Type: packaging procedure\nCode: %s\nNr components: %d\n"%(
            code, ncomponents))

        top_id, _ = insert_code(c,
            "SPARE PART %d"%(cnt, ), code, 0,
            0, "NR", "", "INTERNAL SUPPLIER",
            drawings = [fn1]
        )

        ts = datetime.date.fromisoformat(date0).toordinal()
        did = set()
        for i in range(ncomponents):
            while True:
                cid = (rnd.get() %
                    (mech_max - mech_min + 1) + mech_min)
                if not cid in did:
                    break
            did.add(cid)

            make_assembly(c, top_id, ((cid, (i * cnt % 10) + 1),))

def revise_code(c, old_rid, new_date):

        c.execute("""
            SELECT code_id, MAX(iter)
            FROM item_revisions
            WHERE code_id = (
                SELECT code_id
                FROM item_revisions
                WHERE id = ?
            )
            GROUP BY code_id
            """, (old_rid, ))
        (code_id, old_iter) = c.fetchone()

        c.execute("""
            SELECT id, date_from_days, ver
            FROM item_revisions
            WHERE code_id = ?
            AND iter = ?
            """, (code_id, old_iter))

        (latest_rid, old_date_from_days, rev) = c.fetchone()

        #print("Code, ver, iter=", code, ver, iter_)
        if rev == '0':
            rev = 'A'
        else:
            rev = chr(ord(rev)+1)

        new_date_from_days = new_date
        new_date_from = db.days_to_iso(new_date_from_days)

        if new_date_from_days == db.prototype_date:
            new_iter = db.prototype_iter
        else:
            new_iter = old_iter + 1

        c.execute("""
                INSERT INTO item_revisions(
                    code_id,
                    date_from,
                    date_from_days,
                    date_to,
                    ver,
                    iter,
                    note,
                    descr,
                    default_unit,
                    gval1, gval2, gval3, gval4, gval5, gval6, gval7, gval8
                ) SELECT
                    code_id,
                    ?,
                    ?,
                    '',
                    ?,
                    ?,
                    note,
                    descr,
                    default_unit,
                    gval1, gval2, gval3, gval4, gval5, gval6, gval7, gval8
                FROM item_revisions
                WHERE id = ?
            """, (new_date_from, new_date_from_days, rev, new_iter, old_rid))

        old_date_to_days = new_date_from_days-1
        old_date_to = db.days_to_iso(old_date_to_days)
        c.execute("""
            UPDATE item_revisions
            SET date_to=?, date_to_days=?
            WHERE id= ?
        """, (old_date_to, old_date_to_days, latest_rid))

        c.execute("""SELECT MAX(id) FROM item_revisions""")
        new_rid = c.fetchone()[0]

        return (new_rid, rev)

def revise_assembly(c, old_rid, new_date):

    (new_rid, rev) = revise_code(c, old_rid, new_date)

    c.execute("""
        INSERT INTO assemblies(
                    unit, child_id,
                    revision_id,
                    qty, each, ref
        ) SELECT unit, child_id,
                    ?,
                    qty, each, ref
        FROM assemblies
        WHERE revision_id=?
    """, (new_rid, old_rid))

    # TODO: make some changes to the assembly
    return (new_rid, rev)

def change_code(c, code_id, new_date):
    # fetch the latest code revision
    c.execute("""SELECT MAX(iter)
                 FROM item_revisions
                 WHERE code_id=?""", (code_id,))
    (iter_,) = c.fetchone()
    c.execute("""SELECT id
                 FROM item_revisions
                 WHERE code_id=?
                   AND iter=?""", (code_id, iter_))
    (rev_id,) = c.fetchone()
    c.execute("""SELECT code
                 FROM items
                 WHERE id=?""", (code_id,))
    (code,) = c.fetchone()

    if code.startswith("81"):
        # no assembly
        new_id, new_rev = revise_code(c, rev_id, new_date)
        fn1 = "documents/drawings/%s_(drw)_rev%s.txt"%(code, new_rev)
        open(fn1, "w").write("Type: mechanical drawing\nCode:%s\n"%(
            code, ))
        c.execute("""INSERT INTO drawings(
                code, revision_id, filename, fullpath
            ) VALUES ( ?, ?, ?, ? )
                """, (code, new_id, os.path.basename(fn1), os.path.abspath(fn1))
        )

    elif code.startswith("82"):
            # assembly
            new_id, new_rev = revise_assembly(c, rev_id, new_date)
            fn1 = "documents/drawings/%s_(drw)_rev%s.txt"%(code, new_rev)
            open(fn1, "w").write("Type: mechanical drawing\nCode:%s\n"%(
                code, ))
            c.execute("""INSERT INTO drawings(
                    code, revision_id, filename, fullpath
                ) VALUES ( ?, ?, ?, ? )
                    """, (code, new_id, os.path.basename(fn1), os.path.abspath(fn1))
            )
            fn2 = "documents/assembling-procedures/%s_(asm)_rev%s.txt"%(code, new_rev)
            open(fn2, "w").write("Type: assembling procedure\nCode:%s\n"%(
                code, ))
            c.execute("""INSERT INTO drawings(
                    code, revision_id, filename, fullpath
                ) VALUES ( ?, ?, ?, ? )
                    """, (code, new_id, os.path.basename(fn2), os.path.abspath(fn2))
            )

    elif code.startswith("6"):
            new_id, new_rev = revise_assembly(c, rev_id, new_date)
            fn1 = "documents/boards/%s_(el)_rev%s.txt"%(code, new_rev)
            open(fn1, "w").write("Type: board drawing\nCode: %s\n"%(
                code,))
            c.execute("""INSERT INTO drawings(
                    code, revision_id, filename, fullpath
                ) VALUES ( ?, ?, ?, ? )
                    """, (code, new_id, os.path.basename(fn1), os.path.abspath(fn1))
            )

    elif code[0] in "57":
        new_id, new_rev = revise_code(c, rev_id, new_date)
    elif code.startswith("1"):
        print("WARNING: code ", code, " is ignore")
        return
        # topcode
        # update the packaging procedure
        # update the assembly
    else:
        # ??
        print("Code=", code)
        assert(False)

# make some changes
def make_changes(c):
    global date0

    min_id = get_min_by_code(c, '6%')
    max_id = get_max_by_code(c, '82%')

    print("Make few changes")

    new_date = db.iso_to_days(date0)
    # to be coherent with test plan
    if changes_count  == 200:
        dtinc = 10
        new_date = db.iso_to_days(date0)
    else:
        dtinc = 22.0 * 365 / changes_count
        new_date = db.iso_to_days(date0) * 1.0
    for cnt in range(changes_count):

        new_date += dtinc

        code_id = rnd.get() % (max_id - min_id + 1) + min_id

        change_code(c, code_id, int(new_date))

def make_prototype(c):

    min_id = get_min_by_code(c, '5%')
    max_id = get_max_by_code(c, '82%')

    code_ids = set()
    for cnt in range(prototype_count):
        code_id = rnd.get() % (max_id - min_id + 1) + min_id
        code_ids.add(code_id)

    print("Make few prototypes")

    for code_id in code_ids:
        change_code(c, code_id, db.prototype_date)

def xrmdir(path):
        if os.path.isdir(path):
            for fn in os.listdir(path):
                ffn = os.path.join(path, fn)
                xrmdir(ffn)
            os.rmdir(path)
        elif os.path.exists(path):
            os.unlink(path)

# insert non ascii string
def insert_unicode_code(c):
    s="UTF8 TEST HELLO WORLD - 你好世界"
    insert_code(c, s, "TEST-HELLOWORLD", 0,
                0, "NR", "", "")
    insert_code(c, "all lowercase", "TEST-alllowercase", 0,
                0, "NR", "gval1 lower", "gval2 lower")
    insert_code(c, "ALL UPPER CASE", "TEST-ALLUPPERCASE", 0,
                0, "NR", "GVAL1 UPPER", "GVAL2 UPPER")
    insert_code(c, "DEGREE SYMBOL '°'", "TEST-DEGREE", 0,
                0, "NR", "", "")
    insert_code(c, "A-ACUTE 'à'", "TEST-AACUTE", 0,
                0, "NR", "", "")

    code = "TEST-LONG_FN"
    fn = "documents/drawings/%s_(drw)_%s_rev%s.txt"%(code, 'xX' * 20, 0)
    open(fn, "w").write("Long filename\n")
    _, id_long_fn = insert_code(c, "LONG FILENAME CODE", code, 0,
                0, "NR", "", "", drawings=[fn])

    code = "TEST-NORMAL-CODE"
    fn = "documents/drawings/%s_(drw)_rev%s.txt"%(code, 0)
    open(fn, "w").write("Normal filename\n")
    _, id_normal = insert_code(c, "NORMAL CODE", code, 0,
                0, "NR", "", "", drawings=[fn])

    code = "TEST-MISSING-FN"
    fn = "documents/drawings/%s_(drw)_rev%s.txt"%(code, 0)
    _, id_missing_file = insert_code(c, "MISSING FILE", code, 0,
                0, "NR", "", "", drawings=[fn])

    code = "TEST-FILE-TOO-BIG"
    fn = "documents/drawings/%s_(drw)_rev%s.txt"%(code, 0)
    with open(fn, "w") as f:
        f.seek(1024*1024*1024*10);
        f.write(" ");
    _, id_file_too_long = insert_code(c, "FILE TOO BIG", code, 0,
                0, "NR", "", "", drawings=[fn])

    code = "TEST-ASSY-TO-EXPORT"
    arid, _ = insert_code(c, "TEST ASSY TO EXPORT", code, 0,
                0, "NR", "", "")
    make_assembly(c, arid,
        (id_long_fn, id_missing_file, id_file_too_long, id_normal))

def insert_codes_with_date(c):

    aaid, _ = insert_code(c, "TEST-ASS-A", "TEST-ASS-A", 0,
                0, "NR", date='2020-01-01', date_to='2021-01-01')
    _, caid = insert_code(c, "TEST-A", "TEST-A", 0,
                0, "NR", date='2020-01-01')
    _, cbid = insert_code(c, "TEST-B", "TEST-B", 0,
                0, "NR", date='2020-01-01', date_to='2021-01-01')

    make_assembly(c, aaid, [caid, cbid])

def insert_codes_with_loop(c):

    car, cai = insert_code(c, "TEST-LOOP-A", "TEST-LOOP-A")
    cbr, cbi = insert_code(c, "TEST-LOOP-B", "TEST-LOOP-B")
    ccr, cci = insert_code(c, "TEST-LOOP-C", "TEST-LOOP-C")

    make_assembly(c, car,((cbi, 1),))
    make_assembly(c, cbr,((cci, 1),))
    make_assembly(c, ccr,((cai, 1),))

def insert_codes_with_color(c):
    car, _ = insert_code(c, "TEST COLOR A", "TEST-COLOR-A",
                   gval1='COLOR', gval2='A')
    _, cbi = insert_code(c, "TEST COLOR B", "TEST-COLOR-B",
                   gval1='COLOR', gval2='B')
    _, cci = insert_code(c, "TEST COLOR C", "TEST-COLOR-C",
                   gval1='COLOR', gval2='C')
    _, cdi = insert_code(c, "TEST COLOR D", "TEST-COLOR-D",
                   gval1='COLOR', gval2='D')
    _, cei = insert_code(c, "TEST COLOR E", "TEST-COLOR-E",
                   gval1='COLOR', gval2='E')
    cfr, cfi = insert_code(c, "TEST COLOR F", "TEST-COLOR-F",
                   gval1='COLOR', gval2='F')
    _, cgi = insert_code(c, "TEST COLOR G", "TEST-COLOR-G",
                   gval1='COLOR', gval2='G')
    make_assembly(c, car,(
        (cbi, 1, "COLOR", "M"),
        (cci, 1, "COLOR", "N"),
        (cdi, 1, "COLOR", "O"),
        (cei, 10, "COLOR", "P"),
        (cfi, 10, "COLOR", "Q"),
    ))
    make_assembly(c, cfr,(
        (cgi, 1, "COLOR", "R"),
    ))

def build_bom(top, data):
    res = []

    def runner(t):
        res.append(data[t]["code"])
        for k, v in data[t]["deps"].items():
            runner(v["code_id"])

    runner(top)
    return res

def make_big_assemblies(c):

    assemblies_params = [
        # leaf size, bom height
        (7, 3),  # tot     57
        (5, 4),  # tot    156
        (6, 4),  # tot    259
        (7, 4),  # tot    400
        (5, 5),  # tot    781
        (6, 5),  # tot   1555
        (7, 5),  # tot   2801
        (6, 6),  # tot   9331
        (7, 6),  # tot  19608
        (6, 7),  # tot  55987
        (7, 7),  # tot 137257
        (6, 8),  # tot 325923
        (7, 8),  # tot 960800
    ]

    codes_map = dict()
    def build_assy(leaf_size, bom_deep, cnt):
        new_code = "BIG-ASSY-%dx%d-%03d"%(leaf_size, bom_deep, cnt)
        #print("\t", new_code, codes_map)
        if new_code in codes_map:
            arid, acid, count = codes_map[new_code]
            return arid, acid, count

        count = 1

        if bom_deep == 1:
            arid, acid = insert_code(c,
                "BIG ASSY COUNT=%7d, %d X %d"%(count, leaf_size, bom_deep),
                new_code, 0, 0, "NR")
            codes_map[new_code] = arid, acid, count
            return arid, acid, count

        codes_id = []
        cnt2 = 0
        for i in range(leaf_size):
            _, code_id, cnt3 = build_assy(leaf_size, bom_deep - 1, cnt2)
            cnt2 += 1
            count += cnt3
            codes_id.append(code_id)

        arid, acid = insert_code(c,
            "BIG ASSY COUNT=%7d, %d X %d"%(count, leaf_size, bom_deep),
            new_code, 0, 0, "NR")

        make_assembly(c, arid, codes_id)
        codes_map[new_code] = arid, acid, count

        return arid, acid, count

    for leaf_size, bom_deep in  assemblies_params:
        build_assy(leaf_size, bom_deep, 0)

def create_db(show_stat):
    dbtype = cfg.config()["BOMBROWSER"]["db"]
    c = cfg.config()[dbtype.upper()]
    db.init(dbtype, dict(c))
    d = db.DB()
    d.create_db()

    xrmdir("documents")
    os.mkdir("documents")
    os.mkdir("documents/drawings")
    os.mkdir("documents/boards")
    os.mkdir("documents/assembling-procedures")
    os.mkdir("documents/packaging-procedure")

    with db.Transaction(d) as c:
        # 510xxx
        print("Insert resistor 510xxx")
        insert_resistor(c)
        # 520xxx
        print("Insert capacitor 520xxx")
        insert_capacitor(c)
        # 530xxx
        print("Insert uprocessor 530xxx")
        insert_microprocessor(c)

        # 710xxx
        print("Insert screws 710xxx")
        insert_screws(c)
        # 720xxx
        print("Insert washer 720xxx")
        insert_washer(c)
        # 730xxx
        print("Insert elastic washer 730xxx")
        insert_elastic_washer(c)

        # 610xxx
        print("Insert boards 610xxx")
        insert_board(c)

        # 810###
        print("Insert mechanical componets 810xxx")
        insert_mechanical_components(c)

        # 820 #
        print("Insert mechanical assemblies 820xxx")
        insert_mechanical_assemblies(c)

        # 100xxx
        print("Insert top codes 100xxx")
        insert_top_codes(c)

        # 101xxx
        print("Insert spare parts 101xxx")
        insert_spare_parts(c)

        make_changes(c)
        make_prototype(c)

        if False:
            c.executemany("""
                INSERT INTO database_props(name, value)
                VALUES (?, ?)
            """,(
                    ("cfg.template_simple.name", "Simple table"),
                    ("cfg.template_simple.columns", "seq,level,code,indented_code,descr,qty,each,unit,ref,rev,iter,date_from,date_to"),
                    ("cfg.template_simple.captions", "Seq,Level,Code,Code,Description,Q.ty,Each,Unit,Reference,Rev,Iter,From date,To date"),

                    ("cfg.template_all.name", "Full table"),
                    ("cfg.template_all.columns", "seq,level,parent,parent_descr,\"-\",code,indented_code,descr,qty,each,unit,ref,rev,iter,date_from,date_to,gval1,gval2,gval3,gval4,gval5,gval6"),
                    ("cfg.template_all.captions", "Seq,Level,Parent code,Parent descritpion,-,Code,Code,Description,Q.ty,Each,Unit,Reference,Rev,Iter,From date,To date,Supplier#1 PN,Supplier#1 name,Manufacturer#1 PN,Manufacturer#1 name,Manufacturer#2 PN,Manufacturer#2 name"),

                    ("cfg.template_dummy.name", "Dummy table"),
                    ("cfg.template_dummy.columns", "seq,parent,code,descr,qty,each,unit,ref,rev,iter,date_from,date_to"),
                    ("cfg.template_dummy.captions", "Seq,Parent code,Code,Description,Q.ty,Each,Unit,Reference,Rev,Iter,From date,To date")

            ))

        print("Insert unicode codes")
        insert_unicode_code(c)
        print("Insert code with date")
        insert_codes_with_date(c)
        print("Insert assembly with a loop")
        insert_codes_with_loop(c)
        print("Insert assembly with color")
        insert_codes_with_color(c)

        print("Build big assemblies")
        make_big_assemblies(c)

        if show_stat:
            print()
            c.execute("SELECT COUNT(*) FROM items")
            cnt = c.fetchone()[0]
            print("Total number of code=", cnt)

            c.execute("SELECT COUNT(*) FROM item_revisions")
            cnt = c.fetchone()[0]
            print("Total number of revisions=", cnt)

            # we expect that the biggest assembly are inside the last
            # top assemblies
            c.execute("SELECT id FROM items WHERE code LIKE '100%' ORDER BY id ASC")
            codes = [x[0] for x in c.fetchall()][-10:]

    if show_stat:
        cnt = 0
        max_id = -1
        for code_id in codes:
            (top, data) = d.get_bom_by_code_id3(code_id, db.end_of_the_world)
            c = len(build_bom(top, data))
            if c > cnt:
                max_id = code_id
                cnt = c
        print("Maximum bom size=", cnt, "; code_id=", max_id)

def help_():
    print("usage: mkdb.py [--stat] --test-db|--big-db|--help")

def main(args):
    args = sys.argv
    show_stat = False
    i = 1
    if len(args) == 1:
        print("ERROR: missing parameter")
        help_()
        return

    while i < len(args):
        if args[i] == "--test-db":
            set_standard_config()
        elif args[i] == "--big-db":
            set_advance_config()
        elif args[i] == "--stat":
            show_stat = True
        else:
            print("ERROR: unknown parameter")
            help_()
            return
        i += 1

    create_db(show_stat)

main(sys.argv)


from urllib.parse import urlencode

import ujson as json

from google.auth import exceptions
from google.oauth2._client import _handle_error_response, _parse_expiry

_URLENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded'
_JWT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
_REFRESH_GRANT_TYPE = 'refresh_token'

async def _token_endpoint_request(session, token_uri, body):
    """Makes a request to the OAuth 2.0 authorization server's token endpoint.

    Args:
        session (aiohttp.client.ClientSession): ClientSession used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        body (Mapping[str, str]): The parameters to send in the request body.

    Returns:
        Mapping[str, str]: The JSON-decoded response data.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.
    """
    body = urlencode(body)
    headers = {
        'content-type': _URLENCODED_CONTENT_TYPE,
    }

    response = await session.post(url=token_uri, headers=headers, data=body)

    response_text = await response.text(encoding='utf-8')

    if response.status != 200:
        _handle_error_response(response_text)

    response_data = json.loads(response_text)

    return response_data

async def jwt_grant(session, token_uri, assertion):
    """Implements the JWT Profile for OAuth 2.0 Authorization Grants.

    For more details, see `rfc7523 section 4`_.

    Args:
        session (aiohttp.client.ClientSession): ClientSession used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        assertion (str): The OAuth 2.0 assertion.

    Returns:
        Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
            expiration, and additional data returned by the token endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.

    .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
    """
    body = {
        'assertion': assertion,
        'grant_type': _JWT_GRANT_TYPE,
    }

    response_data = await _token_endpoint_request(session, token_uri, body)

    try:
        access_token = response_data['access_token']
    except KeyError:
        raise exceptions.RefreshError(
            'No access token in response.', response_data)

    expiry = _parse_expiry(response_data)

    return access_token, expiry, response_data

async def refresh_grant(session, token_uri, refresh_token, client_id,
                        client_secret):
    """Implements the OAuth 2.0 refresh token grant.

    For more details, see `rfc678 section 6`_.

    Args:
        session (aiohttp.client.ClientSession): ClientSession used to make
            HTTP requests.
        token_uri (str): The OAuth 2.0 authorizations server's token endpoint
            URI.
        refresh_token (str): The refresh token to use to get a new access
            token.
        client_id (str): The OAuth 2.0 application's client ID.
        client_secret (str): The Oauth 2.0 appliaction's client secret.

    Returns:
        Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The
            access token, new refresh token, expiration, and additional data
            returned by the token endpoint.

    Raises:
        google.auth.exceptions.RefreshError: If the token endpoint returned
            an error.

    .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
    """
    body = {
        'grant_type': _REFRESH_GRANT_TYPE,
        'client_id': client_id,
        'client_secret': client_secret,
        'refresh_token': refresh_token,
    }

    response_data = await _token_endpoint_request(session, token_uri, body)

    try:
        access_token = response_data['access_token']
    except KeyError:
        raise exceptions.RefreshError(
            'No access token in response.', response_data)

    refresh_token = response_data.get('refresh_token', refresh_token)
    expiry = _parse_expiry(response_data)

    return access_token, refresh_token, expiry, response_data


from datetime import datetime
from Device import Device

class OnOff(Device):
    nullValue = "null"

    def __init__(self, filename, nullColor, colors):
        super(OnOff, self).__init__(filename)

        self.nullColor = nullColor
        self.colors = {self.nullValue: nullColor, '0': colors[0], '1': colors[1]}

    def buildPlotData(self, x, k):
        xColorKeys = k[1]
        # xColorKeys.insert(0, self.nullValue)

        xAxisLabels = []
        xAxisTicks = []
        # adding initial value to x axis
        xAxisLabels.append(x[0])
        xAxisTicks.append(0)

        lastVal = 0
        legendMap = {}
        width = []
        color = []
        leftValues = []
        for i in range(len(x) - 1):
            val = (x[i + 1] - x[i]).seconds
            width.append(val)
            color.append(self.colors[xColorKeys[i]])
            leftValues.append(lastVal)
            # xAxisLabels.append(x[i + 1])
            xAxisLabels.append(x[i + 1])
            xAxisTicks.append(xAxisTicks[len(xAxisTicks) - 1] + val)
            # h = ax.barh(yPos, val, 0.2, color=self.colorsBars[xColorKeys[i]], edgecolor='grey', linewidth=0.5,
            #             left=lastVal)
            legendMap[xColorKeys[i]] = i
            lastVal += val

        height = 0.2
        plotData = [width, height, color, leftValues, legendMap]

        return plotData, xAxisLabels, xAxisTicks

    def _Device__plotInternal(self, ax, x, k):
        plotData, xAxisLabels, xAxisTicks = self.buildPlotData(x, k)

        yticks = ax.get_yticks().tolist()
        yticklabels = ax.get_yticklabels()
        pos = len(yticks)
        yticks.append(pos)
        pos -= (plotData[1] / 2.0)
        yticklabels.append(self.id)

        ax.set_yticks(yticks)
        ax.set_yticklabels(yticklabels)

        h = ax.barh([pos] * len(plotData[0]), plotData[0], height=plotData[1], color=plotData[2], left=plotData[3],
                    edgecolor='grey', linewidth=0.5)

        # building legend
        legendMap = plotData[4]
        legend = ax.get_legend()
        labels = [] if legend is None else [str(x._text) for x in legend.texts]
        handles = [] if legend is None else legend.legendHandles
        # handles, labels = ax.get_legend_handles_labels()
        for key in legendMap.iterkeys():
            if key not in labels:
                handles.append(h[legendMap[key]])
                labels.append(key)

        ax.legend(handles, labels, loc='center left', bbox_to_anchor=(1, 0.5))
        h1, l1 = ax.get_legend_handles_labels()
        # self.__sortPlotXaxis(ax, x, xAxisLabels, xAxisTicks)

        return xAxisLabels, xAxisTicks

    def __plotInternal(self, ax, x, k):
        self._Device__plotInternal(ax, x, k)

    def collectData(self, startDate, lambdaFunc):
        colors = self.colors

        i = self._Device__skipToDate(startDate)

        x = []
        xColorKeys = []
        xByClass = {}

        for key in colors.iterkeys():
            xByClass[key] = []

        # update state on startDate if state exists
        iStart = i
        if iStart > 0:
            i -= 1
        else:
            x.append(startDate)
            xColorKeys.append(self.nullValue)
            xByClass[self.nullValue].append(startDate)

        while i < len(self.root):
            child = self.root[i]
            date = datetime.strptime(child.get('Time')[:-3], self.dateFormat)

            date = lambdaFunc(x, date)
            if date is None:
                break

            x.append(date)
            i += 1

            key = child.text
            xByClass[key].append(date)
            xColorKeys.append(key)

        # update state on startDate if state exists
        if iStart > 0:
            x[0] = startDate

        k = [xByClass, xColorKeys]
        return x, k

# def __init__(self, filename, nullColorDot, dotColors, nullColorBar, barColors):
#     super(OnOff, self).__init__(filename)
#
#     self.nullColorDot = nullColorDot
#     self.colorsDots = {self.nullValue: nullColorDot, '0': dotColors[0], '1': dotColors[1]}
#
#     self.nullColorBar = nullColorBar
#     self.colorsBars = {self.nullValue: nullColorBar, '0': barColors[0], '1': barColors[1]}
#
#     self.nullColor = self.nullColorBar
#     self.colors = self.colorsBars
#     # self.colors = self.colorsBars

# def setPlotDots(self):
#     self.nullColor = self.nullColorDot
#     self.colors = self.colorsDots
#
# def setPlotBars(self):
#     self.nullColor = self.nullColorBar
#     self.colors = self.colorsBars

# def __mergeXaxis(self, axTicks, axLabels, xAxisTicks, xAxisLabels):
#     i = 0
#     j = 0
#     while i < len(axTicks):
#         while j < len(xAxisTicks) and axTicks[i] > xAxisTicks[j]:
#             j += 1
#
#         if j >= len(xAxisTicks) or axTicks[i] < xAxisTicks[j]:
#             xAxisTicks.insert(j, axTicks[i])
#             xAxisLabels.insert(j, datetime.strptime(axLabels[i]._text, self.dateFormat))
#             # xAxisLabels.insert(j, str(axLabels[i]._text))
#
#         i += 1
#
# def __sortPlotXaxis(self, ax, x, xAxisLabels, xAxisTicks):
#     self._Device__sortPlotXaxis(ax, x, xAxisLabels, xAxisTicks)
#
# def _Device__sortPlotXaxis(self, ax, x, xAxisLabels, xAxisTicks):
#     # merge labels
#     self.__mergeXaxis(ax.get_xticks(), ax.get_xticklabels(), xAxisTicks, xAxisLabels)
#
#     # Removes too close labels
#     minGap = round((xAxisTicks[len(xAxisTicks) - 1] - xAxisTicks[0]) / 50.0)
#     res = Plot.dateWithMinimalGap([xAxisLabels, xAxisTicks], lambda i: xAxisTicks[i] - xAxisTicks[i - 1], minGap)
#     xAxisLabels = res[0]
#     xAxisTicks = res[1]
#
#     ax.set_xticks(xAxisTicks)
#     ax.set_xticklabels(xAxisLabels)
#
#     bgcolor = 0.95
#     ax.set_axis_bgcolor((bgcolor, bgcolor, bgcolor))

# def plotDots(self, ax, k):
#     for key in k.iterkeys():
#         ax.plot(k[key], [0] * len(k[key]), self.colors[key], label=key)

# def _Device__plotInternal(self, ax, x, k):
#     self.plotDots(ax, k[0])
#     ax.legend()

# def buildBarsPlot(self, ax, startDate, endDate, yHeight=0):
#     lambdaFunc = lambda x, date: date < endDate
#     x, k = self.collectData(startDate, lambdaFunc, self.colorsBars)

# x.append(endDate)
# x.insert(0, startDate)
# nPts = len(x)

# print('Start date:[%s]' % x[0])
# print('End date:[%s]' % x[nPts - 1])
# print('nPts:[%d]' % nPts)


"""
The module in which they are implemented
    pages for post administration (creation, editing, deletion)
    post list page
    post details page
"""

from datetime import datetime

from flask import Flask, render_template, abort, url_for, Markup
from flask_admin import Admin, form

from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
import os

app = Flask(__name__, static_url_path="/static")
app.config["FLASK_ADMIN_SWATCH"] = "cerulean"
app.config["SECRET_KEY"] = "anykey"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db/blog.sqlite"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
app.config['STORAGE'] = "static/storage/post_img"
db = SQLAlchemy(app)

class Post(db.Model):
    """
        A class of Post

        ORM class of db

        Attributes
        -------
         id : Integer
            id of record
        title : String(100)
            title of post
        content : String
            content of post
        image : String
            the path to the saved image
        create_date : DateTime
            date and time of post creation
        is_active : Boolean
            a sign of active post

        Methods
        -------
        __repr__(self)
            Representative of post record

        """

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.String(), nullable=False)
    image = db.Column(db.String())
    create_date = db.Column(db.DateTime(), default=datetime.now)
    is_active = db.Column(db.Boolean, default=False, nullable=False)

    def __repr__(self):
        return f"<Post(id={self.id}, title={self.title}," \
               f" create_date={self.create_date})>"

file_path = os.path.abspath(os.path.dirname(__name__))

def name_gen_image(model, file_data):
    """Generate name of image from save to db

       Parameters
       ----------
       model :
           the model object
       file_data :
           upload file data

       Returns
       -------
       hash_name
           a name of file
       """

    hash_name = f'{model.title}/{file_data.filename}'
    return hash_name

class PostView(ModelView):
    """
        A class of Post View

        Methods
        -------
        _list_thumbnail(view, context, model, name)
            Return tumbnail url

        create_form(self, obj)
            Return admin create form

        edit_form(self, obj):
            Return admin edit form
        """
    column_display_pk = True
    can_create = True
    can_edit = True
    can_delete = True

    def _list_thumbnail(view, context, model, name):
        if not model.image:
            return ''

        url = url_for('static', filename=os.path.join('storage/', model.image))
        if model.image.split('.')[-1] in ['jpg', 'jpeg', 'png', 'svg', 'gif']:
            return Markup(f'<img src={url} width="100">')

    column_formatters = {
        'image': _list_thumbnail
    }

    form_extra_fields = {

        "image": form.ImageUploadField('',

                                        base_path=
                                        os.path.join(file_path, 'static/storage'),
                                        url_relative_path='storage',
                                        namegen=name_gen_image,
                                        max_size=(1200, 780, True),
                                        thumbnail_size=(100, 100, True),

                                        )}

    def create_form(self, obj=None):
        return super(PostView, self).create_form(obj)

    def edit_form(self, obj=None):
        return super(PostView, self).edit_form(obj)

admin = Admin(app, name='microblog', template_mode='bootstrap3')
admin.add_view(PostView(Post, db.session, name="Posts"))

@app.get("/")
def index():
    """Index page renderer

        Returns
        -------
        render template
            render template of index.html
        """
    return render_template("index.html")

@app.route("/posts")
def posts():
    """Posts page renderer

            Returns
            -------
            render template
                render template of blog.html
            """
    posts_qry = db.session.query(Post).all()
    return render_template("blog.html", posts=posts_qry)

@app.route("/posts/<int:post_id>")
def get_post(post_id):
    """Post page renderer

            Parameters
            ----------
            post_id : int
                The id of record in post db

            Returns
            -------
            render template
                render template of post.html
            """
    posts_qry = db.session.query(Post).all()
    image_qry = db.session.query(Post.image).filter(Post.id == post_id).all()
    image_name = str(image_qry[0])
    image_file = url_for('static', filename='storage/' + image_name)
    post = next(filter(lambda p: p.id == post_id, posts_qry), None)
    if post is None:
        abort(404)
    return render_template("post.html", post=post, image_url=image_file)

if __name__ == "__main__":
    app.run(debug=True)


import unittest
from typing import List

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        i, n = 0, len(matrix)
        while i < n // 2:
            matrix[i], matrix[-i - 1] = matrix[-i - 1], matrix[i]
            i += 1

        for i in range(n):
            for j in range(i):
                matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

        return matrix

class Tests(unittest.TestCase):
    def tests(self):
        sol = Solution()
        self.assertEqual(
            [[7, 4, 1], [8, 5, 2], [9, 6, 3]],
            sol.rotate(matrix=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
        )
        self.assertEqual(
            [[15, 13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9], [16, 7, 10, 11]],
            sol.rotate(
                matrix=[[5, 1, 9, 11], [2, 4, 8, 10], [13, 3, 6, 7], [15, 14, 12, 16]]
            ),
        )

if __name__ == "__main__":
    unittest.main()