import random
import time
import algorithms  # local module
import pandas as pd
from graphics import graphic as simulation_graphic

from process import Process
from pathlib import Path


def get_cpu_time_unit():
    """
    a unit of time independent on cpu run this code for simulating time
    """

    started_at = time.time()
    for i in range(1, 10000):
        if i % 2 == 0:
            temp = i / 2
        else:
            temp = 2 * i

    ended_at = time.time()
    return ended_at - started_at


class Simulator:

    def __init__(self, algorithm: str):
        """
        run_time attr is for running algorithm in second
        cpu_total_time is total time of running processes for cpu. The number doesn't have any unit
        cpu_run_time attr total time of running processes for cpu. unit of time depend on get_cpu_time_unit function

        :param algorithm: algorithm name that valid in algorithm list
        """
        self.algorithm = algorithm
        self.algorithm_class = self.get_algorithm_class()
        self.algorithms_list = ["FCFS", "NonPreemptiveSFJ", "PreemptiveSFJ", "RR",
                                "NonPreemptivePriority", "PreemptivePriority"]
        self.processes = []
        self.total_process = 0
        self.run_time = 0
        self.cpu_run_time = 0
        self.cpu_total_time = 0
        self.cpu_utilization = 0
        self.throughput = 0
        self.average_waiting_time = 0.0
        self.average_turnaround_time = 0.0
        self.average_response_time = 0.0

    def _compress_df_rows(self, df: pd.DataFrame, column: str) -> pd.DataFrame:
        """
        you can compress your dataframe based on your column name.
        for example you have 1000 rows that their priority column have value of 0
        so you average values of all other columns and compress 1000 rows to one
        :param df: dataframe which you want to compress same column values
        :param column: the column that you want to compress values
        :return: compressed dataframe
        """
        # raise error if column not in compress columns
        if column not in ('priority', 'burst_time', 'arrival_time'):
            raise KeyError("Your column not valid")

        df = df.sort_values(column, ignore_index=True)
        unique_columns = list(df[column].unique())

        data = []
        # compress column based on column name
        if column == "priority":
            # mean over each priority
            for c in unique_columns:
                result = df.where(df[column] == c).mean(numeric_only=True)
                data.append([
                    result['waiting_time'],
                    result['turnaround_time'],
                    result['response_time']
                ])
            # create dataframe
            compress_df = pd.DataFrame(
                data,
                columns=['waiting_time', 'turnaround_time', 'response_time'],
                index=unique_columns)

        elif column == "burst_time":

            # divide variety of the column to 20 part and create range of numbers instead of a number
            pieces = 20 if len(unique_columns) > 20 else 1
            batches = len(unique_columns) // pieces
            index_ranges = []

            for c in range(pieces + 1):
                low_range = batches * c
                high_range = batches * (c + 1)
                # mean over the ranges
                result = df.where((low_range <= df[column]) & (df[column] < high_range)).mean(numeric_only=True)
                data.append([
                    result['waiting_time'],
                    result['turnaround_time'],
                    result['response_time']
                ])

                # create index ranges for creating index of dataframe
                index_ranges.append(f"{low_range}-{high_range}")

            # create dataframe
            compress_df = pd.DataFrame(
                data,
                columns=['waiting_time', 'turnaround_time', 'response_time'],
                index=index_ranges)

        elif column == "arrival_time":
            # divide variety of the column to 20 part and create range of numbers instead of a number
            pieces = 20 if len(unique_columns) > 20 else 1
            batches = len(unique_columns) // pieces
            index_ranges = []

            for c in range(pieces + 1):
                low_range = batches * c
                high_range = batches * (c + 1)
                # mean over the ranges
                result = df.where((low_range <= df[column]) & (df[column] < high_range)).mean(numeric_only=True)
                data.append([
                    result['waiting_time'],
                    result['turnaround_time'],
                    result['response_time']
                ])

                # create index ranges for creating index of dataframe
                index_ranges.append(f"{low_range}-{high_range}")

            # create dataframe
            compress_df = pd.DataFrame(
                data,
                columns=['waiting_time', 'turnaround_time', 'response_time'],
                index=index_ranges)

        return compress_df

    def set_algorithm(self, algorithm: str) -> bool:
        """
        changing algorithm and validate algorithm
        :param algorithm: new algorithm name
        :return: True if has changed
        """
        self.algorithm = algorithm
        self.algorithm_class = self.get_algorithm_class()
        return True

    @staticmethod
    def generate_processes_data(
            path: str = 'data.csv', size: int = 1000, max_arrival_time: int = 100, max_priority=10, max_burst_time=40
    ) -> bool:
        """
        Generate process with random numbers for arrival and burst time and priority. then save it to a csv file
        :param max_burst_time: maximum number for random number of burst time
        :param max_priority: maximum number for random number of priority
        :param max_arrival_time: maximum number for random number of arrival time
        :param path: path to save csv file
        :param size: number of processes
        :return: bool
        """
        data = []
        # save data as lists of lists then create dataframe. e.g [ [pid1, arrival1], [pid2, arrival2]]
        for i in range(size):
            data.append([
                i + 1,  # PID
                random.randint(0, max_arrival_time),  # arrival_time
                random.randint(0, max_priority),  # priority
                random.randint(0, max_burst_time),  # burst_time
            ])

        df = pd.DataFrame(data, columns=['pid', 'arrival_time', 'priority', 'burst_time'])
        df.to_csv(path_or_buf=path, index=False)

        return True

    def read_processes_data(self, path='data.csv', dataframe=None) -> bool:
        """
        read data from csv file or pandas dataframe
        one of path or dataframe parms needed
        :param path: string path of your csv file
        :param dataframe: you can pass dataframe object
        :return: a true boolean if everythings goes right
        """
        if isinstance(dataframe, pd.DataFrame):
            df = dataframe
        elif path and path.split('.')[-1] == 'csv':
            df = pd.read_csv(path)
        else:
            raise Exception("Your file should be a csv format or pass dataframe object to function")

        # clear last loaded processes
        self.processes.clear()

        for i in range(0, df['pid'].count()):
            process = Process(
                pid=df['pid'][i],
                arrival_time=df['arrival_time'][i],
                priority=df['priority'][i],
                burst_time=df['burst_time'][i]
            )
            self.processes.append(process)

        return True

    def get_algorithm_class(self):
        """
        find algorithm class based on algorithm folder
        :return a class
        """
        try:
            return getattr(algorithms, self.algorithm)
        except Exception as e:
            print(e)
            raise Exception("try again! you have to enter a valid algorithm")

    def run(self):
        """
        Simulate algorithm and save the result of it
        """

        if len(self.processes) == 0:
            raise Exception("you have to load processes")

        # algorithm instance. need process list object
        algorithm = self.algorithm_class(self.processes)

        start_time = time.time()
        # run algorithm
        result = algorithm.run()
        end_time = time.time()
        self.run_time = end_time - start_time

        # set result of simulation
        # this processes referred to executed processes in output of algorithm run
        processes = result['executed_processes']
        total_process = len(processes)
        cpu_total_time = result['cpu_total_time']
        cpu_run_time = cpu_total_time * get_cpu_time_unit()
        throughput = total_process / cpu_total_time
        cpu_utilization = (cpu_total_time - result['cpu_idle_time']) / cpu_total_time

        average_waiting_time = sum(process.waiting_time for process in processes) / total_process
        average_turnaround_time = sum(process.turnaround_time for process in processes) / total_process
        average_response_time = sum(process.response_time for process in processes) / total_process

        self.total_process = total_process
        self.cpu_total_time = cpu_total_time
        self.cpu_run_time = cpu_run_time
        self.throughput = throughput
        self.cpu_utilization = cpu_utilization
        self.average_waiting_time = average_waiting_time
        self.average_turnaround_time = average_turnaround_time
        self.average_response_time = average_response_time
        # update processes
        self.processes = processes

    def save_result_simulation(self):
        """
        Save result of simulation to results/algorithmname.csv
        :param max_arrival_time: maximum number for random number of arrival time
        :param path: path to save csv file
        :param size: number of processes
        :return: bool
        """
        if not self.cpu_total_time > 0:
            print("you have to run an algorithm then save it")
            return

        self.processes.sort(key=lambda p: p.pid)
        data = []
        columns = [
            # process information
            'pid', 'arrival_time', 'priority', 'burst_time', 'waiting_time', 'turnaround_time', 'response_time',
            'start_time', 'end_time',
            # simulation information
            'total_process', 'run_time', 'cpu_total_time', 'cpu_run_time', 'cpu_utilization', 'throughput',
            'average_waiting_time', 'average_turnaround_time', 'average_response_time'
        ]
        # save data as lists of lists then create dataframe. e.g [ [pid1, arrival1], [pid2, arrival2]]
        for process in self.processes:
            data.append([
                process.pid,
                process.arrival_time,
                process.priority,
                process.burst_time,
                process.waiting_time,
                process.turnaround_time,
                process.response_time,
                process.start_time,
                process.end_time,
            ])

        # insert simulation information to just first row
        data[0].extend([
            self.total_process,
            self.run_time,
            self.cpu_total_time,
            self.cpu_run_time,
            self.cpu_utilization,
            self.throughput,
            self.average_waiting_time,
            self.average_turnaround_time,
            self.average_response_time
        ])
        df = pd.DataFrame(data, columns=columns)

        # handle path in linux and windows
        folder_path = Path("results/")
        df.to_csv(path_or_buf=folder_path / f"{self.algorithm}.csv", index=False)

        return True

    def analyze_algorithms(self):

        # handle path in linux and windows
        folder_path = Path("results/")
        columns = [
            'average_waiting_time', 'average_turnaround_time', 'average_response_time'
        ]
        algorithms_data = []
        exists_algorithms = []
        for algo in self.algorithms_list:
            try:
                df = pd.read_csv(folder_path / f"{algo}.csv", usecols=columns)
                algorithms_data.append([
                    algo,  # algorithm name
                    df['average_waiting_time'][0],
                    df['average_turnaround_time'][0],
                    df['average_response_time'][0],
                ])
                exists_algorithms.append(algo)
            except FileNotFoundError:
                pass

        if algorithms_data is None:
            raise Exception("You have to run one algorithm at least")

        columns.insert(0, 'name')
        algorithms_df = pd.DataFrame(algorithms_data, columns=columns, index=exists_algorithms)
        algorithms_df.to_csv(folder_path / "results.csv", index=False)

        # create subpot
        subplot = algorithms_df.plot(kind='bar')
        subplot.set_xlabel('algorithm name')
        subplot.set_ylabel('Time')
        subplot.set_title(f"analyze algorithms")
        subplot.legend()

        subplot.figure.set_size_inches(20.5, 15.5)
        # Set the figure title
        subplot.figure.canvas.manager.set_window_title(f"analyze {self.total_process} algorithms")
        subplot.figure.show()
        subplot.figure.savefig(folder_path / "result")

    def plot_algorithm_result(self):

        # handle path in linux and windows
        folder_path = Path("results/")
        try:
            df = pd.read_csv(folder_path / f"{self.algorithm}.csv")
        except FileNotFoundError:
            raise Exception("You should have run algorithm first")

        # set data
        try:
            self.run_time = df['run_time'][0]
            self.cpu_total_time = df['cpu_total_time'][0]
            self.cpu_run_time = df['cpu_run_time'][0]
            self.cpu_utilization = df['cpu_utilization'][0]
            self.throughput = df['throughput'][0]
            self.average_waiting_time = df['average_waiting_time'][0]
            self.average_turnaround_time = df['average_turnaround_time'][0]
            self.average_response_time = df['average_response_time'][0]
        except:
            pass

        df = df[['burst_time', 'arrival_time', 'priority', 'waiting_time', 'turnaround_time', 'response_time']]

        # get compressed dataframe for showing in chart
        # subplot settings based on priority
        priority_df = self._compress_df_rows(df, 'priority')
        priority_subplot = priority_df.plot(
            kind='bar',
            xlabel="Priority",
            ylabel="Time",
            title=self.algorithm
        )
        priority_subplot.figure.set_size_inches(30, 15)

        # subplot settings based on burst time
        burst_df = self._compress_df_rows(df, 'burst_time')
        burst_subplot = burst_df.plot(
            kind='bar',
            xlabel="Burst time",
            ylabel="Time",
            title=self.algorithm
        )
        burst_subplot.figure.set_size_inches(30, 15)

        # subplot settings based on arrival time
        arrival_df = self._compress_df_rows(df, 'arrival_time')
        arrival_subplot = arrival_df.plot(
            kind='bar',
            xlabel="Arrival time",
            ylabel="Time",
            title=self.algorithm
        )
        arrival_subplot.figure.set_size_inches(30, 15)

        fig_text = (
                'Simulation time: %.10f s\n'
                'CPU total time: %.0f s\n'
                'CPU run time: %.0f s\n'
                'CPU utilization: %.6f%%\n'
                'Throughput: %.6f\n'
                'Average waiting time: %.2f\n'
                'Average turnaround time: %.2f\n'
                'Average response time: %.2f' % (
                    self.run_time,
                    self.cpu_total_time,
                    self.cpu_run_time,
                    (self.cpu_utilization * 100),
                    self.throughput,
                    self.average_waiting_time,
                    self.average_turnaround_time,
                    self.average_response_time
                )
        )

        # add text to subplots
        priority_subplot.figure.text(
            0.5, 0.25,
            fig_text,
            bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 50}
        )
        burst_subplot.figure.text(
            0.5, 0.25,
            fig_text,
            bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 50}
        )
        arrival_subplot.figure.text(
            0.5, 0.25,
            fig_text,
            bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 50}
        )

        # show and save
        priority_subplot.figure.show()
        burst_subplot.figure.show()
        arrival_subplot.figure.show()

        # handle path in linux and windows
        fig_folder_path = Path("results/charts/")
        priority_subplot.figure.savefig(fig_folder_path / f'priority_{self.algorithm}')
        burst_subplot.figure.savefig(fig_folder_path / f'burst_{self.algorithm}')
        arrival_subplot.figure.savefig(fig_folder_path / f'arrival_{self.algorithm}')

    def json_export(self):
        return {
            "total_process": self.total_process,
            "run_time": self.run_time,
            "cpu_total_time": self.cpu_total_time,
            "cpu_run_time": self.cpu_run_time,
            "throughput": self.throughput,
            "cpu_utilization": self.cpu_utilization,
            "average_waiting_time": self.average_waiting_time,
            "average_turnaround_time": self.average_turnaround_time,
            "average_response_time": self.average_response_time
        }

    def __str__(self):
        return (
            'Total processes: %.0f \n'
            'Simulation time: %.10f s\n'
            'CPU total time: %.0f \n'
            'CPU run time: %.0f s\n'
            'CPU utilization: %.6f%%\n'
            'Throughput: %.6f\n'
            'Average waiting time: %.2f\n'
            'Average turnaround time: %.2f\n'
            'Average response time: %.2f' % (
                self.total_process,
                self.run_time,
                self.cpu_total_time,
                self.cpu_run_time,
                (self.cpu_utilization * 100),
                self.throughput,
                self.average_waiting_time,
                self.average_turnaround_time,
                self.average_response_time
            )
        )


if __name__ == '__main__':
    simulation_graphic.run()

import random
import time
import algorithms  # local module
import pandas as pd
from graphics import graphic as simulation_graphic

from process import Process
from pathlib import Path


def get_cpu_time_unit():
    """
    a unit of time independent on cpu run this code for simulating time
    """

    started_at = time.time()
    for i in range(1, 10000):
        if i % 2 == 0:
            temp = i / 2
        else:
            temp = 2 * i

    ended_at = time.time()
    return ended_at - started_at


class Simulator:

    def __init__(self, algorithm: str):
        """
        run_time attr is for running algorithm in second
        cpu_total_time is total time of running processes for cpu. The number doesn't have any unit
        cpu_run_time attr total time of running processes for cpu. unit of time depend on get_cpu_time_unit function

        :param algorithm: algorithm name that valid in algorithm list
        """
        self.algorithm = algorithm
        self.algorithm_class = self.get_algorithm_class()
        self.algorithms_list = ["FCFS", "NonPreemptiveSFJ", "PreemptiveSFJ", "RR",
                                "NonPreemptivePriority", "PreemptivePriority"]
        self.processes = []
        self.total_process = 0
        self.run_time = 0
        self.cpu_run_time = 0
        self.cpu_total_time = 0
        self.cpu_utilization = 0
        self.throughput = 0
        self.average_waiting_time = 0.0
        self.average_turnaround_time = 0.0
        self.average_response_time = 0.0

    def _compress_df_rows(self, df: pd.DataFrame, column: str) -> pd.DataFrame:
        """
        you can compress your dataframe based on your column name.
        for example you have 1000 rows that their priority column have value of 0
        so you average values of all other columns and compress 1000 rows to one
        :param df: dataframe which you want to compress same column values
        :param column: the column that you want to compress values
        :return: compressed dataframe
        """
        # raise error if column not in compress columns
        if column not in ('priority', 'burst_time', 'arrival_time'):
            raise KeyError("Your column not valid")

        df = df.sort_values(column, ignore_index=True)
        unique_columns = list(df[column].unique())

        data = []
        # compress column based on column name
        if column == "priority":
            # mean over each priority
            for c in unique_columns:
                result = df.where(df[column] == c).mean(numeric_only=True)
                data.append([
                    result['waiting_time'],
                    result['turnaround_time'],
                    result['response_time']
                ])
            # create dataframe
            compress_df = pd.DataFrame(
                data,
                columns=['waiting_time', 'turnaround_time', 'response_time'],
                index=unique_columns)

        elif column == "burst_time":

            # divide variety of the column to 20 part and create range of numbers instead of a number
            pieces = 20 if len(unique_columns) > 20 else 1
            batches = len(unique_columns) // pieces
            index_ranges = []

            for c in range(pieces + 1):
                low_range = batches * c
                high_range = batches * (c + 1)
                # mean over the ranges
                result = df.where((low_range <= df[column]) & (df[column] < high_range)).mean(numeric_only=True)
                data.append([
                    result['waiting_time'],
                    result['turnaround_time'],
                    result['response_time']
                ])

                # create index ranges for creating index of dataframe
                index_ranges.append(f"{low_range}-{high_range}")

            # create dataframe
            compress_df = pd.DataFrame(
                data,
                columns=['waiting_time', 'turnaround_time', 'response_time'],
                index=index_ranges)

        elif column == "arrival_time":
            # divide variety of the column to 20 part and create range of numbers instead of a number
            pieces = 20 if len(unique_columns) > 20 else 1
            batches = len(unique_columns) // pieces
            index_ranges = []

            for c in range(pieces + 1):
                low_range = batches * c
                high_range = batches * (c + 1)
                # mean over the ranges
                result = df.where((low_range <= df[column]) & (df[column] < high_range)).mean(numeric_only=True)
                data.append([
                    result['waiting_time'],
                    result['turnaround_time'],
                    result['response_time']
                ])

                # create index ranges for creating index of dataframe
                index_ranges.append(f"{low_range}-{high_range}")

            # create dataframe
            compress_df = pd.DataFrame(
                data,
                columns=['waiting_time', 'turnaround_time', 'response_time'],
                index=index_ranges)

        return compress_df

    def set_algorithm(self, algorithm: str) -> bool:
        """
        changing algorithm and validate algorithm
        :param algorithm: new algorithm name
        :return: True if has changed
        """
        self.algorithm = algorithm
        self.algorithm_class = self.get_algorithm_class()
        return True

    @staticmethod
    def generate_processes_data(
            path: str = 'data.csv', size: int = 1000, max_arrival_time: int = 100, max_priority=10, max_burst_time=40
    ) -> bool:
        """
        Generate process with random numbers for arrival and burst time and priority. then save it to a csv file
        :param max_burst_time: maximum number for random number of burst time
        :param max_priority: maximum number for random number of priority
        :param max_arrival_time: maximum number for random number of arrival time
        :param path: path to save csv file
        :param size: number of processes
        :return: bool
        """
        data = []
        # save data as lists of lists then create dataframe. e.g [ [pid1, arrival1], [pid2, arrival2]]
        for i in range(size):
            data.append([
                i + 1,  # PID
                random.randint(0, max_arrival_time),  # arrival_time
                random.randint(0, max_priority),  # priority
                random.randint(0, max_burst_time),  # burst_time
            ])

        df = pd.DataFrame(data, columns=['pid', 'arrival_time', 'priority', 'burst_time'])
        df.to_csv(path_or_buf=path, index=False)

        return True

    def read_processes_data(self, path='data.csv', dataframe=None) -> bool:
        """
        read data from csv file or pandas dataframe
        one of path or dataframe parms needed
        :param path: string path of your csv file
        :param dataframe: you can pass dataframe object
        :return: a true boolean if everythings goes right
        """
        if isinstance(dataframe, pd.DataFrame):
            df = dataframe
        elif path and path.split('.')[-1] == 'csv':
            df = pd.read_csv(path)
        else:
            raise Exception("Your file should be a csv format or pass dataframe object to function")

        # clear last loaded processes
        self.processes.clear()

        for i in range(0, df['pid'].count()):
            process = Process(
                pid=df['pid'][i],
                arrival_time=df['arrival_time'][i],
                priority=df['priority'][i],
                burst_time=df['burst_time'][i]
            )
            self.processes.append(process)

        return True

    def get_algorithm_class(self):
        """
        find algorithm class based on algorithm folder
        :return a class
        """
        try:
            return getattr(algorithms, self.algorithm)
        except Exception as e:
            print(e)
            raise Exception("try again! you have to enter a valid algorithm")

    def run(self):
        """
        Simulate algorithm and save the result of it
        """

        if len(self.processes) == 0:
            raise Exception("you have to load processes")

        # algorithm instance. need process list object
        algorithm = self.algorithm_class(self.processes)

        start_time = time.time()
        # run algorithm
        result = algorithm.run()
        end_time = time.time()
        self.run_time = end_time - start_time

        # set result of simulation
        # this processes referred to executed processes in output of algorithm run
        processes = result['executed_processes']
        total_process = len(processes)
        cpu_total_time = result['cpu_total_time']
        cpu_run_time = cpu_total_time * get_cpu_time_unit()
        throughput = total_process / cpu_total_time
        cpu_utilization = (cpu_total_time - result['cpu_idle_time']) / cpu_total_time

        average_waiting_time = sum(process.waiting_time for process in processes) / total_process
        average_turnaround_time = sum(process.turnaround_time for process in processes) / total_process
        average_response_time = sum(process.response_time for process in processes) / total_process

        self.total_process = total_process
        self.cpu_total_time = cpu_total_time
        self.cpu_run_time = cpu_run_time
        self.throughput = throughput
        self.cpu_utilization = cpu_utilization
        self.average_waiting_time = average_waiting_time
        self.average_turnaround_time = average_turnaround_time
        self.average_response_time = average_response_time
        # update processes
        self.processes = processes

    def save_result_simulation(self):
        """
        Save result of simulation to results/algorithmname.csv
        :param max_arrival_time: maximum number for random number of arrival time
        :param path: path to save csv file
        :param size: number of processes
        :return: bool
        """
        if not self.cpu_total_time > 0:
            print("you have to run an algorithm then save it")
            return

        self.processes.sort(key=lambda p: p.pid)
        data = []
        columns = [
            # process information
            'pid', 'arrival_time', 'priority', 'burst_time', 'waiting_time', 'turnaround_time', 'response_time',
            'start_time', 'end_time',
            # simulation information
            'total_process', 'run_time', 'cpu_total_time', 'cpu_run_time', 'cpu_utilization', 'throughput',
            'average_waiting_time', 'average_turnaround_time', 'average_response_time'
        ]
        # save data as lists of lists then create dataframe. e.g [ [pid1, arrival1], [pid2, arrival2]]
        for process in self.processes:
            data.append([
                process.pid,
                process.arrival_time,
                process.priority,
                process.burst_time,
                process.waiting_time,
                process.turnaround_time,
                process.response_time,
                process.start_time,
                process.end_time,
            ])

        # insert simulation information to just first row
        data[0].extend([
            self.total_process,
            self.run_time,
            self.cpu_total_time,
            self.cpu_run_time,
            self.cpu_utilization,
            self.throughput,
            self.average_waiting_time,
            self.average_turnaround_time,
            self.average_response_time
        ])
        df = pd.DataFrame(data, columns=columns)

        # handle path in linux and windows
        folder_path = Path("results/")
        df.to_csv(path_or_buf=folder_path / f"{self.algorithm}.csv", index=False)

        return True

    def analyze_algorithms(self):

        # handle path in linux and windows
        folder_path = Path("results/")
        columns = [
            'average_waiting_time', 'average_turnaround_time', 'average_response_time'
        ]
        algorithms_data = []
        exists_algorithms = []
        for algo in self.algorithms_list:
            try:
                df = pd.read_csv(folder_path / f"{algo}.csv", usecols=columns)
                algorithms_data.append([
                    algo,  # algorithm name
                    df['average_waiting_time'][0],
                    df['average_turnaround_time'][0],
                    df['average_response_time'][0],
                ])
                exists_algorithms.append(algo)
            except FileNotFoundError:
                pass

        if algorithms_data is None:
            raise Exception("You have to run one algorithm at least")

        columns.insert(0, 'name')
        algorithms_df = pd.DataFrame(algorithms_data, columns=columns, index=exists_algorithms)
        algorithms_df.to_csv(folder_path / "results.csv", index=False)

        # create subpot
        subplot = algorithms_df.plot(kind='bar')
        subplot.set_xlabel('algorithm name')
        subplot.set_ylabel('Time')
        subplot.set_title(f"analyze algorithms")
        subplot.legend()

        subplot.figure.set_size_inches(20.5, 15.5)
        # Set the figure title
        subplot.figure.canvas.manager.set_window_title(f"analyze {self.total_process} algorithms")
        subplot.figure.show()
        subplot.figure.savefig(folder_path / "result")

    def plot_algorithm_result(self):

        # handle path in linux and windows
        folder_path = Path("results/")
        try:
            df = pd.read_csv(folder_path / f"{self.algorithm}.csv")
        except FileNotFoundError:
            raise Exception("You should have run algorithm first")

        # set data
        try:
            self.run_time = df['run_time'][0]
            self.cpu_total_time = df['cpu_total_time'][0]
            self.cpu_run_time = df['cpu_run_time'][0]
            self.cpu_utilization = df['cpu_utilization'][0]
            self.throughput = df['throughput'][0]
            self.average_waiting_time = df['average_waiting_time'][0]
            self.average_turnaround_time = df['average_turnaround_time'][0]
            self.average_response_time = df['average_response_time'][0]
        except:
            pass

        df = df[['burst_time', 'arrival_time', 'priority', 'waiting_time', 'turnaround_time', 'response_time']]

        # get compressed dataframe for showing in chart
        # subplot settings based on priority
        priority_df = self._compress_df_rows(df, 'priority')
        priority_subplot = priority_df.plot(
            kind='bar',
            xlabel="Priority",
            ylabel="Time",
            title=self.algorithm
        )
        priority_subplot.figure.set_size_inches(30, 15)

        # subplot settings based on burst time
        burst_df = self._compress_df_rows(df, 'burst_time')
        burst_subplot = burst_df.plot(
            kind='bar',
            xlabel="Burst time",
            ylabel="Time",
            title=self.algorithm
        )
        burst_subplot.figure.set_size_inches(30, 15)

        # subplot settings based on arrival time
        arrival_df = self._compress_df_rows(df, 'arrival_time')
        arrival_subplot = arrival_df.plot(
            kind='bar',
            xlabel="Arrival time",
            ylabel="Time",
            title=self.algorithm
        )
        arrival_subplot.figure.set_size_inches(30, 15)

        fig_text = (
                'Simulation time: %.10f s\n'
                'CPU total time: %.0f s\n'
                'CPU run time: %.0f s\n'
                'CPU utilization: %.6f%%\n'
                'Throughput: %.6f\n'
                'Average waiting time: %.2f\n'
                'Average turnaround time: %.2f\n'
                'Average response time: %.2f' % (
                    self.run_time,
                    self.cpu_total_time,
                    self.cpu_run_time,
                    (self.cpu_utilization * 100),
                    self.throughput,
                    self.average_waiting_time,
                    self.average_turnaround_time,
                    self.average_response_time
                )
        )

        # add text to subplots
        priority_subplot.figure.text(
            0.5, 0.25,
            fig_text,
            bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 50}
        )
        burst_subplot.figure.text(
            0.5, 0.25,
            fig_text,
            bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 50}
        )
        arrival_subplot.figure.text(
            0.5, 0.25,
            fig_text,
            bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 50}
        )

        # show and save
        priority_subplot.figure.show()
        burst_subplot.figure.show()
        arrival_subplot.figure.show()

        # handle path in linux and windows
        fig_folder_path = Path("results/charts/")
        priority_subplot.figure.savefig(fig_folder_path / f'priority_{self.algorithm}')
        burst_subplot.figure.savefig(fig_folder_path / f'burst_{self.algorithm}')
        arrival_subplot.figure.savefig(fig_folder_path / f'arrival_{self.algorithm}')

    def json_export(self):
        return {
            "total_process": self.total_process,
            "run_time": self.run_time,
            "cpu_total_time": self.cpu_total_time,
            "cpu_run_time": self.cpu_run_time,
            "throughput": self.throughput,
            "cpu_utilization": self.cpu_utilization,
            "average_waiting_time": self.average_waiting_time,
            "average_turnaround_time": self.average_turnaround_time,
            "average_response_time": self.average_response_time
        }

    def __str__(self):
        return (
            'Total processes: %.0f \n'
            'Simulation time: %.10f s\n'
            'CPU total time: %.0f \n'
            'CPU run time: %.0f s\n'
            'CPU utilization: %.6f%%\n'
            'Throughput: %.6f\n'
            'Average waiting time: %.2f\n'
            'Average turnaround time: %.2f\n'
            'Average response time: %.2f' % (
                self.total_process,
                self.run_time,
                self.cpu_total_time,
                self.cpu_run_time,
                (self.cpu_utilization * 100),
                self.throughput,
                self.average_waiting_time,
                self.average_turnaround_time,
                self.average_response_time
            )
        )


if __name__ == '__main__':
    simulation_graphic.run()

# local module a unit of time independent on cpu run this code for simulating time run_time attr is for running algorithm in second         cpu_total_time is total time of running processes for cpu. The number doesn't have any unit         cpu_run_time attr total time of running processes for cpu. unit of time depend on get_cpu_time_unit function          :param algorithm: algorithm name that valid in algorithm list you can compress your dataframe based on your column name.         for example you have 1000 rows that their priority column have value of 0         so you average values of all other columns and compress 1000 rows to one         :param df: dataframe which you want to compress same column values         :param column: the column that you want to compress values         :return: compressed dataframe # raise error if column not in compress columns # compress column based on column name # mean over each priority # create dataframe # divide variety of the column to 20 part and create range of numbers instead of a number # mean over the ranges # create index ranges for creating index of dataframe # create dataframe # divide variety of the column to 20 part and create range of numbers instead of a number # mean over the ranges # create index ranges for creating index of dataframe # create dataframe changing algorithm and validate algorithm         :param algorithm: new algorithm name         :return: True if has changed Generate process with random numbers for arrival and burst time and priority. then save it to a csv file         :param max_burst_time: maximum number for random number of burst time         :param max_priority: maximum number for random number of priority         :param max_arrival_time: maximum number for random number of arrival time         :param path: path to save csv file         :param size: number of processes         :return: bool # save data as lists of lists then create dataframe. e.g [ [pid1, arrival1], [pid2, arrival2]] # PID # arrival_time # priority # burst_time read data from csv file or pandas dataframe         one of path or dataframe parms needed         :param path: string path of your csv file         :param dataframe: you can pass dataframe object         :return: a true boolean if everythings goes right # clear last loaded processes find algorithm class based on algorithm folder         :return a class Simulate algorithm and save the result of it # algorithm instance. need process list object # run algorithm # set result of simulation # this processes referred to executed processes in output of algorithm run # update processes Save result of simulation to results/algorithmname.csv         :param max_arrival_time: maximum number for random number of arrival time         :param path: path to save csv file         :param size: number of processes         :return: bool # process information # simulation information # save data as lists of lists then create dataframe. e.g [ [pid1, arrival1], [pid2, arrival2]] # insert simulation information to just first row # handle path in linux and windows # handle path in linux and windows # algorithm name # create subpot # Set the figure title # handle path in linux and windows # set data # get compressed dataframe for showing in chart # subplot settings based on priority # subplot settings based on burst time # subplot settings based on arrival time # add text to subplots # show and save # handle path in linux and windows