import numpy as np
import pandas as pd
import pygimli as pg
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import pygimli.meshtools as mt
from pygimli.physics import ert
from pygimli.physics.ves import VESModelling
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class PolygonTab:
    def __init__(self, parent, polygon_data, on_save):
        self.polygon_data = polygon_data
        self.on_save = on_save
        
        # Создание вкладки
        self.frame = tk.Frame(parent)
        
        # Поле ввода сопротивления
        self.resistivity_label = tk.Label(self.frame, text="Resistivity:")
        self.resistivity_label.pack(anchor=tk.W)
        
        self.resistivity_entry = tk.Entry(self.frame)
        self.resistivity_entry.insert(0, str(polygon_data['resistivity']))
        self.resistivity_entry.pack(anchor=tk.W, fill=tk.X)
        
        # Таблица точек (X, Y)
        self.points_label = tk.Label(self.frame, text="Points (X, Y):")
        self.points_label.pack(anchor=tk.W)
        
        self.points_table = ttk.Treeview(self.frame, columns=("X", "Y"), show="headings")
        self.points_table.heading("X", text="X")
        self.points_table.heading("Y", text="Y")
        self.points_table.pack(fill=tk.BOTH, expand=True)

        self.points_table.bind("<Double-1>", self.on_point_double_click)

        # Заполнение таблицы точек
        self.update_points_table()

        # Кнопки
        self.add_point_button = tk.Button(self.frame, text="Add Point", command=self.add_point)
        self.add_point_button.pack(side=tk.LEFT, pady=5)

        self.delete_point_button = tk.Button(self.frame, text="Delete Point", command=self.delete_point)
        self.delete_point_button.pack(side=tk.LEFT, pady=5)
        
        self.save_button = tk.Button(self.frame, text="Save Changes", command=self.save_changes)
        self.save_button.pack(side=tk.LEFT, pady=5)

    def update_points_table(self):
        self.points_table.delete(*self.points_table.get_children())
        for i, point in enumerate(self.polygon_data['points']):
            self.points_table.insert("", "end", iid=i, values=(point[0], point[1]))

    def add_point(self):
        # Добавляем новую точку, если в полигоне уже 3 или более точек
        self.polygon_data['points'].append([0, 0])  # Новая точка с дефолтными координатами
        self.update_points_table()

    def delete_point(self):
        if len(self.polygon_data['points']) > 3:
            selected_item = self.points_table.selection()
            if selected_item:
                self.points_table.delete(selected_item[0])
                self.polygon_data['points'].pop(int(selected_item[0]))
                self.update_points_table()

    def on_point_double_click(self, event):
        item = self.points_table.selection()
        if item:
            x, y = self.points_table.item(item[0], 'values')
            x = simpledialog.askfloat("Edit Point", "Enter new X value:", initialvalue=x)
            y = simpledialog.askfloat("Edit Point", "Enter new Y value:", initialvalue=y)
            if x is not None and y is not None:
                self.points_table.item(item[0], values=(x, y))
                self.polygon_data['points'][int(item[0])] = [x, y]

    def save_changes(self):
        # Обновление сопротивления
        resistivity = self.resistivity_entry.get()
        try:
            self.polygon_data['resistivity'] = float(resistivity)
        except ValueError:
            messagebox.showerror("Error", "Invalid resistivity value.")
            return

        # Обновление координат точек
        points = []
        for child in self.points_table.get_children():
            x, y = self.points_table.item(child, 'values')
            try:
                points.append([float(x), float(y)])
            except ValueError:
                messagebox.showerror("Error", "Invalid point coordinates.")
                return

        if len(points) >= 3:
            self.polygon_data['points'] = points
        else:
            messagebox.showerror("Error", "A polygon must have at least 3 points.")
            return
        
        self.on_save()  # Уведомляем родителя об обновлении данных

class GeoApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Geophysical Modelling GUI")
        self.geometry("1200x800")

        # Основной макет
        self.main_frame = tk.Frame(self)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # Разделение окна на две части
        self.left_frame = tk.Frame(self.main_frame, width=400)
        self.left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)
        self.right_frame = tk.Frame(self.main_frame)
        self.right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.polygons = []  # Начальная модель пустая
        self.datapole = None
        self.surrounding_resistivity = 100  # Значение сопротивления окружающей среды по умолчанию

        self.create_widgets()

    def create_widgets(self):
        # Левая панель
        self.polygon_frame = tk.LabelFrame(self.left_frame, text="Polygon Editor", padx=10, pady=10)
        self.polygon_frame.pack(fill=tk.BOTH, expand=True)

        self.polygon_tabs = ttk.Notebook(self.polygon_frame)
        self.polygon_tabs.pack(fill=tk.BOTH, expand=True)

        self.add_polygon_button = tk.Button(self.left_frame, text="Add Polygon", command=self.add_polygon)
        self.add_polygon_button.pack(pady=5)

        self.delete_polygon_button = tk.Button(self.left_frame, text="Delete Polygon", command=self.delete_polygon)
        self.delete_polygon_button.pack(pady=5)

        # Поле ввода сопротивления окружающей среды
        self.surrounding_resistivity_label = tk.Label(self.left_frame, text="Surrounding Resistivity:")
        self.surrounding_resistivity_label.pack(anchor=tk.W)

        self.surrounding_resistivity_entry = tk.Entry(self.left_frame)
        self.surrounding_resistivity_entry.insert(0, str(self.surrounding_resistivity))
        self.surrounding_resistivity_entry.pack(anchor=tk.W, fill=tk.X)

        # Правая панель
        self.plot_frame = tk.Frame(self.right_frame)
        self.plot_frame.pack(fill=tk.BOTH, expand=True)

        self.figure = plt.Figure(figsize=(8, 6), dpi=100)
        self.ax = self.figure.add_subplot(111)
        self.canvas = FigureCanvasTkAgg(self.figure, master=self.plot_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        self.load_button = tk.Button(self.right_frame, text="Load Data", command=self.load_data)
        self.load_button.pack(pady=10)

        self.plot_model_button = tk.Button(self.right_frame, text="Plot 2D Model", command=self.plot_model)
        self.plot_model_button.pack(pady=10)

        self.plot_curve_button = tk.Button(self.right_frame, text="Plot VES Curves", command=self.plot_curve)
        self.plot_curve_button.pack(pady=10)

    def add_polygon(self):
        # Создаем новый полигон с 3 точками по умолчанию
        points = [[0, 0], [100, 0], [50, -100]]  # Минимум 3 точки
        resistivity = 100  # Сопротивление по умолчанию

        polygon_data = {'points': points, 'resistivity': resistivity}
        self.polygons.append(polygon_data)
        self.update_polygon_tabs()

    def delete_polygon(self):
        selected_tab = self.polygon_tabs.select()
        if selected_tab:
            tab_index = self.polygon_tabs.index(selected_tab)
            del self.polygons[tab_index]
            self.update_polygon_tabs()

    def update_polygon_tabs(self):
        # Очистка текущих вкладок
        for tab in self.polygon_tabs.tabs():
            self.polygon_tabs.forget(tab)

        for idx, polygon in enumerate(self.polygons):
            tab = PolygonTab(self.polygon_tabs, polygon, self.update_polygon_tabs)
            self.polygon_tabs.add(tab.frame, text=f"Polygon {idx + 1}")

    def load_data(self):
        # Загружаем данные из Excel или другого источника
        filepath = "ves47.xlsx"
        df = pd.read_excel(filepath)
        self.datapole = df.values
        messagebox.showinfo("Data Loaded", "Data has been loaded successfully.")

    def plot_model(self):
        # Построение 2D модели на основе полигонов
        plc = mt.createWorld(start=[-800, -600], end=[800, 0], layers=[])

        # Добавляем полигоны в PLC
        for idx, polygon in enumerate(self.polygons):
            poly = mt.createPolygon(polygon['points'], isClosed=True, marker=idx + 1)
            plc += poly  # Добавляем полигоны в PLC

        # Создаем сетку из PLC
        mesh = pg.meshtools.createMesh(plc, quality=33)

        # Определяем сопротивления для каждой ячейки сетки
        resistivity_data = np.full(mesh.cellCount(), self.surrounding_resistivity)  # Значение по умолчанию для окружающей среды
        for cell in mesh.cells():
            marker = cell.marker()
            if marker > 0 and marker <= len(self.polygons):
                resistivity_data[cell.id()] = self.polygons[marker - 1]['resistivity']
            else:
                resistivity_data[cell.id()] = self.surrounding_resistivity

        # Отображение модели
        self.ax.clear()
        pg.show(mesh, ax=self.ax, label=pg.unit('res'), logScale=True)
        self.canvas.draw()



    def plot_curve(self):
        if self.datapole is None or len(self.datapole) == 0:
            messagebox.showwarning("Warning", "Load data first!")
            return

        self.scheme = ert.createData(pg.utils.grange(start=0.1, end=480, dx=1, n=35, log=True), sounding=True)

        # Построение 2D модели на основе полигонов
        plc = mt.createWorld(start=[-800, -600], end=[800, 0], layers=[])

        # Добавляем полигоны в PLC
        for idx, polygon in enumerate(self.polygons):
            poly = mt.createPolygon(polygon['points'], isClosed=True, marker=idx + 1)
            plc += poly  # Добавляем полигоны в PLC

        # Создаем сетку из PLC
        mesh = pg.meshtools.createMesh(plc, quality=33)

        # Определяем сопротивления для каждой ячейки сетки
        resistivity_data = np.full(mesh.cellCount(), self.surrounding_resistivity)  # Значение по умолчанию для окружающей среды
        for cell in mesh.cells():
            marker = cell.marker()
            if marker > 0 and marker <= len(self.polygons):
                resistivity_data[cell.id()] = self.polygons[marker - 1]['resistivity']
            else:
                resistivity_data[cell.id()] = self.surrounding_resistivity
        # Check if the shape of the array and access columns
        print(self.datapole)  # Add this line to inspect the data

        try:
            # Assuming self.datapole is a 2D numpy array and columns are indexed
            # For example, let's assume columns are: [a, b, n, m, rhoa]
            a = self.datapole[:, 0]
            b = self.datapole[:, 1]
            n = self.datapole[:, 2]
            m = self.datapole[:, 3]
            rhoa = self.datapole[:, 4]
            
            ab2p = (b - a) / 2
            mn2p = (n - m) / 2
            x = pg.x(self.scheme)
            ab2 = (x[self.scheme('b')] - x[self.scheme('a')]) / 2
            mn2 = (x[self.scheme('n')] - x[self.scheme('m')]) / 2
            data = ert.simulate(mesh, res=resistivity_data, scheme=self.scheme, verbose=False)      
            ves = VESModelling(ab2=ab2, mn2=mn2)
        
            # Finalize the plot
            self.ax.clear()
            self.ax.loglog(ab2, data, '-o', label='2D model')
            self.ax.loglog(ab2p, rhoa, '-x', label='Field VES curve')
            self.ax.plot(ab2, ves.response([0.2, 1.5, 9, 60, 75, 300, 70, 30, 240, 115, 2000]), '-x', label='1D inversion')
            self.ax.set_xlabel('AB/2 (m)')
            self.ax.set_ylabel('Apparent resistivity ($\Omega$m)')
            self.ax.grid(True)
            self.ax.legend()
            self.canvas.draw()
            
        except Exception as e:
            messagebox.showerror("Error", "Index out of range in the data array.")
        except Exception as e:
            messagebox.showerror("Error", str(e))
    
    def is_point_in_polygon(self, point, polygon_points):
        # Простейшая функция проверки, находится ли точка в полигоне
        # Это нужно заменить на вашу реализацию, если у вас есть сложные полигоны
        from matplotlib.path import Path
        path = Path(polygon_points)
        return path.contains_point(point)

if __name__ == "__main__":
    app = GeoApp()
    app.mainloop()