from datetime import datetime
import itertools
import json
from abc import ABC
from dateutil import parser

import pandas as pd
import re

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QDialog, QDialogButtonBox, QGridLayout, QListWidget, QWidget, \
    QLineEdit, QPushButton, QLabel, QListWidgetItem, QListView, QTableView, QSplitter, \
    QVBoxLayout, QToolBar
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, QSettings, QSize

from . import plugin_base
from util import pandas_table_model


class TuflowFvPluginItem(plugin_base.PluginItemBase):

    def __init__(self, plugin, sim_filename):
        super().__init__(plugin)
        self.sim_filename = sim_filename
        self.succeeded = False
        self.start_time = None
        self.end_time = None
        self.config_num = None
        self.gpus_to_use = set()  # Set of GPUs to use for simulation
        self.cpus_to_use = 0
        self.cpus_counted = 0
        self.fv_uses_dates = False
        self.timeFormatRegex = re.compile(".*TIME FORMAT.*==(.*)", re.IGNORECASE)
        self.startTimeRegex = re.compile(".*START TIME.*==(.*)", re.IGNORECASE)
        self.endTimeRegex = re.compile(".*END TIME.*==(.*)", re.IGNORECASE)
        self.timestepDateTimeRegex = re.compile(".*t =(.*)dt =.*", re.IGNORECASE)
        self.timestepHrsRegex = re.compile(".*t = (.*) hrs.*", re.IGNORECASE)
        self.fatalErrorRegex = re.compile(".*(Fatal Error Encountered).*", re.IGNORECASE)

    def clone_info(self):
        new_item = TuflowFvPluginItem(self.plugin, self.sim_filename)
        return new_item

    def get_executable(self):
        return self.plugin.get_executable()

    def get_sim_description(self):
        return f''

    def resources(self):
        return self.gpus_to_use, self.cpus_counted

    def able_to_run(self, gpus_avail, cpus_avail):
        # See if there is an available configuration available
        avail_configs = self.plugin.available_configs()
        # print(avail_configs)
        ngpus_avail = len(gpus_avail)
        for config_index, (gpus, cpus, cpus_counted) in avail_configs:
            # print(gpus)
            # print(cpus_counted)
            if gpus <= ngpus_avail and cpus_counted <= cpus_avail:
                return True
        return False

    def get_commandline_arguments(self, gpus_avail, cpus_avail):
        args = []

        self.config_num = None

        ngpus_avail = len(gpus_avail)

        avail_configs = self.plugin.available_configs()
        for config_index, (gpus, cpus, cpus_counted) in avail_configs:
            if gpus <= ngpus_avail and cpus_counted <= cpus_avail:
                self.config_num = config_index
                gpus_avail_list = sorted(list(gpus_avail))
                self.gpus_to_use = set(gpus_avail_list[:gpus])
                self.cpus_to_use = cpus
                self.cpus_counted = cpus_counted
                break

        if self.config_num is None:
            raise ValueError('get_commandline_arguments config not found')

        self.plugin.start_running_config(self.config_num)

        if self.gpus_to_use:
            for gpu in self.gpus_to_use:
                args.append(f'-pu{gpu}')

        args.append(str(self.sim_filename))
        # print(args)

        return args

    # returns updated percent complete or None if not updated
    def process_screen_line(self, line_text):
        progress_percent = None
        # figure out if we are using date / times, the start time and the end time
        time_format_text = self.timeFormatRegex.search(line_text)
        if time_format_text:
            time_format_text = time_format_text.groups()[0].strip()
            if time_format_text.strip().lower() != 'hours':
                self.fv_uses_dates = True

        start_time_text = self.startTimeRegex.search(line_text)
        if self.start_time is None and start_time_text:
            start_time_text = start_time_text.groups()[0].strip()
            if self.fv_uses_dates:
                self.start_time = parser.parse(start_time_text, dayfirst=True, fuzzy=True)
                # self.start_time = datetime.strptime(start_time_text, "%d/%m/%Y %H:%M:%S", fuzzy=True)
            else:
                self.start_time = float(start_time_text)
            # print(f'Start time: {self.start_time}')

        end_time_text = self.endTimeRegex.search(line_text)
        if end_time_text:
            end_time_text = end_time_text.groups()[0].strip()
            if self.fv_uses_dates:
                self.end_time = parser.parse(end_time_text, dayfirst=True, fuzzy=True)
                # self.end_time = datetime.strptime(end_time_text, "%d/%m/%Y %H:%M:%S")
            else:
                self.end_time = float(end_time_text)
            # print(f'End time: {self.end_time}')

        if self.fv_uses_dates:
            timestep_date_time_text = self.timestepDateTimeRegex.search(line_text)
            if timestep_date_time_text:
                timestep_date_time_text = timestep_date_time_text.groups()[0].strip()
                curr_time = parser.parse(timestep_date_time_text, dayfirst=True, fuzzy=True)
                progress_percent = ((curr_time - self.start_time) / (self.end_time - self.start_time)) * 100.0
        else:
            timestep_hours_text = self.timestepHrsRegex.search(line_text)
            if timestep_hours_text:
                curr_time = float(timestep_hours_text.groups()[0].strip())
                # print(f'curr time: {curr_time}')
                progress_percent = ((curr_time - self.start_time) / (self.end_time - self.start_time)) * 100.0

        if line_text.find("Run Successful") != -1:
            self.succeeded = True

        if progress_percent is not None:
            # make sure we are at least 1 but less than 99
            progress_percent = max(min(1.0, progress_percent), progress_percent)
        return progress_percent

    def sim_finished(self):
        self.plugin.finished_running_config(self.config_num)
        self.config_num = None

    def run_finished_successfully(self):
        return self.succeeded

    def set_omp_threads(self):
        return self.cpus_to_use > 1
