# To run the program
# Import our module
import numpy as np
import math
import tkinter as tk
import pandas as pd
import matplotlib.pyplot as plt
from tkinter import filedialog
from tkinter import messagebox
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_pdf import PdfPages


# the main Tkinter window
ws = tk.Tk()
# setting the title 
ws.title("Circular Column Design")
# dimensions of the main window
ws.attributes('-zoomed', True)
# Note: Windows use below; *Not* -fullscreen
#ws.state('zoomed')
#ws.geometry("1200x1000")

# Set up key variables common to all routines (called from bottom)
def variables():
    global dia, Fc, bar_n, bar_dia, fsy, cover, phi, check_no
    dia = 0
    bar_n = 0
    bar_dia = 0
    Fc = 0
    fsy = 0
    cover = 0
    phi = 0
    check_no = 0

# main program (called from bottom)
def main():
    global dia, Fc, bar_n, bar_dia, fsy, cover, phi, check_no   
    def print_charts():
        global fig1, x, y, phi_x, phi_y, dia, Fc, bar_n, bar_dia, fsy, cover, phi, check_no
        
        # get the values of variables from input frame
        # check of empty or zero input values
        def check_input():
            global counter
            counter = 0
                
            if not dia_col.get():
                counter = counter + 1
            if not bar_num.get():
                counter = counter + 1
            if not bar_diameter.get():
                counter = counter + 1
            if not cover_conc.get():
                counter = counter + 1
            if not Fc_conc.get():
                counter = counter + 1
            if not fsy_bar.get():
                counter = counter + 1
            if not phi_const.get():
                counter = counter + 1
            return
        
        # call check input function
        check_input()
        
        # if there is input in all boxes get values
        if counter == 0:
            
            dia = float(dia_col.get())
            bar_n =  int(bar_num.get())
            bar_dia = float(bar_diameter.get())
            cover = float(cover_conc.get())
            Fc = float(Fc_conc.get())
            fsy = float(fsy_bar.get())
            phi = float(phi_const.get())
            
            # check if any input values are "0" otherwise program will not run
            # make list of variables and test with "if" statement
            check_zero = [dia, bar_n, bar_dia, cover, Fc, fsy, phi]
            check_no = 0
            if check_no in check_zero:
                win1 = tk.Tk()
                win1.withdraw()
                messagebox.showwarning("Warning","Enter non-zero values only")
                win1.destroy()
            else:
                check_no = 1
  
        else:
            win2 = tk.Tk()
            win2.withdraw()
            messagebox.showwarning("Warning","One or more inputs missing")
            win2.destroy()
      
        def calc_charts():
            global dia, Fc, bar_n, bar_dia, fsy, cover, phi, x, y, phi_x, phi_y, fig1, canvas
            # analysis and plotting of charts
            # Calculate alpha and gamma to AS3600-2018
            if Fc > 120:
               alpha2 = 0.67
               gamma = 0.67
            else:
               alpha2 = 0.85 - 0.0015*Fc
               gamma = 0.97 - 0.0025*Fc
            
            # Youngs modulus of steel
            E = 200e9
            
            # Cross-section
            theta = np.linspace(0, 2*np.pi, 100)
            
            r = dia/2
            
            x1 = r*np.cos(theta)
            x2 = r*np.sin(theta)
            
            # reinforcing bar coordinates
            theta_b = np.linspace(0, 2*np.pi, bar_n+1)
            rb = dia/2 - (cover + bar_dia/2)
            xb1 = rb*np.cos(theta_b)
            xb2 = rb*np.sin(theta_b)
            
            # Figure that will contain graphic output
            
            # the figure that will contain the plots and text, size in inches
            fig1 = Figure(figsize = (11.67, 8.27), dpi = 100)
                
            # set up a 20 x 30 grid of boxes
            ax = fig1.add_gridspec(nrows=21, ncols=30)
            
            # add some text
            Col_input = fig1.add_subplot(ax[0:8, 0:8])
            Col_input.axis("off")
            Col_input.text(0.1,0.9,"Diameter (mm) = %.0f" % dia)
            Col_input.text(0.1,0.8,"Number of bars = %s" % bar_n)
            Col_input.text(0.1,0.7,"Bar diameter (mm) = %.0f" % bar_dia)
            Col_input.text(0.1,0.6,"Concrete cover (mm) = %.0f" % cover)
            Col_input.text(0.1,0.5,"Concrete strength (MPa) = %.0f" % Fc)
            Col_input.text(0.1,0.4,"Steel strength (MPa) = %.0f" % fsy)
            Col_input.text(0.1,0.3,"Capacity reduction = %.2f" % phi)
            
            # Column cross-section plot    
            # define plot1 and add the subplot
            # in rows 10 to 20 and columns 0 to 8
            plot1 = fig1.add_subplot(ax[11:20, 0:8])
            
            # plot reinforcing bars    
            plot1.scatter(xb2,xb1,color='black')
            
            # plot circumference    
            plot1.plot(x1, x2)  
            
            # Add a title
            plot1.set_title('Column cross section')
            
            # Add x and y Label
            plot1.set_xlabel('x axis (mm)')
            plot1.set_ylabel('y axis (mm)')
            
            # Add a grid
            plot1.grid(alpha=.4,linestyle='--')
            
            # Add x axis
            ap = r*1.15
            plot1.plot([-ap, ap],[0, 0], color='black')
            # Add y axis
            plot1.plot([0,0],[-ap,ap],color='black')
            
            # Calculate moments and forces    
            # Calculate and print bar distances from top of section
            yd1 = dia/2 - xb1
            
            # Number of depths, the first part becomes integer smaller than half No,
            # the second part evaluates to True if there is a remainder, in addition,
            # it adds True = 1; False = 0
            # Number of bar depths No_yd
            No_yd = int(bar_n/2) + (bar_n % 2 > 0)
            # Use remainder % to check if even number, if so, add 1
            if(bar_n % 2) == 0: No_yd = No_yd + 1
            
            # Convert numpy array to list
            # Bar depths (mm) yd2
            yd2 = yd1.tolist()
            # Size (length) of the list
            x = len(yd2)
            # Delete depths greater than No_yd
            # yd2 is the new shorter list of depths
            del yd2[No_yd:x]
            # Convert depths to metres
            yd3 = [i/1000 for i in yd2]
            
            # Create list with number of bars at each depth No_bars
            # Always have 1 bar at top, 2 bars at next depths, and 1 bar at bottom if even number of bars
            No_bars = [2] * (No_yd - 1)
            No_bars.insert(0, 1)
            
            if(bar_n % 2) == 0:
               No_bars.pop(No_yd-1)
               No_bars.append(1)
            
            # Create list of total steel area at each level (depth) in the section
            # Multiply No_bars by a scalar, area of reinf bar
            # Steel areas (sq.mm) Ast_i
            Ast_i = [i * math.pi*(bar_dia/2)**2 for i in No_bars]
            # Convert steel area to square metres
            Ast_d = [i/1e6 for i in Ast_i]
            
            # Change units to N and m
            Fc = Fc*1000000.0
            dia = dia/1000.0
            r = dia/2
            bar_dia = bar_dia/1000.0
            
            # Calculate axial force and moment of concrete and steel at each (slice) depth
            # Number of depths (slices) n_slices
            # Slice thickness (m) T
            n_slices = 20
            T = dia/n_slices
            # Initialise Nu_c and Mu_c
            Nu_c = 0.0
            Mu_c = 0.0
            # Moments and axial forces for plotting, initial lists
            Moments = []
            Forces = []
            for n in range(n_slices):
               # Depth to CL of slice dn
               dn = T*n + T/2
               dN = T*n + T
               # Width at CL of slice wn
               wn = 2*(dn*(2*r - dn))**0.5
               # Concrete area of slice Acn
               Acn = wn*T
               # Compression in each slice
               Ccn = Acn*alpha2*Fc
               # Accumulate conc compression for each slice
               # Concrete axial force Nu_c
               Nu_c = Nu_c + Ccn
               Mcn = Ccn*(r-dn)
               # Concrete Moment Mu_c
               Mu_c = Mu_c + Mcn
            
               # Calculate strain at bar depths
               # Steel bar strain epsilon_si
               epsilon_si = [0.003*(dN-i)/dN for i in yd3]
               # Modified strain (max=0.0025) epsilon_si_mod
               epsilon_si_mod = [0.0025 if i > 0.0025 else -0.0025 if i < -0.0025 else i for i in epsilon_si]
               # Stress in bars sigma_si
               sigma_si = [i * E for i in epsilon_si_mod]
               # Bar force Fsi
               Fsi = [i * j for i, j in zip(sigma_si, Ast_d)]
               # Steel reinf axial force Nu_s
               Nu_s = sum(Fsi)
               # Steel Moment Mu_s
               Mu_si = [i * (r - j) for i, j in zip(Fsi, yd3)]
               Mu_s = sum(Mu_si)
        
               # Sum moments and axial forces about slice depth
               Mu_n = Mu_c + Mu_s
               Nu_n = Nu_c + Nu_s
               # Add numbers to list for plotting
               Moments.append(Mu_n)
               Forces.append(Nu_n)
            
            # Change units to kN and kNm
            Moments_kNm = [0.001 * i for i in Moments]
            Forces_kN = [0.001 * j for j in Forces]
                
            # Plot Capacity Chart
            
            # define plot2 and add the subplot to figure fig1
            # in rows 0 to 20 and columns 12 to 30
            plot2 = fig1.add_subplot(ax[0:21, 12:30])
        
            # x-axis label
            plot2.set_xlabel('Moment kNm')
            # y-axis label
            plot2.set_ylabel('Force kN')
            # Plot title
            plot2.set_title('Circular Column Capacity Chart')
            
            # Plot smoothed line
            # Convert lists to arrays
            # Note: need to change x and y, plot y,x instead of x,y
            # so chart is plotted in the conventional way
            y = np.array(Moments_kNm)    
            x = np.array(Forces_kN)
                
            def smoothline(x,y):
                coefs = np.polyfit(x,y,deg=8)
                p_obj = np.poly1d(coefs) #this is a convenience class
                return p_obj
                
            p_obj = smoothline(x,y)
            x_line = np.linspace(min(x), max(x), 100) #make new xvalues 
            y_line = p_obj(x_line)
            
            plot2.plot(y,x,'o')  
            plot2.plot(y_line,x_line, 'r--')
            
            # apply reduction factor and create new plot line
            phi_y = y*phi
            phi_x = x*phi
            
            def smoothline(phi_x,phi_y):
                coefs = np.polyfit(phi_x,phi_y,deg=8)
                p_obj_phi = np.poly1d(coefs) #this is a convenience class
                return p_obj_phi
                
            p_obj_phi = smoothline(phi_x,phi_y)
            x_phi_line = np.linspace(min(phi_x), max(phi_x), 100) #make new xvalues 
            y_phi_line = p_obj_phi(x_phi_line)
            
            plot2.plot(phi_y,phi_x,'o')  
            plot2.plot(y_phi_line,x_phi_line, 'b--')
            
            # Add a grid
            plot2.grid(alpha=.4,linestyle='--')
            
            # creating the Tkinter canvas containing the Matplotlib figures
            canvas = FigureCanvasTkAgg(fig1, master = ws)
            #canvas.config(width=100, height=50)
            canvas.draw()
          
            # placing the canvas on the Tkinter window
            canvas.get_tk_widget().pack()

        # calculate the charts using function above
        if check_no > 0 and counter == 0:
            calc_charts()
    
    def recalculate():
        # remove graphic output ready for next iteration
        canvas.get_tk_widget().pack_forget()
        # go back to start of function
        print_charts()
    
    # save and quit the program
    def save():
        global x, y, phi_x, phi_y, fig1
        # add numerical output to saved pdf file
        fig2, ax = plt.subplots(figsize = (11.67, 8.27), dpi = 100)
    
        # hide axes
        fig2.patch.set_visible(False)
        ax.axis('off')
        ax.axis('tight')
    
        mdata = (y,x,phi_y,phi_x)
        # round to two decimal places
        mdata_o = np.around(mdata, decimals=2)
        # transpose to columns
        mdata_t = np.transpose(mdata_o)
        cols = ('Limit State Moments (kNm)','Limit State Forces (kN)','Reduced Moments (kNm)','Reduced Forces (kN)')
        
        df = pd.DataFrame(mdata_t, columns=cols)
    
        ax.table(cellText=df.values, colLabels=df.columns, loc='center')
        ax.set_title("Column Capacity Numerical Data")
        
        # Not sure how it happens, but adding this line caused the program to exit
        # which is fine as "save and quit" is one button
        plt.close()
        
        # tkinter window for file save dialog
        ws1 = tk.Tk()
        ws1.withdraw()
        
        # show a dialog box to save the file
        fname = filedialog.asksaveasfilename(title='Name a file', filetypes=[("pdf", ".pdf")], defaultextension='.pdf')
        pp = PdfPages(fname)
        pp.savefig(fig1)
        pp.savefig(fig2)
        pp.close()
        
        # Destroy the tkinter instance once function is complete
        # this releases and stops hanging problems
        ws1.destroy()

    
    # Input-output
    # frame for numerical input data
    frame = tk.Frame(ws, padx=10, pady=10)
    frame.pack(expand=True)
    
    dia_lbl = tk.Label(frame, text='Column diameter (mm)')
    dia_lbl.grid(row=1, column=1)
    dia_col = tk.Entry(frame)
    dia_col.grid(row=1, column=2, pady=5)
    
    bar_n_lbl = tk.Label(frame, text='Number of reo bars')
    bar_n_lbl.grid(row=2, column=1)
    bar_num = tk.Entry(frame)
    bar_num.grid(row=2, column=2, pady=5)
    
    bar_dia_lbl = tk.Label(frame, text='Bar diameter (mm)')
    bar_dia_lbl.grid(row=3, column=1)
    bar_diameter = tk.Entry(frame)
    bar_diameter.grid(row=3, column=2, pady=5)
    
    cover_lbl = tk.Label(frame, text='Cover to main bars (mm)')
    cover_lbl.grid(row=4, column=1)
    cover_conc = tk.Entry(frame)
    cover_conc.grid(row=4, column=2, pady=5)
    
    Fc_lbl = tk.Label(frame, text='Concrete Fc (MPa)')
    Fc_lbl.grid(row=5, column=1)
    Fc_conc = tk.Entry(frame)
    Fc_conc.grid(row=5, column=2, pady=5)
    
    fsy_lbl = tk.Label(frame, text='Rebar fsy (MPa)')
    fsy_lbl.grid(row=6, column=1)
    fsy_bar = tk.Entry(frame)
    fsy_bar.grid(row=6, column=2, pady=5)
    
    phi_lbl = tk.Label(frame, text='Reduction factor (constant)')
    phi_lbl.grid(row=7, column=1)
    phi_const = tk.Entry(frame)
    phi_const.grid(row=7, column=2, pady=5)
    
    note1_lbl = tk.Label(frame, padx=20, width=600, anchor='w', text='Notes :')
    note1_lbl.grid(row=1, column=3)
    note2_lbl = tk.Label(frame, padx=20, width=600, anchor='w', justify='left', wraplength=500, text='This program calculates the limit state capacity of a circular concrete section by the method \
    of slices. A capacity chart is produced and the chart along with numerical values are saved to a pdf file')
    note2_lbl.grid(row=2, column=3, rowspan=2)
    note2_lbl = tk.Label(frame, padx=20, width=600, anchor='w', justify='left', wraplength=500, text='Design codes have a variety of methods for calculating the capacity reduction factor. \
    The method can also change from one code revision to the next. For simplicity, a constant reduction factor can be entered. Limit state and reduced capacity plots are provided. \
    Enter "1" to see just one plot.')
    note2_lbl.grid(row=4, column=3, rowspan=3)
    
    # frame for buttons below the input
    frame2 = tk.Frame(frame)
    frame2.grid(row=8, columnspan=2, pady=10)
    
    # Button to show charts on screen
    btn_PI = tk.Button(frame2, text="Show Chart", padx=10, pady=5, command=print_charts)
    btn_PI.pack(side=tk.LEFT)
    
    # Button to re-calculate
    btn_PI = tk.Button(frame2, text="Re-calculate", padx=10, pady=5, command=recalculate)
    btn_PI.pack(side=tk.LEFT)
    
    # Button to save charts to file and exit program
    btn_PI = tk.Button(frame2, text="Save and Quit", padx=10, pady=5, command=save)
    btn_PI.pack(side=tk.LEFT)
    
    # Quit button
    #exit_btn = tk.Button(frame2, text='Quit', padx=10, pady=5, command=lambda:ws.destroy())
    #exit_btn.pack(side=tk.LEFT)

# define and initiate global variables
variables()
# initiate main program
main()

ws.mainloop()

