import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
import seaborn as sn
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as colors
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from concurrent.futures import ProcessPoolExecutor as PPE
from concurrent.futures import as_completed
from skimage import measure
from time import time
from tqdm import tqdm
from pickle import loads
#import pandas as pd
import vtk
import plotly.graph_objects as go

from ..tests.bumpy_sphere import bumpy_sphere

def mpu_meshgrid(mpu_object,buf=1.25,res=10,workers=1,k=None,plane_axis=None,plane_value=None,marching=False,gradient=False,record=False,global_f=False):
    dims = []
    if not marching:
        for d in range(mpu_object.ddim):
            if d is plane_axis:
                continue
            dims.append(np.linspace(mpu_object.dim_range[d*2]-buf,
                                    mpu_object.dim_range[d*2+1]+buf,res))
    else:
        res = res*1j
        x,y,z = np.ogrid[mpu_object.dim_range[0]-buf:mpu_object.dim_range[1]+buf:res,
                         mpu_object.dim_range[2]-buf:mpu_object.dim_range[3]+buf:res,
                         mpu_object.dim_range[4]-buf:mpu_object.dim_range[5]+buf:res]
        results = mpu_object.function_marching(x,y,z)
        return (x,y,z),results
    DIMS = np.meshgrid(*dims)
    DIMSf = []
    for d in range(len(DIMS)):
        DIMSf.append(DIMS[d].flatten())
    value = []
    if plane_value is not None:
        plane_values = [plane_value]*len(DIMSf[0])
        DIMSf.insert(plane_axis,plane_values)
        plane = np.array(plane_values).reshape(DIMS[0].shape)
        DIMS.insert(plane_axis,plane)
    if workers > 1:
        chunksize = len(DIMSf[0])//workers
        number_of_chunks = len(DIMSf[0])//chunksize
        executor = PPE(max_workers=workers)
        if global_f:
            pass
        elif k is not None:
            k = [k]*len(DIMSf[0])
            DIMSf.append(k)
        else:
            k = [len(mpu_object.patches)]*len(DIMSf[0])
            DIMSf.append(k)
        if not gradient:
            result_generator  = executor.map(loads(mpu_object.pickled_DD[0]),zip(*DIMSf),chunksize=chunksize)
        else:
            result_generator  = executor.map(loads(mpu_object.pickled_DD[1]),zip(*DIMSf),chunksize=chunksize)
        executor.shutdown(wait=True)
        if not gradient:
            results = []
            for result in result_generator:
                results.append(result)
            results = np.array(results)
            results = results.reshape(DIMS[0].shape)
        else:
            u = []
            v = []
            w = []
            for result in result_generator:
                u.append(result[0])
                v.append(result[1])
                w.append(result[2])
            u = np.array(u)
            u = u.reshape(DIMS[0].shape)
            v = np.array(v)
            v = u.reshape(DIMS[0].shape)
            w = np.array(w)
            w = u.reshape(DIMS[0].shape)
            results = [u,v,w]
    else:
        time_data = []
        results = []
        if global_f:
            pass
        elif k is not None:
            k = [k]*len(DIMSf[0])
            DIMSf.append(k)
        else:
            k = [len(mpu_object.patches)]*len(DIMSf[0])
            DIMSf.append(k)
        if not gradient:
            for i in zip(*DIMSf):
                start = time()
                results.append(mpu_object.DD[0](i))
                stop = time() - start
                time_data.append(stop)
            results = np.array(results)
            results = results.reshape(DIMS[0].shape)
            if record:
                results = time_data
        else:
            u = []
            v = []
            w = []
            for i in zip(*DIMSf):
                start = time()
                results = mpu_object.DD[1](i)
                stop = time() - start
                time_data.append(stop)
                u.append(results[0])
                v.append(results[1])
                w.append(results[2])
            u = np.array(u)
            u = u.reshape(DIMS[0].shape)
            v = np.array(v)
            v = v.reshape(DIMS[0].shape)
            w = np.array(w)
            w = w.reshape(DIMS[0].shape)
            results = [u,v,w]
            if record:
                results = time_data
    return DIMS,results

def plot_volume(mpu_object,resolution=20,workers=1,cmin=-1,cmax=0,surface_count=14,k=None,global_f=False,show_points=False):
    """
    This function accepts a perfusion domain object instance and returns a figure
    object for rendering the domain for visualization.

    Parameters
    ----------
              mpu_object: svcco.surface object instance

              resolution: int (default: 20)
                          The number of points along each axis to sample when
                          determining the contours of the implicit function
                          representing the domain.

                          The total number of sampled points scales cubically
                          to the resolution size. (i.e. resolution=20;
                          total_points=8000)

              workers: int (default: 1)
                          The number of processes to split the function evaluations
                          among. Becuase volume rendering can easily require tens
                          of thousands of function evaluations
    """
    DIMS,results = mpu_meshgrid(mpu_object,res=resolution,workers=workers,k=k,global_f=global_f)
    if show_points:
        fig = go.Figure(data=(go.Isosurface(
                        x=DIMS[0].flatten(),
                        y=DIMS[1].flatten(),
                        z=DIMS[2].flatten(),
                        value=results.flatten(),
                        opacity=0.1,
                        isomin=cmin,
                        isomax=cmax,
                        surface_count=surface_count),go.Scatter3d(x = mpu_object.points[:,0],y=mpu_object.points[:,1],z=mpu_object.points[:,2])))
    else:
        fig = go.Figure(data=(go.Isosurface(
                        x=DIMS[0].flatten(),
                        y=DIMS[1].flatten(),
                        z=DIMS[2].flatten(),
                        value=results.flatten(),
                        opacity=0.1,
                        isomin=cmin,
                        isomax=cmax,
                        surface_count=surface_count)))
    fig.show()
    return fig

def plot_volume_individual(func,x_range,y_range,z_range,buf=1.25,res=20,surface_count=15,cmin=-1,cmax=0):
    res = res*1j
    x,y,z = np.ogrid[x_range[0]-buf:x_range[1]+buf:res,
                     y_range[0]-buf:y_range[1]+buf:res,
                     z_range[0]-buf:z_range[1]+buf:res]
    results = func(x,y,z)
    fig = go.Figure(data=(go.Isosurface(
                    x=x.flatten(),
                    y=y.flatten(),
                    z=z.flatten(),
                    value=results.flatten(),
                    opacity=0.1,
                    isomin=cmin,
                    isomax=cmax,
                    surface_count=surface_count)))
    fig.show()

def plot_slice_error(mpu_object,resolution=20,k=None,workers=1,plane_axis=2,plane_value=0,unit_scale='mm'):
    DIMS,results_exact = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                      k=len(mpu_object.patches),plane_axis=plane_axis,
                                      plane_value=plane_value)
    DIMS,results_approximate = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                      k=k,plane_axis=plane_axis,plane_value=plane_value)
    DIMSv,voxels = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                k=len(mpu_object.patches),marching=True)
    verts, faces, normals, values = measure.marching_cubes(voxels,level=0)
    verts *= np.array([np.diff(ar.flat)[0] for ar in [DIMSv[0],DIMSv[1],DIMSv[2]]])
    verts += np.array([DIMSv[0].min(),DIMSv[1].min(),DIMSv[2].min()])

    error = results_exact - results_approximate
    error_min = error.min()
    error_max = error.max()
    error_divide = max(abs(error_min),abs(error_max))
    error = error/(error_divide)
    colormap = plt.cm.RdBu
    main_axes = list(range(len(DIMS)))
    main_axes.remove(plane_axis)
    axes_labels = ['x ({})'.format(unit_scale),'y ({})'.format(unit_scale),'z ({})'.format(unit_scale)]
    axes_labels_all = ['x ({})'.format(unit_scale),'y ({})'.format(unit_scale),'z ({})'.format(unit_scale)]
    axes_labels.remove(axes_labels[plane_axis])
    fig = plt.figure(figsize=(12,6))
    spec = gridspec.GridSpec(ncols=4, nrows=1,
                             width_ratios=[1, 1, 1, 1.5])
    ax1 = fig.add_subplot(spec[0])
    ax2 = fig.add_subplot(spec[1])
    ax3 = fig.add_subplot(spec[2])
    ax4 = fig.add_subplot(spec[3],projection='3d')

    cax1 = ax1.contourf(DIMS[main_axes[0]],DIMS[main_axes[1]],results_exact,norm=colors.TwoSlopeNorm(0))
    ax1.contour(DIMS[main_axes[0]],DIMS[main_axes[1]],results_exact,levels=[0],linewidths=(2,))
    #ax1.set_title('Exact Interpolation \n {} Patches'.format(len(mpu_object.patches)))
    ax1.set_xlabel(axes_labels[0])
    ax1.set_ylabel(axes_labels[1])

    cax2 = ax2.contourf(DIMS[main_axes[0]],DIMS[main_axes[1]],results_approximate,norm=colors.TwoSlopeNorm(0))
    ax2.contour(DIMS[main_axes[0]],DIMS[main_axes[1]],results_approximate,levels=[0],linewidths=(2,))
    #ax2.set_title('Approximate Interpolation \n {} Patches'.format(k))
    ax2.set_xlabel(axes_labels[0])

    cax3 = ax3.contourf(DIMS[main_axes[0]],DIMS[main_axes[1]],error,cmap=colormap,norm=colors.TwoSlopeNorm(0))

    #ax3.set_title('Relative Truncation Error')
    ax3.set_xlabel(axes_labels[0])

    mesh = Poly3DCollection(verts[faces])
    mesh.set_edgecolor('k')
    ax4.add_collection3d(mesh)
    ax4.plot_surface(DIMS[0],DIMS[1],DIMS[2],alpha=0.2,color='r')
    ax4.set_xlim(DIMSv[0].min(),DIMSv[0].max())
    ax4.set_ylim(DIMSv[1].min(),DIMSv[1].max())
    ax4.set_zlim(DIMSv[2].min(),DIMSv[2].max())
    ax4.set_xlabel(axes_labels_all[0])
    ax4.set_ylabel(axes_labels_all[1])
    ax4.set_zlabel(axes_labels_all[2])
    plt.draw()
    p0 = ax1.get_position().get_points().flatten()
    p1 = ax2.get_position().get_points().flatten()
    p2 = ax3.get_position().get_points().flatten()
    ax_cbar = fig.add_axes([p0[0], 0.1, p1[2]-p0[0], 0.05])
    ax_cbar.set_in_layout(True)
    cbar1 = plt.colorbar(cax1, cax=ax_cbar, orientation='horizontal', pad=0.25)
    labels = cbar1.ax.get_xticklabels()
    cbar1.ax.set_xticklabels(labels, rotation=40)


    ax_cbar1 = fig.add_axes([p2[0], 0.1, p2[2]-p2[0], 0.05])
    ax_cbar1.set_in_layout(True)
    cbar2 = plt.colorbar(cax3, cax=ax_cbar1, orientation='horizontal', pad=0.25)
    labels = cbar2.ax.get_xticklabels()
    cbar2.ax.set_xticklabels(labels, rotation=40)
    plt.subplots_adjust(bottom=0.25)

    plt.show()

def level_contour(mpu_object,resolution=20,k=2,buf=1.25,level=0,workers=1,plane_axis=2,plane_value=0.5,unit_scale='mm',name="untitled"):
    if plane_axis == 0:
        plane_value = (mpu_object.x_range[1]-mpu_object.x_range[0])*plane_value + mpu_object.x_range[0]
    elif plane_axis == 1:
        plane_value = (mpu_object.y_range[1]-mpu_object.y_range[0])*plane_value + mpu_object.y_range[0]
    elif plane_axis == 2:
        plane_value = (mpu_object.z_range[1]-mpu_object.z_range[0])*plane_value + mpu_object.z_range[0]
    DIMS,results_exact = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                      k=k,plane_axis=plane_axis,
                                      plane_value=plane_value,buf=buf)
    colormap = plt.cm.RdBu
    main_axes = list(range(len(DIMS)))
    main_axes.remove(plane_axis)
    fig,ax = plt.subplots()
    CS = ax.contour(DIMS[main_axes[0]],DIMS[main_axes[1]],results_exact,levels=[level],linewidths=(2,))
    #CS.collections[0].set_label(r"$\Gamma_{" +str(len(mpu_object.patches))+ r"}$")
    return CS

def plot_mpu_boundaries(mpu_object,resolution=20,k=[2],buf=1.25,level=0,workers=1,plane_axis=2,plane_value=0.5,test_size=100,unit_scale='mm',name="untitled"):
    if plane_axis == 0:
        plane_value = (mpu_object.x_range[1]-mpu_object.x_range[0])*plane_value + mpu_object.x_range[0]
    elif plane_axis == 1:
        plane_value = (mpu_object.y_range[1]-mpu_object.y_range[0])*plane_value + mpu_object.y_range[0]
    elif plane_axis == 2:
        plane_value = (mpu_object.z_range[1]-mpu_object.z_range[0])*plane_value + mpu_object.z_range[0]
    DIMS,results_exact = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                      k=len(mpu_object.patches),plane_axis=plane_axis,
                                      plane_value=plane_value,buf=buf)
    APPROX = []
    for ki in k:
        DIMS,approximate = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                          k=ki,plane_axis=plane_axis,plane_value=plane_value)
        APPROX.append(approximate)

    colormap = plt.cm.RdBu
    main_axes = list(range(len(DIMS)))
    main_axes.remove(plane_axis)
    axes_labels = ['x ({})'.format(unit_scale),'y ({})'.format(unit_scale),'z ({})'.format(unit_scale)]
    axes_labels_all = ['x ({})'.format(unit_scale),'y ({})'.format(unit_scale),'z ({})'.format(unit_scale)]
    axes_labels.remove(axes_labels[plane_axis])
    fig,ax = plt.subplots()
    #spec = gridspec.GridSpec(ncols=4, nrows=1,
    #                         width_ratios=[1, 1, 1, 1.5])
    #ax1 = fig.add_subplot(spec[0])
    #ax2 = fig.add_subplot(spec[1])
    #ax3 = fig.add_subplot(spec[2])
    #ax4 = fig.add_subplot(spec[3],projection='3d')
    positions = []
    cax = ax.contourf(DIMS[main_axes[0]],DIMS[main_axes[1]],results_exact,norm=colors.TwoSlopeNorm(0),cmap="RdBu_r",alpha=0.5)
    CS = ax.contour(DIMS[main_axes[0]],DIMS[main_axes[1]],results_exact,levels=[level],linewidths=(2,))
    CS.collections[0].set_label(r"$\Gamma_{" +str(len(mpu_object.patches))+ r"}$")
    #fmt = {CS.levels[0]:r"$\Gamma_{" +str(len(mpu_object.patches)) + r"}$"}
    #ax.clabel(CS,fmt=fmt,fontsize=10)
    for c,ap in enumerate(APPROX):
        tmp = ax.contour(DIMS[main_axes[0]],DIMS[main_axes[1]],ap,levels=[level],linewidths=(2,),linestyles='--')
        tmp.collections[0].set_label(r"$\Gamma_{" +str(k[c])+ r"}$")
        #fmt = {tmp.levels[0]:r"$\Gamma_{" +str(k[c])+ r"}$"}
        #ax.clabel(tmp,fmt=fmt,fontsize=10)
    plt.legend()
    pts = np.random.random((test_size,3))
    xr = (np.min(DIMS[0].flatten()),np.max(DIMS[0].flatten()))
    yr = (np.min(DIMS[1].flatten()),np.max(DIMS[1].flatten()))
    zr = (np.min(DIMS[2].flatten()),np.max(DIMS[2].flatten()))
    pts[:,0] = (pts[:,0]*2-1)*(xr[1] - xr[0])/2 + (mpu_object.x_range[0] + mpu_object.x_range[1])/2
    pts[:,1] = (pts[:,1]*2-1)*(yr[1] - yr[0])/2 + (mpu_object.y_range[0] + mpu_object.y_range[1])/2
    pts[:,2] = (pts[:,2]*2-1)*(zr[1] - zr[0])/2 + (mpu_object.z_range[0] + mpu_object.z_range[1])/2
    pts[:,plane_axis] = plane_value
    interior = []
    exterior = []
    interior_wrong = []
    exterior_wrong = []
    for i in range(pts.shape[0]):
        if mpu_object.within(pts[i,0],pts[i,1],pts[i,2],len(mpu_object.patches),level=level)[0]:
            if mpu_object.within(pts[i,0],pts[i,1],pts[i,2],k[0],level=level)[0]:
                interior.append(pts[i,:].tolist())
            else:
                exterior_wrong.append(pts[i,:].tolist())
        else:
            if not mpu_object.within(pts[i,0],pts[i,1],pts[i,2],k[0],level=level)[0]:
                exterior.append(pts[i,:].tolist())
            else:
                interior_wrong.append(pts[i,:].tolist())
    interior = np.array(interior)
    exterior = np.array(exterior)
    interior_wrong = np.array(interior_wrong)
    exterior_wrong = np.array(exterior_wrong)
    if interior.shape[0] > 0:
        if interior.shape[0] > 100:
            stop = 99
        else:
            stop = None
        ax.scatter(interior[:stop,main_axes[0]],interior[:stop,main_axes[1]],c='blue',alpha=0.7)
    if exterior.shape[0] > 0:
        if exterior.shape[0] > 100:
            stop = 99
        else:
            stop = None
        ax.scatter(exterior[:stop,main_axes[0]],exterior[:stop,main_axes[1]],c='red',alpha=0.7)
    if exterior_wrong.shape[0] > 0:
        if exterior_wrong.shape[0] > 100:
            stop = 99
        else:
            stop = None
        ax.scatter(exterior_wrong[:stop,main_axes[0]],exterior_wrong[:stop,main_axes[1]],c='red',alpha=0.7,marker='x')
    if interior_wrong.shape[0] > 0:
        if interior_wrong.shape[0] > 100:
            stop = 99
        else:
            stop = None
        ax.scatter(interior_wrong[:stop,main_axes[0]],interior_wrong[:stop,main_axes[1]],c='blue',alpha=0.7,marker='x')
    plt.savefig(name+'.svg', format="svg", bbox_inches='tight')
    return interior.shape[0],exterior.shape[0],interior_wrong.shape[0],exterior_wrong.shape[0],test_size

def plot_gradient(mpu_object,workers=1,k=None,resolution=20,plane_axis=2,plane_value=0,
                  contour_overlay=True,unit_scale='mm'):
    DIMS,gradient = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                 plane_axis=plane_axis,plane_value=plane_value,
                                 gradient=True)
    DIMS,gradient_approximate = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                             plane_axis=plane_axis,plane_value=plane_value,
                                             k=k,gradient=True)
    if contour_overlay:
        DIMS_c,result = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                     plane_axis=plane_axis,plane_value=plane_value)
    main_axes = list(range(len(DIMS)))
    main_axes.remove(main_axes[plane_axis]) #use del?
    fig = plt.figure(figsize=(6,5))
    ax = fig.add_subplot(111)

    if contour_overlay:
        cax = ax.contourf(DIMS[main_axes[0]],DIMS[main_axes[1]],result,norm=colors.TwoSlopeNorm(0))
        p0 = ax.get_position().get_points().flatten()
        ax_cbar = fig.add_axes([p0[0], 0.1, p0[2]-p0[0], 0.05])
        ax_cbar.set_in_layout(True)
        cbar1 = plt.colorbar(cax, cax=ax_cbar, orientation='horizontal', pad=0.25)
        labels = cbar1.ax.get_xticklabels()
        cbar1.ax.set_xticklabels(labels, rotation=40)

    ax.quiver(DIMS[main_axes[0]],DIMS[main_axes[1]],
              gradient[main_axes[0]],gradient[main_axes[1]])

    ax.quiver(DIMS[main_axes[0]],DIMS[main_axes[1]],
              gradient_approximate[main_axes[0]],gradient_approximate[main_axes[1]],color='r')
    axes_labels = ['x ({})'.format(unit_scale),'y ({})'.format(unit_scale),'z ({})'.format(unit_scale)]
    axes_labels.remove(axes_labels[plane_axis])
    ax.set_xlabel(axes_labels[0])
    ax.set_ylabel(axes_labels[1])
    plt.subplots_adjust(bottom=0.25)
    plt.show()

def plot_gradient_error(mpu_object,workers=1,k=None,resolution=20,plane_axis=2,plane_value=0,
                  contour_overlay=True,unit_scale='mm'):
    DIMS,gradient = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                 plane_axis=plane_axis,plane_value=plane_value,
                                 gradient=True)
    DIMS,approximate = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                    plane_axis=plane_axis,plane_value=plane_value,
                                    k=k,gradient=True)
    DIMS_c,result = mpu_meshgrid(mpu_object,res=resolution,workers=workers,
                                 plane_axis=plane_axis,plane_value=plane_value)
    gradient = [gradient[0].flatten(),gradient[1].flatten(),gradient[2].flatten()]
    approximate = [approximate[0].flatten(),approximate[1].flatten(),approximate[2].flatten()]
    alignment_error = []
    magnitude_error = []
    for i in range(len(gradient[0])):
        grad_mag = (gradient[0][i]**2+gradient[1][i]**2+gradient[2][i]**2)**(1/2)
        approx_mag = (approximate[0][i]**2+approximate[1][i]**2+approximate[2][i]**2)**(1/2)
        alignment_error.append((gradient[0][i]/grad_mag)*(approximate[0][i]/approx_mag)+(gradient[1][i]/grad_mag)*(approximate[1][i]/approx_mag)+
                               (gradient[2][i]/grad_mag)*(approximate[2][i]/approx_mag))
        magnitude_error.append(grad_mag-approx_mag)
    fig = plt.figure()
    ax1 = fig.add_subplot(121)
    ax1.scatter(result.flatten(),alignment_error)
    ax1.set_xlabel('Contour Layer')
    ax1.set_ylabel('Alignment Difference')
    ax2 = fig.add_subplot(122)
    ax2.scatter(result.flatten(),magnitude_error)
    ax2.set_xlabel('Contour Layer')
    ax2.set_ylabel('Magnitude Difference')
    plt.show()

def plot_error(mpu_object,workers=1,k=None,resolution=20,plane_axis=2,plane_value=0):
    DIMS,result = mpu_meshgrid(mpu_object,workers=workers,res=resolution,plane_axis=plane_axis,
                               plane_value=plane_value)
    DIMS,approx = mpu_meshgrid(mpu_object,workers=workers,k=k,res=resolution,plane_axis=plane_axis,
                               plane_value=plane_value)
    fig = plt.figure()
    ax = fig.add_subplot(111)
    main_axes = list(range(len(DIMS)))
    main_axes.remove(main_axes[plane_axis])
    contours = ax.contour(DIMS[main_axes[0]],DIMS[main_axes[1]],result)
    levels = contours.levels[1:-1]
    level_areas = []
    level_error = []
    error_per_area = []
    resultf = result.flatten()
    approxf = approx.flatten()
    l0 = -1
    a0 = 0
    passed_levels = []
    for idx, level in enumerate(levels):
        #contour = contours.collections[idx]
        #if len(contour.get_paths()) == 0:
        #    continue
        #else:
        #    passed_levels.append(level)
        #vs = contour.get_paths()[0].vertices
        #a = 0
        #d0_0,d1_0 = vs[0]
        #for [d0_1,d1_1] in vs[1:]:
        #    dd0 = d0_1 - d0_0
        #    dd1 = d1_1 - d1_0
        #    a += 0.5*(d1_0*dd0 - d0_0*dd1)
        #    d0_0 = d0_1
        #    d1_0 = d1_1
        #level_areas.append(a-a0)
        #a0 = a
        lower_filter = set(np.argwhere(l0<resultf).flatten())
        upper_filter = set(np.argwhere(resultf<level).flatten())
        bounded_filter = list(lower_filter.intersection(upper_filter))
        level_error.append(np.mean(resultf[bounded_filter] - approxf[bounded_filter]))
        l0 = level
        #error_per_area.append(level_error[-1]/level_areas[-1])
    plt.clf()
    plt.plot(levels,level_error)
    plt.show()

def plot_time(mpu_object,resolution=20,k_start=2,k_end=100,gradient=False,plane_axis=None,plane_value=None):
    all_time = []
    mean_time = []
    k_values  = []
    sd_time_values = []
    all_error = []
    mean_error = []
    sd_error = []
    all_k = []
    #d_type = []
    DIMS,exact = mpu_meshgrid(mpu_object,k=None,res=resolution,gradient=gradient,
                                  plane_axis=plane_axis,plane_value=plane_value)
    for i in tqdm(range(k_start,k_end+1),desc='Evaluating Time Grids  '):
        k_values.append(i)
        DIMS,time_data = mpu_meshgrid(mpu_object,k=i,res=resolution,gradient=gradient,
                                      plane_axis=plane_axis,plane_value=plane_value,
                                      record=True)
        DIMS,approx = mpu_meshgrid(mpu_object,k=i,res=resolution,gradient=gradient,
                                      plane_axis=plane_axis,plane_value=plane_value)
        error = abs(exact.flatten() - approx.flatten())
        k_tmp = [i]*len(error)
        #d_type.extend(['error']*len(error))
        #d_type.extend(['time']*len(time_data))
        mean_error.append(np.mean(error))
        sd_error.append(np.std(error))
        all_time.extend(time_data)
        mean_time.append(np.mean(time_data))
        sd_time_values.append(np.std(time_data))
        all_error.extend(error)
        all_k.extend(k_tmp)

    data = pd.DataFrame()
    data['times'] = all_time
    data['k'] = all_k
    data['error'] = all_error
    sn.lineplot(data=data,x='k',y='times')
    ax2 = plt.twinx()
    sn.lineplot(data=data,x='k',y='error',ax=ax2,color='red')
    plt.show()

def time_PU_global(mpu_object):
    start = time()
    mpu_object.solve()
    pu_time = time()-start
    start = time()
    mpu_object.solve(PU=False)
    global_time = time()-start
    return pu_time,global_time

#def pu_v_global(mpu_object,start=10,stop=40):

def show_path(points,points2):
    fig = plt.figure()
    ax  = fig.add_subplot(111,projection='3d')
    ax.scatter3D(points[0,0],points[0,1],points[0,2],color='g')
    ax.scatter3D(points[1:,0],points[1:,1],points[1:,2],color='r')
    ax.scatter3D(points2[:,0],points2[:,1],points2[:,2],color='b')
    plt.show()

def show_mesh(points,points2,points3):
    fig = plt.figure()
    ax  = fig.add_subplot(111,projection='3d')
    ax.scatter3D(points[:,0],points[:,1],points[:,2],color='r')
    ax.scatter3D(points2[:,0],points2[:,1],points2[:,2],color='b')
    ax.scatter3D(points3[:,0],points3[:,1],points3[:,2],color='g')
    plt.show()

def newton(mpu_object):
    f1 = mpu_object.function([1,1,1,None])
    f2 = mpu_object.function([1.1,1,1,None])
    g = np.array(mpu_object.gradient([1,1,1,None]))
    print('F1: {}'.format(f1))
    print('F2: {}'.format(f2))
    print('Ap: {}'.format(f1+np.dot(np.array([0.1,0,0]),g)))

def show_patches(mpu_object):
    patch_sets = [set(tuple([tuple(pp) for pp in p.points.tolist()])) for p in mpu_object.patches]
    unique_points = []
    overlap_points = []
    for patch_idx,patch_i in enumerate(patch_sets):
        patch_i_diff = patch_i.difference({})
        patch_i_overlap = set([])
        for patch_jdx,patch_j in enumerate(patch_sets):
            if patch_idx == patch_jdx:
                continue
            else:
                patch_i_diff = patch_i_diff.difference(patch_j)
                patch_i_overlap.update(patch_i.intersection(patch_j))
            print(len(unique_points))
        unique_points.extend(list(patch_i_diff))
        overlap_points.extend(list(patch_i_overlap))
    unique_points = np.array(unique_points)
    overlap_points = np.array(overlap_points)
    fig = plt.figure()
    ax  = fig.add_subplot(111,projection='3d')
    ax.scatter3D(unique_points[:,0],unique_points[:,1],unique_points[:,2],color='r')
    ax.scatter3D(overlap_points[:,0],overlap_points[:,1],overlap_points[:,2],color='b')
    plt.show()
#get area of contourf for L1 error estimate as a function of "tissue depth" which will basically argue
#that for our area of interest we can ignore the problematic areas of unstable far-field growth

#for gradient error take the angle and magnitude differences and also show them as a function of tissue
# layer position
