import random
import copy as c
import math
import os

class Grid:
    def __init__(self):
        # self.row* is template or each row
        self.row = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        # self.grid* is the grid that is edited
        self.grid = [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ]
        # self.repeat_all* is counter for the times while loop is repeated
        self.repeat_all = 0
        # self.hint_grid* is copy of self.grid* that is not solved yet
        self.hint_grid = c.deepcopy(self.grid)

    def get_pos(self, grid, coord):
        return self.get_row(grid, coord[1])[coord[0]]
        # returns the number located at the entered coord* in the entered grid*

    def get_row(self, grid, row: int):
        return c.deepcopy(grid[row])
        # returns a row of numbs from entered grid*
        # parameter row* is an int from 0-8 with 0 at top of grid and 8 at bottom

    def get_col(self, grid, col: int):
        column_data = []
        for row_g in grid:
            column_data.append(c.deepcopy(row_g[col]))
        return c.deepcopy(column_data)
        # returns a column of numbs from grid*
        # parameter col* is an int from 0-8 with 0 as left most col and 8 as right most col

    def get_box(self, grid, box_coord):
        # Get Y row of coord
        if box_coord[1] == 0:
            box_row = [self.get_row(grid, 0), self.get_row(grid, 1), self.get_row(grid, 2)]
        elif box_coord[1] == 1:
            box_row = [self.get_row(grid, 3), self.get_row(grid, 4), self.get_row(grid, 5)]
        elif box_coord[1] == 2:
            box_row = [self.get_row(grid, 6), self.get_row(grid, 7), self.get_row(grid, 8)]
        else:
            raise ValueError
        # sets box_row as one of the three big rows

        # Get x column of y row
        if box_coord[0] == 0:
            box = [box_row[0][:3], box_row[1][:3], box_row[2][:3]]
        elif box_coord[0] == 1:
            box = [box_row[0][3:6], box_row[1][3:6], box_row[2][3:6]]
        elif box_coord[0] == 2:
            box = [box_row[0][6:], box_row[1][6:], box_row[2][6:]]
        else:
            raise ValueError
        # filters box_row to parameter x coord

        return box
        # returns list of 3 lists

    def get_box_id(self, coord):
        x_box = math.floor(coord[0] / 3)
        y_box = math.floor(coord[1] / 3)
        return (x_box, y_box)
        # returns coord of for getting box (0-2, 0-2)

    def check_list(self, data: list):
        # removes all 0s from list
        if 0 in data:
            for i in range(len(data)):
                try:
                    data.remove(0)
                except ValueError:
                    break

        # Checks any duplicates
        for i in range(len(data)):
            for j in range(i + 1, len(data)):
                if data[i] == data[j]:
                    return True  # if duplicate RETURNS TRUE
        return False  # if list passes check RETURNS FALSE

    def check_row(self, grid, row: int):
        # checks is data of row at parameter row* (int) is valid
        data = self.get_row(grid, row)
        if self.check_list(data):
            return True  # IF duplicate RETURNS TRUE
        return False  # if pass check RETURNS FALSE

    def check_col(self, grid, col: int):
        # checks is data of col at parameter col* (int) is valid
        data = self.get_col(grid, col)
        if self.check_list(data):
            return True  # IF duplicate RETURNS TRUE
        return False  # if pass check RETURNS FALSE

    def check_pos(self, grid, coord, numb):  # if pos bad ret TRUE
        # checks row, col, box of parameter coord* with parameter numb* in grid*
        # check row
        row_c = c.deepcopy(self.get_row(grid, coord[1]))
        row_c[coord[0]] = numb
        if self.check_list(row_c):
            return True  # IF duplicate RETURNS TRUE

        # check col
        col_c = c.deepcopy(self.get_col(grid, coord[0]))
        col_c[coord[1]] = numb
        if self.check_list(col_c):
            return True  # IF duplicate RETURNS TRUE

        # check box
        box_c = self.get_box(grid, self.get_box_id(coord))
        flat_list_box = [item for sublist in box_c for item in sublist]
        if numb in flat_list_box:
            return True  # IF duplicate RETURNS TRUE

        return False  # if pass check RETURNS FALSE

    def edit_row(self, grid, row_numb, row_data):
        # edits row indexed with parameter row_numb* and replaces with parameter row_data*
        grid[row_numb] = c.deepcopy(row_data)
        return grid
        # returns edited grid

    def edit_pos(self, grid, coord, numb):
        # edits param grid* with param numb* at param coord*
        grid[coord[1]][coord[0]] = numb
        return grid
        # returns edited grid

    def flat_list(self, list_of_lists):
        # takes in a list of multiple lists
        return [item for sublist in list_of_lists for item in sublist]
        # returns a single list with all of the same elements

    def find_missing_numb(self, data):
        temp = []
        for i in range(1, 10):
            if i not in data:
                temp.append(i)
        return temp
        # return numbs not in list of data* from 1-9

    def remove_duplicates_zero(self, data):
        temp = []
        for element in data:
            if element in temp or element == 0:
                continue
            else:
                temp.append(element)
        return temp

    def reset_last_row(self, grid):
        for i, row in enumerate(grid):
            if 0 in row:
                last_row = i - 1
                break
        self.edit_row(grid, last_row, [0, 0, 0, 0, 0, 0, 0, 0, 0])
        return last_row
        # resets last row that is full with 0's

    def when_row_is_8(self):
        # change to this when row itter is 7 (one left)
        for col_numb in range(9):
            col_data = self.get_col(self.grid, col_numb)
            missing_ans = self.find_missing_numb(col_data)
            self.edit_pos(self.grid, (col_numb, 8), missing_ans[0])
        # finds the last numb that is not in col and adds to last row

    def ask_first_row(self):
        while True:
            os.system('cls')
            number_valid = True
            row = []
            for i in range(9):
                try:
                    numb = int(input("Enter the order of numbers for the first row.\nWhat do you want the "+str(i+1)+" number to be?:\n"))
                except ValueError:
                    print("Enter a intiger!")
                    number_valid = False
                    break
                if not 0 < int(numb) <= 9:
                    number_valid = False
                    break
                row.append(numb)
            if self.check_list(row) or not number_valid:
                print("One or more of the entered numbers was incorrect!")
                continue
            else:
                print("Good")
                break
        return c.deepcopy(row)

    def when_row_is_7(self):
        #chenge to this when row itter is 6 (two row left)
        def check(possib_dict):
            # checks if combination of 9 cols of 2 numbs works for last 2 rows
            copy_grid = c.deepcopy(self.grid)
            for key, val in possib_dict.items():
                for i, numb in enumerate(val):
                    self.edit_pos(copy_grid, (key, 7 + i), numb)
            if not self.check_row(copy_grid, 8):
                self.grid = c.deepcopy(copy_grid)
                return False
            return True

        def flip(possib_dict, key):
            # flips one of the sets of numbs in possib_dict* at position key*
            possib_dict[key] = [possib_dict[key][-1], possib_dict[key][0]]
            return possib_dict[key], possib_dict

        def func(length):
            # Algorithm that gets all possible combinations for possib_soluction (dict)
            for i in range(2):
                # flips last element in dict
                flip(possib_solution, length)
                if not check(possib_solution):
                    return 1
                    # QUIT **********************
                if length != 0:
                    func(length - 1)

        possib_solution = {}
        for col_numb in range(9):
            possib_solution[col_numb] = self.find_missing_numb(
                self.get_col(self.grid, col_numb))  # create possib_solution with key as col numb
        # returns a good combination of possib_solution
        return func(8)

    def add_user_row_to_grid(self):
        print("DOING")
        self.grid[0] = self.ask_first_row()

    def fill_grid(self):
        row_index = 1
        repeat = 0
        while True:
            shuffle_row = c.deepcopy(self.row)
            random.shuffle(shuffle_row)
            row_work = True
            past = []
            if shuffle_row in past:
                repeat += 1
                continue
            else:
                past.append(c.deepcopy(shuffle_row))
            for i, element in enumerate(shuffle_row):
                if not self.check_pos(self.grid, (i, row_index), element):
                    continue
                else:
                    row_work = False
                    break
            if row_work:
                self.edit_row(self.grid, row_index, shuffle_row)
                repeat = 0
                row_index += 1

            # if single row is taking more than 50000 loops return 0 to end func then delete prev row
            if repeat > 50000:
                return 0

            repeat += 1
            self.repeat_all += 1
            # stop randomizing row and begin alg for last 2 rows
            if row_index == 7:
                return self.when_row_is_7()
                # return self.grid

    def user_grid(self, hints=20):
        # min hints for 1 solution is 17
        self.hint_grid = c.deepcopy(self.grid)

        if hints < 18:
            hints = 18

        amount_zeros = 81 - hints
        zer_per_box_min, zer_per_box_max = int(amount_zeros/9)-1, int(amount_zeros/9)+1

        for col in range(3):
            for row in range(3):
                zer_box = random.randint(zer_per_box_min, zer_per_box_max)
                for i in range(zer_box):
                    while True:
                        x, y = random.randint(0, 2), random.randint(0, 2)
                        coord_x = col*3 + x
                        coord_y = row*3 + y
                        if self.get_pos(self.hint_grid, (coord_x, coord_y)) == " ":
                            continue
                        else:
                            break
                    self.edit_pos(self.hint_grid, (coord_x, coord_y), " ")
        for col in range(9):
            numb_count = 0
            for numb in self.get_col(self.hint_grid, col):
                if numb != " ":
                    numb_count+=1
            while numb_count<5 or numb_count>6:
                row = random.randint(0, 8)
                if numb_count<5:
                    if self.get_pos(self.hint_grid, (col, row)) == " ":
                        self.edit_pos(self.hint_grid, (col, row), self.get_pos(self.grid, (col, row)))
                        numb_count+=1
                    else:
                        continue
                elif numb_count>6:
                    if self.get_pos(self.hint_grid, (col, row)) != " ":
                        self.edit_pos(self.hint_grid, (col, row), " ")
                        numb_count-=1
                    else:
                        continue

        for row in range(9):
            numb_count = 0
            for numb in self.get_row(self.hint_grid, row):
                if numb != " ":
                    numb_count+=1
            while numb_count<5 or numb_count>6:
                col = random.randint(0, 8)
                if numb_count<5:
                    if self.get_pos(self.hint_grid, (col, row)) == " ":
                        self.edit_pos(self.hint_grid, (col, row), self.get_pos(self.grid, (col, row)))
                        numb_count+=1
                    else:
                        continue
                elif numb_count>6:
                    if self.get_pos(self.hint_grid, (col, row)) != " ":
                        self.edit_pos(self.hint_grid, (col, row), " ")
                        numb_count-=1
                    else:
                        continue

        for box_col in range(3):
            for box_row in range(3):
                numb_count = 0
                for box in self.flat_list(self.get_box(self.hint_grid, (box_col, box_row))):
                    if box != " ":
                        numb_count+=1
                while numb_count < 3 or numb_count > 5:
                    x = random.randint(0, 2)
                    y = random.randint(0, 2)
                    if numb_count < 3:
                        if self.get_pos(self.hint_grid, (box_col*3+x, box_row*3+y)) == " ":
                            self.edit_pos(self.hint_grid, (box_col*3+x, box_row*3+y), self.get_pos(self.grid, (box_col*3+x, box_row*3+y)))
                            numb_count+=1
                        else:
                            continue
                    elif numb_count>5:
                        if self.get_pos(self.hint_grid, (box_col*3+x, box_row*3+y)) != " ":
                            self.edit_pos(self.hint_grid, (box_col*3+x, box_row*3+y), " ")
                            numb_count-=1
                        else:
                            continue

        # for i in range(amount_zeros):
        #     x, y = random.randint(0, 8), random.randint(0, 8)
        #     self.edit_pos(self.hint_grid, (x, y), " ")
        return self.hint_grid

if __name__ == "__main__":
    g = Grid()
    bad_row = 0
    g.add_user_row_to_grid()
    while True:
        if g.fill_grid(bad_row) == 0:
            bad_row = g.reset_last_row(g.grid)
            continue
        else:
            break
    print("COMPLETE")
    for row in g.grid:
        print(row)


import shutil
import subprocess
import sys

from pathlib import Path
from typing import Dict, IO, List, Tuple

def main() -> int:
    # Parse command line arguments:
    args: List[str, ] = sys.argv[1:]
    # print(f"args={args}")
    if len(args) == 0:
        print(f"Usage: serial_agent.py [WORKSPACE_DIR] [PACKAGE_NAME]")

    workspace_directory: Path = Path("/tmp/dev_ws") if len(args) <= 1 else Path(args[0])
    package_name: str = "serial_agent" if len(args) <= 2 else args[1]
    # print(f"workspace_directory='{workspace_directory}' package_name='{package_name}'")

    # Create the various directories:
    ros2_package_directory: Path = workspace_directory / "src" / package_name
    python_package_directory: Path = ros2_package_directory / package_name
    python_package_directory.mkdir(parents=True, exist_ok=True)

    # Write out the `package.xml` and `__init__.py` files:
    package_xml_create(ros2_package_directory, package_name)
    init_py_create(python_package_directory)
    setup_cfg_create(ros2_package_directory, package_name)
    setup_py_create(ros2_package_directory, package_name)
    resource_create(ros2_package_directory, package_name)

    # Read in
    serial_agent_py_source: Path = Path("serial_agent.py")
    serial_agent_py_destination: Path = python_package_directory / "serial_agent.py"

    if True:
        shutil.copyfile(serial_agent_py_source, serial_agent_py_destination)
        print(f"{serial_agent_py_source} copied to {serial_agent_py_destination}")
        return 0

    # Temporary testing of SerialAgentGenerator class:
    serial_agent_generator: SerialAgentGenerator = SerialAgentGenerator()
    # Temporary:
    serial_agent_generator.publisher_add("std_msgs.msg", "String", "topic")
    serial_agent_generator.subscription_add("std_msgs.msg", "String", "topic")
    serial_agent_generator.client_add("example_interfaces", "AddTwoInts", "add_two_ints")
    serial_agent_generator.server_add("example_interfaces", "AddTwoInts", "add_two_ints")
    serial_agent_generator.timer_add("add_two_ints", "String", 4.0)
    serial_agent_generator.serial_agent_py_create(ros2_package_directory, package_name)

    return 0

class ImportManager(object):
    """Class to keep track of import statements.

    It is really easy for different packages to conflict on the type names that
    they create.  This package attempts to detect these errors and fail with a
    a semi useful error message.
    """

    # ImportManager.__init__():
    def __init__(self) -> None:
        """Initialize ImportManager"""
        self.all_tags: Tuple[str, ...] = ()  # All allowed tags
        self.tag_to_import_lines: Dict[str, List[str]] = {}  # tag => lines
        self.tag_to_comment_line: Dict[str, str] = {}  # tag => comment line
        self.import_lines_to_tag: Dict[str, str] = {}  # import_line => tag
        self.global_to_import_line: Dict[str, str] = {}  # global_name => import_line

    # ImportManager.tag_create():
    def tag_create(self, tag: str, comment_line: str) -> None:
        """Create an import tag."""
        assert tag not in self.all_tags, "Duplicate tag '{tag}' creation."
        self.all_tags += (tag,)
        self.tag_to_comment_line[tag] = comment_line
        self.tag_to_import_lines[tag] = []

    # ImportManager.insert():
    def insert(self, tag: str, global_name: str, import_line: str, comment: str) -> None:
        if global_name == "AddTwoInts":
            print(f">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> {tag}")
        assert tag in self.all_tags, f"Tag '{tag}' is not one of {self.all_tags}."
        if global_name in self.global_to_import_line:
            # Duplicate global name.  It may be OK:
            previous_import_line: str = self.global_to_import_line[global_name]
            assert import_line  == previous_import_line, (
                f"'{global_name}' is defined by both '{previous_import_line}' and '{import_line}'")
        else:
            if import_line not in self.tag_to_import_lines[tag]:
                self.global_to_import_line[global_name] = import_line
                self.import_lines_to_tag[import_line] = tag
                if comment:
                    import_line += f"  # {comment}"
                self.tag_to_import_lines[tag].append(import_line)

    # ImportManager.simple_import():
    def simple_import(self, tag: str, global_name: str, comment: str = "") -> None:
        """Generate a simple import statement."""
        import_line: str = f"import {global_name}"
        self.insert(tag, global_name, import_line, comment)

    # ImportManager.import_as():
    def import_as(self, tag: str, import_name: str, global_name: str, comment: str = "") -> None:
        """Generate an import/as statement."""
        import_line: str = f"import {import_name} as {global_name}"
        self.insert(tag, global_name, import_line, comment)

    # ImportManager.from_import():
    def from_import(self, tag: str, import_name: str, global_name: str, comment: str = "") -> None:
        """Generate an from/import statement."""
        assert global_name != "topic", f"tag='{tag}"  # Debug only
        import_line: str = f"from {import_name} import {global_name}"
        self.insert(tag, global_name, import_line, comment)

    # ImportManager.from_import_as():
    def from_import_as(self, tag: str, import_name: str,
                       actual_name: str, global_name: str, comment: str = "") -> None:
        """Generate a from/import/as statement."""
        import_line: str = f"from {import_name} import {actual_name} as {global_name}"
        self.insert(tag, global_name, import_line, comment)

    # ImportManager.import_lines_get():
    def import_lines_get(self) -> List[str]:
        """Return a list of all of the imports."""
        # Standard import lines go first:
        import_lines: List[str] = [
            "",
            "# Type hint imports:",
            "from typing import Any, Callable, Dict, List, Optional, Set",
        ]

        # Append the remaining import lines on a tag by tag basis:
        tag: str
        for tag in self.all_tags:
            tag_import_lines: List[str] = self.tag_to_import_lines[tag]
            if tag_import_lines:
                comment_line: str = self.tag_to_comment_line[tag]
                import_lines.extend(["", comment_line] + sorted(self.tag_to_import_lines[tag]))

        return import_lines

    # ImportManager.service_types_import():
    def service_types_import(self, service_package: str, service_type: str) -> Tuple[str, str]:
        """Import the types needed for a service."""

        # Ensure that the service message type is imported.
        # self.from_import("server", service_package, service_type, "type: ignore")

        # Also ensure that associated Request and Response types are imported:
        request_type: str = f"{service_type}.Request"
        response_type: str = f"{service_type}.Response"
        self.from_import("service", f"{service_package}.srv", service_type, "type: ignore")
        return request_type, response_type

# TaggedLines:
class TaggedLines(object):
    """Class for managing the tagged lines."""

    # TaggedLines.__init__():
    def __init__(self) -> None:
        """Initialize the InitManager."""

        self.tags: Tuple[str, ...] = ()
        self.lines: Dict[str, List[str]] = {}
        self.prefix_lines: Dict[str, List[str]] = {}

    # TaggedLines.tag_create():
    def tag_create(self, tag: str, prefix_lines: List[str]) -> None:
        """Create a new tag tagged buffer."""

        assert tag not in self.tags, f"Defining a duplicate tag: '{tag}'"
        self.tags += (tag,)
        self.lines[tag] = []
        self.prefix_lines[tag] = prefix_lines

    # TaggedLine.prefix_set():
    def prefix_set(self, tag: str, prefix_lines: List[str]) -> None:
        """Set the prefix lines."""
        assert tag in self.tags, f"Unknown tag '{tag}'"
        self.prefix_lines[tag] = prefix_lines

        # Force the prefix to show up.
        self.lines[tag].append("")

    # TaggedLines.extend():
    def extend(self, tag: str, lines: List[str]) -> None:
        """Append some lines to a tagged buffer."""
        assert tag in self.tags, f"Unknonw tag '{tag}'"
        self.lines[tag].extend(lines)

    # TaggegLines.lines_get():
    def lines_get(self) -> List[str]:
        """Return the tagged lines as a single list of lines."""
        all_lines: List[str] = []
        tag: str
        for tag in self.tags:
            lines: List[str] = self.lines[tag]
            # Empty lines a the end for rce
            if lines:
                # Empty lines are trimmed.  Thus, a prefix only can be output:
                while lines and lines[0] == "":
                    lines.pop(0)
                all_lines.extend([""] + self.prefix_lines[tag] + lines)
        return all_lines

# SerialAgentGenerator:
class SerialAgentGenerator(object):
    """Class for generating a serial agent."""

    # SerialAgentGenerator.__init__():
    def __init__(self) -> None:
        """Initialize SerialAgentGenerator."""

        # Create all of the import tags for *import_manager*:
        import_manager: ImportManager = ImportManager()
        import_manager.tag_create("required", "# Required imports:")
        import_manager.tag_create("publisher", "# Publisher required imports:")
        import_manager.tag_create("subscription", "# Subscription required imports:")
        import_manager.tag_create("message", "# Message type required imports:")
        import_manager.tag_create("client", "# Client required imports:")
        import_manager.tag_create("server", "# Server required imports:")
        import_manager.tag_create("service", "# Service type required imports:")
        import_manager.tag_create("timer", "# Timer required imports:")
        self.import_manager: ImportManager = import_manager

        # Fill in the requried imports:
        import_manager.simple_import("required", "rclpy", "type: ignore")
        import_manager.from_import_as("required", "rclpy.impl.rcutils_logger",
                                      "RcutilsLogger", "Logger", "type: ignore")
        import_manager.from_import("required", "rclpy.node", "Node", "type: ignore")

        # Various sections of generated code.
        indent: str = " " * 8
        init_tagged_lines: TaggedLines = TaggedLines()
        init_tagged_lines.tag_create("publisher", [])
        init_tagged_lines.tag_create("subscription", [])
        init_tagged_lines.tag_create("client", [])
        init_tagged_lines.tag_create("server", [])
        init_tagged_lines.tag_create("timer", [])

        # This comes last:
        init_tagged_lines.tag_create("server_wait", [])  # Prefix lines filled elsewhere

        self.init_tagged_lines: TaggedLines = init_tagged_lines

        # Callback methods are added to this list:
        callback_tagged_lines: TaggedLines = TaggedLines()
        callback_tagged_lines.tag_create("subscription", [])
        callback_tagged_lines.tag_create("client", [])
        callback_tagged_lines.tag_create("server", [])
        callback_tagged_lines.tag_create("timer", [])
        self.callback_tagged_lines: TaggedLines = callback_tagged_lines

        # Copyright lines for the beginning of the file:
        self.copyright_lines: List[str] = [
            "# Code generated by ros2_serial_manager.py.",
            "# Generated code is not really covered by copyright law.",
        ]

        # Create the main() function:
        dq1: str = "\""
        dq3: str = dq1 * 3
        self.main_lines: List[str] = [
            "",
            "",
            "# main():",
            "def main(args: Optional[List[str]] = None) -> None:",
            "    # Initialize the Python ROS2 interface library.",
            "    rclpy.init(args=args)",
            "",
            "    # Create the serial agent Node:",
            "    serial_agent: SerialAgent = SerialAgent()",
            "",
            "    # Start the serial agent Node.  All activity occurs via callback routines:",
            "    print('Starting rclpy.spin()')",
            "    rclpy.spin(serial_agent)",
            "",
            "    # Destroy the serial agent node explicitly.",
            "    serial_agent.destroy_node()",
            "",
            "    # Shut down the Python ROS2 interface library.",
            "    rclpy.shutdown()",
        ]

        # Serial Agent class lines:
        self.class_header_lines: List[str] = [
            "",
            "",
            "# SerialAgent:",
            "class SerialAgent(Node):",
            f"    {dq3}Serial Agent for managing ROS2 topics and services.{dq3}",
        ]

        self.init_start_lines: List [str] = [
            "",
            "    # SerialAgent.init():",
            "    def __init__(self) -> None:",
            f"        {dq3}Initialize SerialAgent class.{dq3}",
            "",
            "        # Initialize the Node super-class:",
            "        super().__init__('serial_agent')",
        ]

        # The boiler plate code to call main() goes here:
        self.main_call_lines: List[str] = [
            "",
            "",
            "# Call main():",
            "if __name__ == '__main__':",
            "    main(args=None)",
        ]

    # SerialAgent.serial_agent_pyt_create():
    def serial_agent_py_create(self, ros2_package_directory: Path, package_name: str) -> None:
        """Write out `serial_agent.py` file."""
        dq1: str = "\""
        dq3: str = dq1 * 3

        # Joint all of the import lines into *all_import_lines*:
        all_import_lines: List[str] = self.import_manager.import_lines_get()
        all_init_lines: List[str] = self.init_start_lines + self.init_tagged_lines.lines_get()
        all_callback_lines: List[str] = self.callback_tagged_lines.lines_get()

        # Join all of the lines that make up the class into *all_class_lines*:
        all_class_lines: List[str] = (
            self.class_header_lines +
            all_init_lines +
            all_callback_lines
        )

        # Join all the lines together into *all_lines* and create a single
        all_lines: List[str] = (
            self.copyright_lines +
            all_import_lines +
            self.main_lines +
            all_class_lines +
            self.main_call_lines +
            [""]  # Trick to get the final new-line on the file content
        )

        # Generate the `serial_agent.py` file:
        all_content: str = "\n".join(all_lines)
        serial_agent_py_path: Path = ros2_package_directory / package_name / "serial_agent.py"
        serial_agent_py_file: IO[str]
        with open(serial_agent_py_path, "w") as serial_agent_py_file:
            serial_agent_py_file.write(all_content)

    # SerialAgent.publisher_add():
    def publisher_add(self, topic_package_name: str, topic_type: str, topic_name: str) -> None:
        """Add a publisher to the serial agent."""

        # Make sure the Publisher type is imported:
        import_manager: ImportManager = self.import_manager
        import_manager.from_import("publisher", "rclpy.publisher", "Publisher", "type: ignore")
        import_manager.from_import("message", topic_package_name, topic_type, "type: ignore")

        # Make sure the publisher tables are created:
        init_tagged_lines: TaggedLines = self.init_tagged_lines
        init_tagged_lines.prefix_set("publisher", [
            f"        # Create all topic Publisher's here:",
            f"        self.publishers_table: Dict[str, Publisher] = {{}}",
            f"        self.publishers_counter: Dict[str, int] = {{}}",
        ])

        # Add publisher here:
        subscription_callback_name: str = f"{topic_name}_subscription_callback"
        init_tagged_lines.extend("publisher", [
            f"        self.publishers_table['{topic_name}'] = self.create_publisher(",
            f"            {topic_type}, '{topic_name}', 10)",
            f"        self.publishers_counter['{topic_name}'] = 0",
        ])

    # SerialAgent.timer_add():
    def timer_add(self, timer_name: str, timer_type: str, period: float) -> None:
        """Add a timer callback."""

        # Ensure that the Timer type is imported:
        import_manager: ImportManager = self.import_manager
        import_manager.from_import("timer", "rclpy.timer", "Timer", "type: ignore")

        # Create the timer here:
        callback_name: str = f"{timer_name}_timer"
        init_tagged_lines: TaggedLines = self.init_tagged_lines
        init_tagged_lines.prefix_set("timer", [
            f"        # Create all Timer's here:",
            f"        self.timers_table: Dict[str, Timer] = {{}}",
            f"        self.timers_counter: Dict[str, int] = {{}}",
        ])
        init_tagged_lines.extend("timer", [
            f"        self.timers_table['{timer_name}'] = (",
            f"            self.create_timer({period}, self.{callback_name}))",
            f"        self.timers_counter['{timer_name}'] = 0",
        ])

        # Create the callback counter and function:
        dq1: str = "\""
        dq3: str = dq1 * 3
        callback_tagged_lines: TaggedLines = self.callback_tagged_lines
        callback_tagged_lines.extend("timer", [
            "",
            f"    # SerialAgent.{callback_name}:",
            f"    def {callback_name}(self) -> None:",
            f"        {dq3}Callback for {timer_name} Timer.{dq3}",
            "",
            f"        count: int = self.timers_counter['{timer_name}']",
            f"        message: {timer_type} = {timer_type}()",
            f"        message.data = f'Counter={{count}}'",
            "",
            f"        # Temporary:",
            f"        publisher: Publisher = self.publishers_table['topic']",
            f"        publisher.publish(message)",
            f"        print('\\nTimer({dq1}{timer_name}{dq1}): '",
            f"              f'Publish {{message}} to Topic({dq1}{timer_name}{dq1})')",
            "",
            f"        self.timers_counter['{timer_name}'] = count + 1",
            f"        # print('<=SerialAgent.{callback_name}()')",
        ])

    # SerialAgent.client_add():
    def client_add(self, service_package: str, service_type: str, client_name: str) -> None:
        """Add a client to the serial agent."""

        # Ensure that the Client type is imported:
        import_manager: ImportManager = self.import_manager
        import_manager.from_import("client", "rclpy.task", "Future", "type: ignore")
        import_manager.from_import("client", "rclpy.client", "Client", "type: ignore")

        request_type: str
        response_type: str
        request_type, response_type = import_manager.service_types_import(
            service_package, service_type)

        # Ensure that the client wait code is added to the __init__ startup.
        init_tagged_lines: TaggedLines = self.init_tagged_lines
        init_tagged_lines.prefix_set("server_wait", [
            "        # Wait for all requested client services to become available:",
            "        logger: Logger = self.get_logger()",
            "        while self.client_waits:",
            "            client_name: str",
            "            for client_name in tuple(self.client_waits):",
            "                client: Client = self.clients_table[client_name]",
            "                if client.wait_for_service():",
            "                    self.client_waits.remove(client_name)",
            "            if not self.client_waits:",
            "                logger.info(f'Waiting for {{client_waits}} to become available')",
        ])

        # Create the client and add it to the clients dictionariers:
        init_tagged_lines.prefix_set("client", [
            f"        # Client send rouines are defined here:",
        ])
        init_tagged_lines.prefix_set("client", [
            f"        # Create all service Client's here:",
            f"        self.clients_table: Dict[str, Client] = {{}}",
            f"        self.clients_counter: Dict[str, int] = {{}}",
            f"        self.client_waits: Set[str] = set()",
        ])
        init_tagged_lines.extend("client", [
            f"        self.clients_table['{client_name}'] = self.create_client(",
            f"            {service_type}, '{client_name}')",
            f"        self.clients_counter['{client_name}'] = 0",
            f"        self.client_waits.add('{client_name}')",
        ])

        # Generate a request routine with a callback for the eventual response:
        dq1: str = "\""
        dq3: str = dq1 * 3
        request_send_name: str = f"{client_name}_request_send"
        response_callback_name: str = f"{client_name}_response_callback"
        indent: str = " " * (len(request_send_name) + 1)
        callback_tagged_lines: TaggedLines = self.callback_tagged_lines
        callback_tagged_lines.extend("client", [
            "",
            f"    # SerialAgent.{request_send_name}()",
            f"    # Debugging:",
            f"    def {request_send_name}(self, a: int, b: int,",
            f"        {indent}response_callback: Callable[[Any], None]) -> None:",
            f"        {dq3}Initiate a request/response to/from {client_name} server.{dq3}",
            "",
            f"        request: {request_type} = {request_type}()",
            f"        request.a = a",
            f"        request.b = b",
            f"        client: Client = self.clients_table['{client_name}']",
            f"        response_future: Future = client.call_async(request)",
            f"        # Debugging:",
            f"        print(f'response_future={{response_future}})')",
            f"        response_future.add_done_callback(response_callback)",
            f"        print('Client({dq1}{client_name}{dq1}): '",
            f"              f'Sent {dq1}{{request}}{dq1}')",
        ])

        callback_tagged_lines.extend("client", [
            "",
            f"    # SerialAgent.{response_callback_name}():",
            f"    # Debugging:",
            f"    def {response_callback_name}(self, response_future: Future) -> None:",
            f"        {dq3}Response callback routine for {client_name}.{dq3}",
            "",
            f"        # Debugging:",
            f"        try:",
            f"            response: Any = response_future.result()",
            f"        except Exception as error:",
            f"            assert False, 'Fixme!'",
            f"        else:",
            f"            assert isinstance(response, {response_type})",
            f"            print('Client({dq1}{client_name}{dq1}): '",
            f"                  f'Got response={dq1}{{response}}{dq1}')",
        ])

    # SerialAgent.server_add():
    def server_add(self, service_package_name: str, service_type: str, server_name: str) -> None:
        """Add a server to the serial agent."""

        # Ensure that the Server type is imported.
        # Note rclpy.service.Service is changed to Server to be more consistent.
        import_manager: ImportManager = self.import_manager
        import_manager.from_import_as(
            "server", "rclpy.service", "Service", "Server", "type: ignore")

        # Ensure that the service Request and Response types are imported.
        request_type: str
        response_type: str
        request_type, response_type = import_manager.service_types_import(
            service_package_name, service_type)

        # Ensure that the server dictionaries are first initalized to empty:
        init_tagged_lines: TaggedLines = self.init_tagged_lines
        init_tagged_lines.prefix_set("server", [
            "        # Create all service Server's here:",
            "        self.servers_table: Dict[str, Server] = {}",
            "        self.servers_counter: Dict[str, int] = {}",
        ])

        # Create the server and add it to the servers dictionary:
        callback_name: str = f"{server_name}_server_callback"
        init_tagged_lines.extend("server", [
            f"        # Create all topic Subscription's here:",
            f"        self.servers_table['{server_name}'] = self.create_service(",
            f"            {service_type}, '{server_name}', self.{callback_name})",
            f"        self.servers_counter['{server_name}'] = 0",
        ])

        # Create the callback method for the server:
        dq1: str = "\""
        dq3: str = dq1 * 3
        indent: str = " " * len(callback_name)
        callback_tagged_lines: TaggedLines = self.callback_tagged_lines
        callback_tagged_lines.extend("server", [
            "",
            f"    # SerialAgent.{callback_name}() -> {response_type}:",
            f"    def {callback_name}(self,",
            f"         {indent}request: {request_type},",
            f"         {indent}response: {response_type}) -> None:",
            "",
            f"        {dq3}Callback for '{server_name}' service request.{dq3}",
            f"        # Debugging only:",
            f"        print(f'Service({dq1}{server_name}{dq1}): Got {dq1}{{request}}{dq1}')",
            f"        response.sum = request.a + request.b",
            f"        print(f'Service({dq1}{server_name}{dq1}): Sent {dq1}{{response}}{dq1}')",
            f"        return response",
        ])

    # SerialAgent.subscription_add():
    def subscription_add(self, subscription_package_name: str,
                         subscription_type: str, subscription_name: str) -> None:
        """Add a subscription to the serial agent."""

        # Make sure the Subscription type is imported:
        import_manager: ImportManager = self.import_manager
        import_manager.from_import(
            "subscription", "rclpy.subscription", "Subscription", "type: ignore")
        import_manager.from_import(
            "message", subscription_package_name, subscription_type, "type: ignore")

        # Ensure that the subscriptions dictionary is initalized to empty:
        init_tagged_lines: TaggedLines = self.init_tagged_lines
        init_tagged_lines.prefix_set("subscription", [
                "        # Create all topic Subscription's here:",
                "        self.subscriptions_table: Dict[str, Subscription] = {}",
                "        self.subscriptions_counter: Dict[str, int] = {}",
        ])

        # Create the subscription and add it to the subscriptions dictionary:
        callback_name: str = f"{subscription_name}_subscription_callback"
        init_tagged_lines.extend("subscription", [
            f"        self.subscriptions_table['{subscription_name}'] = self.create_subscription(",
            f"            {subscription_type}, '{subscription_name}', self.{callback_name}, 10)",
            f"        self.subscriptions_counter['{subscription_name}'] = 0",
        ])

        # Create the callback method for the subscription:
        dq1: str = "\""
        dq3: str = dq1 * 3
        callback_tagged_lines: TaggedLines = self.callback_tagged_lines
        callback_tagged_lines.extend("subscription", [
            "",
            f"    # SerialAgent.{callback_name}():",
            f"    def {callback_name}(self, message: {subscription_type}) -> None:",
            f"        {dq3}Callback for '{subscription_name}' topic subscription.{dq3}",
            f"        count: int = self.subscriptions_counter['{subscription_name}']",
            f"        count += 1",
            f"        self.subscriptions_counter['{subscription_name}'] = count",
            f"        # Debgging only:",
            f"        print(f'Subscription({dq1}{subscription_name}{dq1}): '"
            f"              f'Got {dq1}{{message.data}}{dq1}')",
            "",
            f"        # Debugging:",
            f"        a: int = count",
            f"        b: int = count + 1",
            f"        self.add_two_ints_request_send(a, b, self.add_two_ints_response_callback)",
        ])

def resource_create(ros2_package_directory: Path, package_name: str) -> None:
    """Create the resource directory an fill it in."""
    resource_directory: Path = ros2_package_directory / "resource"
    resource_directory.mkdir(parents=True, exist_ok=True)

    # Write out an empty file:
    resource_package_path: Path = resource_directory / package_name
    resource_package_file: IO[str]
    with open(resource_package_path, "w") as resource_package_file:
        pass

def setup_py_create(ros2_package_directory: Path, package_name: str) -> None:
    """Create the `setup.py` file. """
    email: str = email_get()
    maintainer: str = maintainer_get()
    setup_py_lines: Tuple[str, ...] = (
        "from setuptools import setup",
        "",
        f"package_name = '{package_name}'",
        "",
        "setup(",
        "    name=package_name,",
        "    version='0.0.0',",
        "    packages=[package_name],",
        "    data_files=[",
        "        ('share/ament_index/resource_index/packages',",
        "            ['resource/' + package_name]),",
        "        ('share/' + package_name, ['package.xml']),",
        "    ],",
        "    install_requires=['setuptools'],",
        "    zip_safe=True,",
        f"    maintainer='{maintainer}',",
        f"    maintainer_email='{email}',",
        "    description='ROS2 Serial line package',",
        "    license='MIT',",
        "    tests_require=['pytest'],",
        "    entry_points={",
        "        'console_scripts': [",
        f"            'serial_agent = {package_name}.serial_agent:main',",
        "        ],",
        "    },",
        ")",
        "",
    )
    setup_py_content: str ="\n".join(setup_py_lines)

    # Write *setup_py_lines_content* to *setup_py_path:
    setup_py_path: Path = ros2_package_directory / "setup.py"
    setup_py_file: IO[str]
    with open(setup_py_path, "w") as setup_py_file:
        setup_py_file.write(setup_py_content)

def package_xml_create(ros2_package_directory: Path, package_name: str) -> None:
    """Create the `package.xml` file."""
    # Get the *maintainer* and *email*:
    email: str = email_get()
    maintainer: str = maintainer_get()

    # Create the *package_xml_text* content.  Use single quotes for strings since XML
    # files tend to use double quotes a lot:
    package_xml_lines: Tuple[str, ...] = (
        '<?xml version="1.0"?>',
        ('<?xml-model href="http://download.ros.org/schema/package_format3.xsd"'
         ' schematypens="http://www.w3.org/2001/XMLSchema"?>'),
        '<package format="3">',
        f'  <name>{package_name}</name>',
        '  <version>0.0.0</version>',
        '  <description>Package for interacting with serial line.</description>',
        f'  <maintainer email="{email}">{maintainer}</maintainer>',
        '  <license>MIT</license>',
        '  <exec_depend>rclpy</exec_depend>',
        '  <exec_depend>std_msgs</exec_depend>',
        '',
        '  <test_depend>ament_copyright</test_depend>',
        '  <test_depend>ament_flake8</test_depend>',
        '  <test_depend>ament_pep257</test_depend>',
        '  <test_depend>python3-pytest</test_depend>',
        '',
        '  <export>',
        '    <build_type>ament_python</build_type>',
        '  </export>',
        '</package>',
        ''
    )
    package_xml_content: str = "\n".join(package_xml_lines)

    # Write out the *package_xml_content* to the `package.xml` file:
    package_xml_path: Path = ros2_package_directory / "package.xml"
    package_xml_file: IO[str]
    with open(package_xml_path, "w") as package_xml_file:
        package_xml_file.write(package_xml_content)

def init_py_create(python_package_directory: Path) -> None:
    """Create the empty `__init__.py` file."""
    init_py_path: Path = python_package_directory / "__init__.py"
    init_py_file: IO[str]
    with open(init_py_path, "w") as init_py_file:
        pass  # An empty file is what is desired

def setup_cfg_create(ros_package_directory: Path, package_name: str) -> None:
    """Create the `setup.cfg` file."""
    # Create the *setup_cfg_content*:
    setup_cfg_lines: Tuple[str, ...] = (
        "[develop]",
        f"script-dir=$base/lib/{package_name}",
        "[install]",
        f"install-scripts=$base/lib/{package_name}",
        "",
    )
    setup_cfg_content: str = "\n".join(setup_cfg_lines)

    # Write *setup_cfg_content* to *set_up_cfg_file*:
    setup_cfg_path: Path = ros_package_directory / "setup.cfg"
    setup_cfg_file: IO[str]
    with open(setup_cfg_path, "w") as setup_cfg_file:
        setup_cfg_file.write(setup_cfg_content)

def email_get() -> str:
    """Return the maintainer E-mail from git."""
    done: subprocess.CompletedProcess = subprocess.run(
        ["git", "config", "--get", "user.email"], capture_output=True, encoding="UTF-8")
    assert done.returncode == 0, "git config --get user.name has error code of {done.returncode}"
    return done.stdout.strip("\n")

def maintainer_get() -> str:
    """Return the maintainer name from git."""
    done: subprocess.CompletedProcess = subprocess.run(
        ["git", "config", "--get", "user.name"], capture_output=True, encoding="UTF-8")
    assert done.returncode == 0, "git config --get user.name has error code of {done.returncode}"
    return done.stdout.strip("\n")

if __name__ == "__main__":
    main()

# [Multiple futures await](https://stackoverflow.com/questions/46785274/asyncio-speculatively-await-multiple-futures)

#   client = MinimalClientAsync()
#   while rclpy.ok():
#       rclpy.spin_once()
#       response_futures: List[asyncio.Future] =
#       done_futures: List[asyncio.Future]
#       pending_futures: List[asyncio.Future]
#       done_futures, pending_futures = (
#           await asyncio.wait(response_futures, return_when=FIRST_COMPLETED))
#       # Ignore pending_futures
#       done_future: asyncio.Future
#       for done_future in done_futures:
#            try:
#                response = done_future.result()
#            except Exception as e:
#                # Remove from all_futures
#                # do something error like here
#            else:
#                # Remove from all_futures
#                # Response is valid here
#                # Do someithing with response

# request = example_interfaces.srv._add_two_ints.AddTwoInts_Request(1, 2)
# request.get_fields_and_types()  => {'a': 'int64', 'b': 'int64'}
#
# response = example_interfaces.srv._add_two_ints.AddTwoInts_Response()
# response.get_fields_and_types() => {'sum': 'int64'}


#! /usr/bin/env python
#
# Simple edge detection testing
# Now with hough transform
# And trapeziodal filter
#

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Read image
img = cv2.imread('ets.jpg')

# Open mask
mask = cv2.imread('mask.png', 0)

# Convert to gray scale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# Run threshhold filter
ret, thresh = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY)

# Add blur to fill "holes" in lines
#blur = cv2.blur(img, (10,10))
#kernel = np.ones((10,10),np.float32)/100
#blur = cv2.filter2D(img,-1,kernel)
blur = cv2.medianBlur(thresh, 11)

# Run another blur
blur2 = cv2.blur(blur, (10,10))

# Then a new threshold filter
ret, thresh2 = cv2.threshold(blur2, 2, 255, cv2.THRESH_BINARY)

# Apply mask to image
masked = cv2.bitwise_and(thresh2, thresh2, mask=mask)

# Detecte edges
edges = cv2.Canny(masked, 150, 160, apertureSize=3)

# Hough transform
minLineLength = 100
maxLineGap = 100
lines = cv2.HoughLinesP(edges,1,np.pi/180,50,minLineLength=minLineLength,maxLineGap=maxLineGap)
#lines = cv2.HoughLines(edges,1,np.pi/180,700)

# Draw lines from hough transform
if lines != None:
    for line in lines:
        x1,y1,x2,y2 = line[0]
        cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
else:
    print("NO LINES FOUND")

#plt.subplot(121),plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
#plt.title('Original Image'), plt.xticks([]), plt.yticks([])
#plt.subplot(122),plt.imshow(edges,cmap = 'gray')
#plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

#plt.imshow(blur, cmap='gray')
#plt.imshow(edges, cmap='gray')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
#cv2.imshow('image',img)
cv2.imwrite('houghlines5.jpg',img)
plt.show()


import os
from time import sleep

from dotenv import dotenv_values

config = dotenv_values('.env')

import openai
openai.api_key = config['OPENAI_API_KEY']
embed_model = 'text-embedding-ada-002'
output_dimensions = 1536

import pinecone
pinecone.init(
    api_key=config['PINECONE_API_KEY'],
    environment="us-west1-gcp-free"
)
index_name = 'simetri-knowledge-base'
if index_name not in pinecone.list_indexes():
    pinecone.create_index(
        index_name,
        dimension=output_dimensions,
        metric='cosine'
    )
# connect to index
index = pinecone.Index(index_name)

# Load PDFs and split them into chunks
from langchain.document_loaders import UnstructuredPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

from uuid import uuid4

pdf_folder_path = './cosmos-data/'
file_names = os.listdir(pdf_folder_path)

loaders = [UnstructuredPDFLoader(os.path.join(pdf_folder_path, file_name)) for file_name in os.listdir(pdf_folder_path)]
datas = [loader.load() for loader in loaders]
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
split_pdfs = [text_splitter.split_documents(data) for data in datas]

for pdf in split_pdfs:
    texts = [doc.page_content for doc in pdf]

    try:
        res = openai.Embedding.create(input=texts, engine=embed_model)
    except:
        done=False
        while not done:
            sleep(5)
            try:
                res = openai.Embedding.create(input=texts, engine=embed_model)
                done = True
            except:
                pass

    embeds = [record['embedding'] for record in res['data']]

    vectors = list()

    for idx, i in enumerate(texts):
        vectors.append({
            'id': str(uuid4()),
            'values': embeds[idx],
            'metadata': {
                'text': texts[idx]
            }
        })

    index.upsert(vectors=vectors)


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow import keras

#  Read csv with pd.read_csv()
df = pd.read_csv("stroke.csv")

# Since the dataframe is not big I will just create a copy to not mess with original data
norm_df = df.copy()
# Drop rows that have NaN values
norm_df = norm_df.dropna()

# Here I start making the data understandable for our keras model

# Here I define a method that takes in a list of column values (A list of strings) and return a dictionary where each column value got assigned a number
def create_int_dict(query_list):
    dict = {}
    i = 0
    for query in query_list:
        dict[query] = i
        i+=1
    return dict

# Here I call the method defined earlier for every unique column
# i used the in-built pandas .unique() method which automatically returns, you guessed it, all unique values in a column
smoking_dict = create_int_dict(norm_df["smoking_status"].unique())
residence_dict = create_int_dict(norm_df["Residence_type"].unique())
work_dict = create_int_dict(norm_df["work_type"].unique())
gender_dict = create_int_dict(norm_df["gender"].unique())
marriage_dict = create_int_dict(norm_df["ever_married"].unique())

# Another helper method to actually replace the column value with the numbers they got assigned earlier with create_int_dict()
# This method takes in the column, the corresponding dictionary, and the dataframe
def replace_column_value(column, dict, df):
    df[column] = df[column].apply(lambda x: dict[x])

# Here I call the function defined earlier
replace_column_value("gender", gender_dict, norm_df)
replace_column_value("work_type", work_dict, norm_df)
replace_column_value("Residence_type", residence_dict, norm_df)
replace_column_value("smoking_status", smoking_dict, norm_df)
replace_column_value("ever_married", marriage_dict, norm_df)

# This is an application of train_test_split() from sklearn
# What it does is it splits arrays or matrices into random train and test subsets

# Overall we need 3 dataframes - Training, testing and validation dataframes

# Firstly we create train_df and test_df, we give the dataframe we want to split, which is norm_df, after that we have test_size=0.2 which means that the method will shuffle and choose 20% percent of the data
train_df, test_df = train_test_split(norm_df, test_size=0.2)
# Secondly we overwrite train_df and create a new variable val_df, then we pass the old train_df instead of norm_df, and test_size stays 20%
train_df, val_df = train_test_split(train_df, test_size=0.2)

# Then we need x and y values
# X is input, y is output

# After that we just use .pop() method and pass it the column value which can be either 0 or 1 (1 - patient suffered a stroke, 0 - patient did not suffer a stroke)
# .pop() returns the item and removes it from the frame
# np.array() return a numpy array
train_y = np.array(train_df.pop('stroke'))
val_y = np.array(val_df.pop('stroke'))
test_y = np.array(test_df.pop('stroke'))

# Since we popped the stroke column we don't have to worry about it and just pass in the dataframes
train_x = np.array(train_df)
val_x = np.array(val_df)
test_x = np.array(test_df)

# Just a method to retur a model created in it
def create_model():

    # We use a normal keras sequential model
    # It uses a sequential model API which allows to instatiate a model and the add a bunch of linear layers to it
    model = keras.Sequential([

        # In a model we can split it into 3 parts
        # Input layer, hidden layers, output layer

        # First layer is a dense input layer
        # Dense layers are the most widely used kind of layer
        # It represents a layer of neurons which receive input from from all neurons of the previous layer
        # We need to give it some parameters
        # The first paramater is units, it defines the size of the output from the dense layer
        # Second is input shape, we just use .shape which return a list which looks like this [rows, columns] that's why I use [-1] index to get the last elemnt oof the list
        # Third is the activation function, I used relu for this case
        # Activation function is a mathematical “gate” in between the input feeding the current neuron and its output going to the next layer
        keras.layers.Dense(64, input_shape=[train_x.shape[-1]], activation="relu"),
        # Now that we defined the input layer, we can move on to our hidden layers
        # Since we don't have that much data we do not need that many layers, one will do the job
        # It has the sama params as the previous layer with the exception of input_shape (We need to define it only once in the input layer)
        keras.layers.Dense(64, activation="relu"),
        # Here we have our output layer, which is a little different
        # Firstly our units are not 64 but 1 this time, simply because the output shape for our case scenario is [1,] since it can only be 1 or 0
        # If you had more output cases for example5 your units would be 5, since the output would have the shape [5,]
        # And you should use a different activation func for outpur layers too
        # I used sigmoid since its good for binary classification
        keras.layers.Dense(1, activation="sigmoid")
    ])

    # After we defined the model and the layers we can compile our model
    model.compile(
        # You have to pass some main parameters
        # First one is a loss function, I used binary_crossentropy since we are doing linear binary classification
        # A loss func is basicallly a method of evaluating how well your algorithm models your dataframe
        loss="binary_crossentropy",
        # Then we have an optimizer, safe bet is to use adam
        # Optimizers are algorithms or methods used to minimize a loss function or to maximize the efficiency of production
        optimizer="adam",
        # And the we just pass in the metrics that interest us
        # I just used BinaryAccuracy
        # It shows how accurate is the model with its decision making
        metrics=[
            keras.metrics.BinaryAccuracy(name='accuracy')
            ]
        )

    # We return our compiled model
    return model

# Call the function and store the model in a variable
model = create_model()
# Here I define an early stopping callback
# It is used to stop model training if it stops improving
early_stopping = tf.keras.callbacks.EarlyStopping(
    # This is the metric that interests us
    # If validation accuracy stops improving we are done with training
    monitor='val_accuracy',
    # This is logging
    verbose=1,
    # How many epochs can pass without improvement before we stop
    patience=10,
    # "max" mode training will stop when the quantity monitored has stopped increasing
    mode='max',
    # Whether to restore model weights from the epoch with the best value of the monitored quantity
    restore_best_weights=True)

# Then we fit the model
# We pass x, y, epochs is how many times the model will train, batch_size is the size of data batches that will be given to our model, then pass in the early stopping callback and validation data
# You can tune your model based on results of the metrics of validation data (That's what "val_accuracy" in the callback meant, we stop when the validation data's accuracy metric stops improving)
model.fit(train_x, train_y, epochs=50, batch_size=24, callbacks=[early_stopping], validation_data=[val_x, val_y])

# After we train the model we can evaluate how good our model did with model.evaluate
# We give it fresh test data we prepared earlier
# And it runs through our model and gives us a list of metrics, so basically a score
score = model.evaluate(test_x, test_y)

# This is just for logging the final score
# I use python's zip() which creates tuples
for name,val in zip(model.metrics_names, score):
    # Log it
    print(f"{name}: {val}")

# Voila - 95% accuracy model


# -*- coding: utf-8 -*-
# 播放数排名，--由cid获取弹幕信息 # 9944条
import pymongo
from bs4 import BeautifulSoup
import requests
import time
import random

def get_ready(ch='video_detail',dbname='blbl'):
    '''数据库调用'''
    global mycol, myclient, mydanmu
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")
    mydb = myclient[dbname]
    mycol = mydb[ch]
    mydanmu = mydb['b1_danmu']
get_ready()
cid = mycol.find({})
headers = {
    'Referer': 'https://www.bilibili.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'
}
for i,j in enumerate(cid):
    if i >= 0:
        print('次数',i,j['data']['pages'][0]['cid'],j['data']['aid'],j['data']['title'],end='')
        url = 'https://comment.bilibili.com/' + str(j['data']['pages'][0]['cid']) +'.xml'
        r = requests.get(url, headers=headers)
        time.sleep(1)
        r.encoding = 'utf8'
        soup = BeautifulSoup(r.text)
        results = soup.find_all('d')
        for result in results:
            danmu = {} # 弹幕信息
            danmu['cid'] = j['data']['pages'][0]['cid']
            danmu['aid'] = j['data']['aid']
            danmu['title'] = j['data']['title']
            danmu['content'] = result.text
            danmu['sendtime'] = result.get('p')
            mydanmu.insert_one(danmu)


import yaml
import os
import numpy as np
from scipy import signal
from sklearn.neighbors import NearestNeighbors
from matplotlib import animation
import matplotlib.pyplot as plt
import aux_fn
from frontend import myFig
import time

with open("param.yaml") as fp:
    params = yaml.load(fp, Loader=yaml.FullLoader)
if params['chebwin_attenuation'] > 0:
    window = signal.chebwin(params['window_size'], at=params['chebwin_attenuation'])
else:
    window = np.ones(params['window_size'])
#load source file
filename = "data/26.05.20/2020-02-14 172715.674766.wav"
feeder = aux_fn.FileFeeder(filename, params['window_size'], params['step'])
gen = iter(feeder)
#process geometry between mics
geom_filepath = os.path.join(os.path.dirname(filename),'arch.csv')
geom = np.genfromtxt(geom_filepath, delimiter=',', skip_header=True)
spherePts = aux_fn.gen_sphere(params['n_points'], params['radius'], True)

shiftsInTicsByDir = aux_fn.get_sphere_shifts(spherePts, geom, feeder.fs, params['sound_speed'])
shiftsInTicsByDir,idx = np.unique(shiftsInTicsByDir, return_index=True, axis=0)
spherePts = spherePts[idx, :]

polar_pts = aux_fn.cartesian_to_spherical(spherePts)

params['max_shift_array'] = np.zeros(len(params['xcorr_pairs']),dtype=np.int)
pairShiftsInTicsByDir = np.zeros((len(spherePts), len(params['xcorr_pairs'])))
for i, pair in enumerate(params['xcorr_pairs']):
    params['max_shift_array'][i] = np.ceil(np.linalg.norm(geom[pair[0], :]-geom[pair[1],:])*feeder.fs/params['sound_speed'])
    pairShiftsInTicsByDir[:, i] = shiftsInTicsByDir[:, pair[0]]-shiftsInTicsByDir[:, pair[1]]

knn = NearestNeighbors(n_neighbors=1)
knn.fit(pairShiftsInTicsByDir)

fig1 = plt.figure(FigureClass=myFig, figsize=(6,3))

def anim_func(frame=0):
    try:
        start = time.perf_counter()
        val = next(gen)
        #filter = aux_fn.gen_point_filter(val[:, 0], params['window_size'], window, params)
        #res = aux_fn.freq_filter(val, params['window_size'], feeder.fs, filter, window)
        res = aux_fn.freq_filter(val, params['window_size'], feeder.fs, [600, 1200], window)
        possible_shifts = aux_fn.get_xcorr_shifts(res, params)
        maxval, maxind = knn.kneighbors(possible_shifts)
        maxind = maxind[maxval < 10]
        res_ind = aux_fn.threaded_get_source(res, shiftsInTicsByDir, maxind)
        direction = polar_pts[res_ind,:]
        time_chunk = params['step']/feeder.fs
        time_process = (time.perf_counter() - start)
        time_boost = time_chunk/time_process
        if frame%10 == 0:
            print("Seconds passed: {:.1f}, speed {:.1f}x".format(feeder.pos/feeder.fs, time_boost))
        fig1.update(direction)
    except StopIteration:
        pass
    return fig1.phi_point, fig1.theta_point,

anim = animation.FuncAnimation(fig1,anim_func,interval=0, blit=True)
plt.show()


import numpy as np
import matplotlib.pyplot as plt

from matplotlib import rc

from pythia.periodograms import scargle
from pythia.iterative_prewhitening import run_ipw, run_ipw_v02

# rc('text', usetex=True)
rc('font',**{'family':'serif','serif':['Computer Modern Roman']})
# plt.rcParams['text.latex.preamble']=[r"\usepackage{amsmath}"]
plt.rcParams['xtick.labelsize']=12
plt.rcParams['ytick.labelsize']=12

if __name__=="__main__":

  # times,fluxes = np.loadtxt("./test.data",unpack=True)
  times,fluxes = np.loadtxt("./test_tess.dat",unpack=True)
  nu_w,amp_w = scargle(times,np.ones_like(times),f0=-5.,fn=5.,norm='amplitude')
  nu,amp = scargle(times,fluxes-np.mean(fluxes),fn=50.,norm='amplitude')

  figw,axw = plt.subplots(1,1,figsize=(6.6957,6.6957),num=2)
  axw.plot(nu_w,amp_w,'k-')
  axw.set_xlabel(r'$\nu\,\,{\rm [d^{-1}]}$',fontsize=14)
  axw.set_ylabel(r'${\rm Amplitude\,\,[ppm]}$',fontsize=14)
  axw.set_title(r'${\rm Window}$',fontsize=14)

  fig,ax = plt.subplots(1,1,figsize=(6.6957,6.6957),num=1)
  ax.plot(nu,amp,'k-')
  ax.set_xlabel(r'$\nu\,\,{\rm [d^{-1}]}$',fontsize=14)
  ax.set_ylabel(r'${\rm Amplitude\,\,[ppm]}$',fontsize=14)
  ax.set_title(r'${\rm Example}$',fontsize=14)
  plt.show()

  yerr = 0.0005* np.ones_like(times)
  residuals, model, offsets, \
  frequencies, amplitudes, \
  phases, stop_criteria = run_ipw(times,fluxes-np.mean(fluxes), yerr, maxiter=5, fn=30.)

  # yerr = 0.0005* np.ones_like(times)
  # residuals, outpars= run_ipw_v02(times,fluxes-np.mean(fluxes), yerr, maxiter=5, fn=30.)
  #
  # frequencies = outpars['frequency']
  # amplitudes = outpars['amplitude']
  # phases = outpars['phase']
  # offsets = np.zeros_like(phases)
  # offsets[0] += outpars['offset']
  # stop_criteria = outpars['snr']

  np.savetxt('test.out',np.array([offsets,frequencies,amplitudes,phases]).T)
  fig,ax = plt.subplots(1,1,figsize=(6.6957,6.6957))

  print(' C + A*sin( 2*pi*f*(t-t0)+phi )')
  nu_,amp_ = scargle(times, residuals, fn=6.5, norm='amplitude')

  print(offsets)
  print(frequencies)
  print(amplitudes)
  print(phases)
  print(stop_criteria)

  outstr = '{} -- C: {:.6f} -- A: {:.6f} -- f: {:.6f} -- phi: {:.6f} -- SNR: {:.6f}'
  for ii,freq in enumerate(frequencies):
      print(outstr.format(ii, offsets[ii], amplitudes[ii], freq, phases[ii],stop_criteria[ii]))
      ax.axvline(freq, linestyle='--',color='red',alpha=0.4)
      ax.axhline(amplitudes[ii], linestyle='--',color='red',alpha=0.4)

  ax.plot(nu,amp,'-',color='grey')
  ax.plot(nu_,amp_,'k-')
  ax.set_xlabel(r'$\nu\,\,{\rm [d^{-1}]}$',fontsize=14)
  ax.set_ylabel(r'${\rm Amplitude\,\,[ppm]}$',fontsize=14)
  ax.set_title(r'${\rm Example}$',fontsize=14)
  plt.show()


"""
    This is a directories creating tool that i use for initializing
    my design projects folders, it creates:
    - Main project directory with the given name and path,
    - Sub-dir for Assets, for svg, eps, png, jpg...etc, assets that i use,
    - Sub-dir for JPG, for exportings jpg pictures.
    - Sub-dir for PNG, for exporting png pictures (logos, icon, etc).
"""

import os
import argparse

def init_args():
    """Defines command line arguments.

    Returns:
        ArgumentParser: argument parser that contains the specified arguments.
    """
    helper = 'Initializes a project directory for design work, by creating a main directory with the given name, and sub-dirs (Assets, JPG, etc).'
    parser = argparse.ArgumentParser(description=helper)

    parser.add_argument(
        'name', type=str, help='The name and path where to init the project (default path: the current directory with the given name).')
    parser.add_argument('-a', '--assets', action='store_true',
                        help='creates a Assets directory if specified.')
    parser.add_argument('-j', '--jpg', action='store_true',
                        help='creates a JPG directory if specified.')
    parser.add_argument('-p', '--png', action='store_true',
                        help='creates a PNG directory if specified.')

    return parser

def init_dir(path, assets, jpg, png):
    """Creates a directory with the given name, and appends sub-dirs
    to it, if specified (Assets, JPG, and PNG).

    Args:
        path (str): path and name for the main directory (project).
        assets (boolean): if True, creates a Assets directory.
        jpg (boolean): if True, creates a JPG directory.
        png (boolean): if True, creates a PNG directory.
    """
    try:
        # Creating the project directory
        os.mkdir(path)
        print("- Project folder created ✔")
        # Creating directory for assets
        if assets:
            os.mkdir(os.path.join(path, 'Assets'))
            print("   + Assets created ✔")
        # Creating directory for jpg
        if jpg:
            os.mkdir(os.path.join(path, 'JPG'))
            print("   + JPG created ✔")
        # Creating directory for png
        if png:
            os.mkdir(os.path.join(path, 'PNG'))
            print("   + PNG created ✔")
        print('-' * 27)
    except FileExistsError:
        # In case of error
        print('# File already exists ❌')

if __name__ == "__main__":
    # create arguments
    parser = init_args()
    args = parser.parse_args()
    init_dir(args.name, args.assets, args.jpg, args.png)


from functions import*
import numpy as np
import time
import matplotlib
matplotlib.use("TkAgg")
from pathlib import Path
from matplotlib import cm

# path = os.path.realpath(Path("C:/Users/ermu8317/Downloads/Sphere_10242"))
# path = "Sphere_10242"
# sizes = [10242, 20480]
path = "Sphere_2964"
sizes = [2964, 5924]
# path = "Sphere_642"
# sizes = [642, 1280]
#
n = 101
phi1 = np.linspace(0, np.pi, n)
theta1 = np.linspace(0, 2 * np.pi, n)
phi2, theta2 = np.meshgrid(phi1, theta1)
phi, theta = phi2.T, theta2.T
scaling_factor = 1
r = 0.9
direction = np.array([0, 0, 1])
d_norm = direction/np.linalg.norm(direction)
r0 = 1.05 * d_norm
# m, m_pos, transformation_matrix, sigmas = read_mesh_from_hdf5(fn2, mode="coil")
# m = d_norm
m = np.array([0, 0, 1])
# omega = 3e3
omega = 18.85956e3

xyz_grid = xyz_grid(r, phi, theta)
# xyz_grid, n = sphere_mgrid(70, r)

# omega = 100
r_target = sphere_to_carthesian(r=r, phi=phi.flatten(), theta=theta.flatten())
# r_target = r_target[np.where(r_target[:, 2] > (0.8*r))]

start = time.time()
time_0 = start
# calculate analytic solution
res1 = reciprocity_three_D(r, theta, r0_v=r0, m=m, phi=phi, omega=omega, projection='sphere_surface')
# res1 = reciprocity_sphere(n=n, grid=xyz_grid, r0_v=r0, m=m, omega=omega)[1]
end = time.time()
t = t_format(end - start)
print(f"{t[0]:.2f}" + t[1] + " receprocity")

start = time.time()
tc, areas, tri_points, n_v, avg_len = read_sphere_mesh_from_txt(sizes=None, path="spheres/", n=400)
# tc, areas, tri_points, n_v, avg_len = read_sphere_mesh_from_txt(sizes, path)
# tc, areas, tri_points, n_v, avg_len = read_sphere_mesh_from_txt_locations_only(sizes, path, scaling=1)
# tc, areas, tri_points, n_v, avg_len = read_sphere_mesh_from_txt_locations_only(sizes, path, scaling=1)
# tc, areas, tri_points, n_v, avg_len = sphere_mesh(samples=10000, scaling=scaling_factor)
print(f"average length: {avg_len}")
end = time.time()
t = t_format(end - start)
print(f"{t[0]:.2f}" + t[1] + " triangulation")
n_elements = tc.shape[0]
print(f"elements: {n_elements}")

start = time.time()
b_im = jacobi_vectors_numpy(tc, n_v, r0, m, omega=omega)
# Q = q_jac_cu(tc, areas, n_v, b_im, tol=1e-19, n_iter=20)
Q = SCSM_jacobi_iter_cupy(tc, areas, n_v, b_im, tol=5e-16, n_iter=20, omega=omega, complex_values=True, verbose=True)
# Q = q_jac_vec(tc, areas, n_v, b_im, tol=5e-16, n_iter=20)
# Q = SCSM_jacobi_iter_vec_numpy(tc, areas, n_v, b_im, tol=5e-16, n_iter=20, omega=omega, complex_values=False, verbose=True)
# Q = SCSM_jacobi_iter_numpy(tc, areas, n_v, b_im, tol=5e-16, n_iter=20, omega=omega, verbose=True)
# Q = SCSM_matrix(tc, areas, n=n_v, b_im=b_im, omega=omega)
# Q = SCSM_tri_sphere_numba(tc, tri_points, areas, r0=r0, m=m, omega=omega)[0]

rs = tc
end = time.time()
t = t_format(end - start)
print(f"{t[0]:.2f}" + t[1] + " Q jacobi")
b_im_ = vector_potential_for_E_single_m(rs=r_target, m=m, m_pos=r0, omega=omega)
start = time.time()

res_flat = SCSM_FMM_E2(Q=Q, r_source=tc, r_target=r_target, eps=1e-15, b_im=b_im_)
res = array_unflatten(res_flat, n_rows=n)
# res3 = functions.parallel_SCSM_E_sphere(man, Q, rs, r, theta=theta, phi=phi, r0=r0, m=m, projection="sphere_surface")
# res = functions.SCSM_E_sphere(Q, rs, r, theta, r0=r0, m=m)
# res = SCSM_E_sphere_numba_surf(Q, rs, r, theta, r0=r0, m=m, phi=phi)

end = time.time()
t = t_format(end - start)
print(f"{t[0]:.2f}" + t[1] + "  E calculation")

# start = time.time()
# res3 = E_near_correction(E=res, Q=Q, r_q=rs, r_sphere=r, tri_points=tri_points, theta=theta, phi=phi, n=n_elements,
#                          r_near=3*avg_len)
# end = time.time()
# t = t_format(end - start)
# print(f"{t[0]:.2f}" + t[1] + "  E correction")

time_last = end
t = t_format(end - time_0)
print(f"{t[0]:.2f}" + t[1] + "  complete simulation")

res2 = res.copy()

diff = np.abs(res1 - res2)
relative_diff = diff / np.linalg.norm(res1)
max_analytic = np.max(res1)
min_analytic = np.min(res1)
max_numeric = np.max(res)
min_numeric = np.min(res)

print(f"max_analytic = {max_analytic}, min_analytic = {min_analytic}, max_numeric = {max_numeric}, min_numeric = {min_numeric}")

# diff2 = np.abs(res1 - res3)
# relative_diff2 = diff2 / np.linalg.norm(res1)
#
error_imag = nrmse(res1, res2) * 100
# rerror_imag2 = np.linalg.norm(diff2) * 100 / np.linalg.norm(res1)

print(f"relative error: {error_imag:.7f}%")
# print(f"relative error with near field: {rerror_imag2:.7f}%")

# plot_E_sphere_surf(res, phi, theta, r)
plot_E_sphere_surf_diff(res1, res2, xyz_grid=xyz_grid, c_map=cm.bwr, title=False, plot_difference=False)
# functions.plot_E_sphere_surf(res2, phi, theta, r)
# functions.plot_E_sphere_surf(diff, phi, theta, r)
# functions.plot_E_sphere_surf(relative_diff, phi, theta, r)


import argparse

def parze():
    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--files", help = "comma separated list of files to compare")
    args = parser.parse_args()
    return args

def combine_files(files):
    fl = files.split(',')
    fl = [fi.strip() for fi in fl]
    results = {}
    for fil in fl:
        with open(fil, "r") as f:
            txt = f.read()
            txt = txt.strip().lstrip('[').rstrip(']')
            lines = txt.split(',')
            for ind, line in enumerate(lines):
                try:
                    results[f"line-{ind}"].append(line.strip())
                except KeyError:
                    results[f"line-{ind}"] = [line.strip()]
    return results

def compare_files(combined_results):
    diffs = []
    for key in combined_results.keys():
        for ind, line in enumerate(combined_results[key]):
            if ind == 0:
                first_line = line
            if line != first_line:
                diffs.append({"line": key, 'first_line': first_line, 'diff_line': line})
    print(f"The lines that are different are:\n{diffs}")

def main():
    args = parze()
    compare_files(combine_files(args.files))

main()


"""
filepuller server
=================
"""
import datetime
import os
import sys
import zlib
import hashlib
from threading import Thread
import http.server, select, socket, socketserver, urllib.parse
import pickle
import binascii
from path import Path

__author__ = 'M'

port = 80
drv = 'd:'
bind_ip = '0.0.0.0'
DIR_SRC_BASE = 'src'

HEADER_200 = b'''HTTP/1.1 200 OK\n\n'''
HEADER_404 = b'''HTTP/1.1 404 File Not Found\n\n'''

IGNORE_PREFIX = ['/src/exec',
                 '/src/res',
                 '/src/others',
                 '/src/.git',
                 '/src/.idea']

IGNORE_EXTENSIONS = ['.pyc', '.project', '.gitignore', '.git', '.svn', '.jar', '__pycache__']

IGNORE_ROOT_DIRS = ['.git', '.idea', 'res', 'exec']

def filter_file(cur_path):
    if [cur_path for ip in IGNORE_PREFIX if cur_path.startswith(ip)]:
        return None
    if cur_path.isfile() and cur_path.ext in IGNORE_EXTENSIONS:
        return None
    return cur_path

def get_files(root):
    """Returns selected list of directories"""
    ret_list = []
    root = Path(root)
    # append files from root dir
    ret_list.extend([Path(file.replace('\\', '/')) for file in root.files() if filter_file(file)])

    # append files from sub dirs
    for _dir in [_dir for _dir in root.dirs() if _dir.name not in IGNORE_ROOT_DIRS]:
        ret_list.extend([Path(file.replace('\\', '/')) for file in Path(_dir).walkfiles() if filter_file(file)])
    return ret_list

def gen_data(path):
    tim = datetime.datetime.now()
    files = get_files(path)
    tim2 = datetime.datetime.now()
    print('Time to walk(%s): %s' % (len(files), tim2 - tim))
    data = []
    for file in files:
        md5 = file.abspath().read_md5()
        if file.startswith('/%s/' % DIR_SRC_BASE) or file.startswith('/%s\\' % DIR_SRC_BASE):
            file = file[len(DIR_SRC_BASE) + 2:]
        data.append(file + ':' + binascii.b2a_hex(md5).decode())
    print('Time to hash(%s): %s' % (len(data), datetime.datetime.now() - tim2))
    return '\n'.join(data)

class ProxyReadHandler (http.server.BaseHTTPRequestHandler):
    __base = http.server.BaseHTTPRequestHandler
    __base_handle = __base.handle

class ThreadingHTTPServer (socketserver.ThreadingMixIn,
                           http.server.HTTPServer):
    pass

def server_instance(bind_host, bind_port, Handler):
    print(('Proxy started on %s:%s' % (bind_host, bind_port)))
    server = ThreadingHTTPServer((bind_host, bind_port), Handler)
    server.serve_forever()

if __name__ == '__main__':
    proxyread = Thread(target=server_instance, args=[bind_ip, port, ProxyReadHandler])
    proxyread.start()


import os
# os.environ["CUDA_VISIBLE_DEVICES"] = "1"
import sys
import glob
import json
import cv2
import numpy as np
import paddle
from PIL import Image
import math
import paddle
import paddle.nn as nn
import paddle.nn.functional as F

patch_size = 1024
overlap = 0

class AvgPool2d(nn.Layer):
    def __init__(self, kernel_size=None, base_size=None, auto_pad=True, fast_imp=False, train_size=None):
        super().__init__()
        self.kernel_size = kernel_size
        self.base_size = base_size
        self.auto_pad = auto_pad

        # only used for fast implementation
        self.fast_imp = fast_imp
        self.rs = [5, 4, 3, 2, 1]
        self.max_r1 = self.rs[0]
        self.max_r2 = self.rs[0]
        self.train_size = train_size

    def extra_repr(self) -> str:
        return 'kernel_size={}, base_size={}, stride={}, fast_imp={}'.format(
            self.kernel_size, self.base_size, self.kernel_size, self.fast_imp
        )

    def forward(self, x):
        if self.kernel_size is None and self.base_size:
            train_size = self.train_size
            if isinstance(self.base_size, int):
                self.base_size = (self.base_size, self.base_size)
            self.kernel_size = list(self.base_size)
            self.kernel_size[0] = x.shape[2] * self.base_size[0] // train_size[-2]
            self.kernel_size[1] = x.shape[3] * self.base_size[1] // train_size[-1]

            # only used for fast implementation
            self.max_r1 = max(1, self.rs[0] * x.shape[2] // train_size[-2])
            self.max_r2 = max(1, self.rs[0] * x.shape[3] // train_size[-1])
        if self.kernel_size[0] >= x.shape[-2] and self.kernel_size[1] >= x.shape[-1]:
            return F.adaptive_avg_pool2d(x, 1)

        if self.fast_imp:  # Non-equivalent implementation but faster
            h, w = x.shape[2:]
            if self.kernel_size[0] >= h and self.kernel_size[1] >= w:
                out = F.adaptive_avg_pool2d(x, 1)
            else:
                r1 = [r for r in self.rs if h % r == 0][0]
                r2 = [r for r in self.rs if w % r == 0][0]
                # reduction_constraint
                r1 = min(self.max_r1, r1)
                r2 = min(self.max_r2, r2)
                s = x[:, :, ::r1, ::r2].cumsum(axis=-1).cumsum(axis=-2)
                n, c, h, w = s.shape
                k1, k2 = min(h - 1, self.kernel_size[0] // r1), min(w - 1, self.kernel_size[1] // r2)
                out = (s[:, :, :-k1, :-k2] - s[:, :, :-k1, k2:] - s[:, :, k1:, :-k2] + s[:, :, k1:, k2:]) / (k1 * k2)
                out = nn.functional.interpolate(out, scale_factor=(r1, r2))
        else:
            n, c, h, w = x.shape
            s = x.cumsum(axis=-1).cumsum_(axis=-2)
            s = nn.functional.pad(s, (1, 0, 1, 0))  # pad 0 for convenience
            k1, k2 = min(h, self.kernel_size[0]), min(w, self.kernel_size[1])
            s1, s2, s3, s4 = s[:, :, :-k1, :-k2], s[:, :, :-k1, k2:], s[:, :, k1:, :-k2], s[:, :, k1:, k2:]
            out = s4 + s1 - s2 - s3
            out = out / (k1 * k2)

        if self.auto_pad:
            n, c, h, w = x.shape
            _h, _w = out.shape[2:]
            # print(x.shape, self.kernel_size)
            pad2d = ((w - _w) // 2, (w - _w + 1) // 2, (h - _h) // 2, (h - _h + 1) // 2)
            out = nn.functional.pad(out, pad2d, mode='replicate')

        return out

def replace_layers(model, base_size, train_size, fast_imp, **kwargs):
    for n, m in model.named_children():
        if len(list(m.children())) > 0:
            ## compound module, go inside it
            replace_layers(m, base_size, train_size, fast_imp, **kwargs)

        if isinstance(m, nn.AdaptiveAvgPool2D):
            pool = AvgPool2d(base_size=base_size, fast_imp=fast_imp, train_size=train_size)
            assert m._output_size == 1
            setattr(model, n, pool)

class Local_Base():
    def convert(self, *args, train_size, **kwargs):
        replace_layers(self, *args, train_size=train_size, **kwargs)
        imgs = paddle.rand(train_size)
        with paddle.no_grad():
            self.forward(imgs)

def tensor2img(input_image, min_max=(-1., 1.), image_num=1, imtype=np.uint8):
    """"Converts a Tensor array into a numpy image array.

    Parameters:
        input_image (tensor): the input image tensor array
        image_num (int): the convert iamge numbers
        imtype (type): the desired type of the converted numpy array
    """
    def processing(img, transpose=True):
        """"processing one numpy image.

        Parameters:
            im (tensor): the input image numpy array
        """
        # grayscale to RGB
        if img.shape[0] == 1:
            img = np.tile(img, (3, 1, 1))
        img = img.clip(min_max[0], min_max[1])
        img = (img - min_max[0]) / (min_max[1] - min_max[0])
        if imtype == np.uint8:
            # scaling
            img = img * 255.0
        # tranpose
        img = np.transpose(img, (1, 2, 0)) if transpose else img
        return img

    if not isinstance(input_image, np.ndarray):
        # convert it into a numpy array
        image_numpy = input_image.numpy()
        ndim = image_numpy.ndim
        if ndim == 4:
            image_numpy = image_numpy[0:image_num]
        elif ndim == 3:
            # NOTE for eval mode, need add dim
            image_numpy = np.expand_dims(image_numpy, 0)
            image_num = 1
        else:
            raise ValueError(
                "Image numpy ndim is {} not 3 or 4, Please check data".format(
                    ndim))

        if image_num == 1:
            # for one image, log HWC image
            image_numpy = processing(image_numpy[0])
        else:
            # for more image, log NCHW image
            image_numpy = np.stack(
                [processing(im, transpose=False) for im in image_numpy])

    else:
        # if it is a numpy array, do nothing
        image_numpy = input_image
    image_numpy = image_numpy.round()
    return image_numpy.astype(imtype)

class LayerNormFunction(paddle.autograd.PyLayer):

    @staticmethod
    def forward(ctx, x, weight, bias, eps):
        ctx.eps = eps
        N, C, H, W = x.shape
        mu = x.mean(1, keepdim=True)
        var = (x - mu).pow(2).mean(1, keepdim=True)
        y = (x - mu) / (var + eps).sqrt()
        ctx.save_for_backward(y, var, weight)
        # y = weight.view(1, C, 1, 1) * y + bias.view(1, C, 1, 1)
        y = paddle.reshape(weight, [1, C, 1, 1]) * y + paddle.reshape(bias, [1, C, 1, 1])
        return y

    @staticmethod
    def backward(ctx, grad_output):
        eps = ctx.eps
        # N, C, H, W = grad_output.size()
        N, C, H, W = grad_output.shape
        y, var, weight = ctx.saved_tensor()
        g = grad_output * paddle.reshape(weight, [1, C, 1, 1])
        mean_g = g.mean(axis=1, keepdim=True)

        mean_gy = (g * y).mean(axis=1, keepdim=True)
        gx = 1. / paddle.sqrt(var + eps) * (g - y * mean_gy - mean_g)
        # return gx, (grad_output * y).sum(axis=3).sum(axis=2).sum(axis=0), grad_output.sum(axis=3).sum(axis=2).sum(axis=0), None
        return gx, (grad_output * y).sum(axis=3).sum(axis=2).sum(axis=0), grad_output.sum(axis=3).sum(axis=2).sum(
            axis=0)

class LayerNorm2d(nn.Layer):

    def __init__(self, channels, eps=1e-6):
        super(LayerNorm2d, self).__init__()
        self.weight = self.create_parameter(shape=[channels])
        self.add_parameter("weight", self.weight)
        self.bias = self.create_parameter(shape=[channels])
        self.add_parameter("bias", self.bias)
        # self.register_parameter('weight', nn.Parameter(torch.ones(channels)))
        # self.register_parameter('bias', nn.Parameter(torch.zeros(channels)))
        self.eps = eps

    def forward(self, x):
        return LayerNormFunction.apply(x, self.weight, self.bias, self.eps)

class SimpleGate(nn.Layer):
    def forward(self, x):
        x1, x2 = x.chunk(2, axis=1)
        return x1 * x2

class NAFBlock(nn.Layer):
    def __init__(self, c, DW_Expand=2, FFN_Expand=2, drop_out_rate=0.):
        super().__init__()
        dw_channel = c * DW_Expand
        self.conv1 = nn.Conv2D(c, dw_channel, 1, padding=0, stride=1, groups=1, bias_attr=True)
        self.conv2 = nn.Conv2D(dw_channel, dw_channel, 3, padding=1, stride=1, groups=dw_channel,
                               bias_attr=True)
        self.conv3 = nn.Conv2D(dw_channel // 2, c, 1, padding=0, stride=1, groups=1, bias_attr=True)

        # Simplified Channel Attention
        self.sca = nn.Sequential(
            nn.AdaptiveAvgPool2D(1),
            nn.Conv2D(in_channels=dw_channel // 2, out_channels=dw_channel // 2, kernel_size=1, padding=0, stride=1,
                      groups=1, bias_attr=True),
        )

        # SimpleGate
        self.sg = SimpleGate()

        ffn_channel = FFN_Expand * c
        self.conv4 = nn.Conv2D(c, ffn_channel, 1, padding=0, stride=1, groups=1, bias_attr=True)
        self.conv5 = nn.Conv2D(ffn_channel // 2, c, 1, padding=0, stride=1, groups=1, bias_attr=True)
        self.norm1 = LayerNorm2d(c)
        self.norm2 = LayerNorm2d(c)

        self.dropout1 = nn.Dropout(drop_out_rate) if drop_out_rate > 0. else nn.Identity()
        self.dropout2 = nn.Dropout(drop_out_rate) if drop_out_rate > 0. else nn.Identity()

        # self.beta = nn.Parameter(torch.zeros((1, c, 1, 1)), requires_grad=True)
        # self.gamma = nn.Parameter(torch.zeros((1, c, 1, 1)), requires_grad=True)
        self.beta = self.create_parameter(shape=[1, c, 1, 1])
        self.add_parameter("beta", self.beta)
        self.gamma = self.create_parameter(shape=[1, c, 1, 1])
        self.add_parameter("gamma", self.gamma)

    def forward(self, inp):
        x = inp

        x = self.norm1(x)

        x = self.conv1(x)
        x = self.conv2(x)
        x = self.sg(x)
        x = x * self.sca(x)
        x = self.conv3(x)

        x = self.dropout1(x)

        y = inp + x * self.beta

        x = self.conv4(self.norm2(y))
        x = self.sg(x)
        x = self.conv5(x)

        x = self.dropout2(x)

        return y + x * self.gamma

class NAFNet(nn.Layer):

    def __init__(self, img_channel=3, width=32, middle_blk_num=1, enc_blk_nums=[ 1, 1, 1, 28 ], dec_blk_nums=[1, 1, 1, 1]):
        super().__init__()

        self.intro = nn.Conv2D(img_channel, width, 3, padding=1, stride=1, groups=1, bias_attr=True)
        self.ending = nn.Conv2D(width, img_channel, 3, padding=1, stride=1, groups=1, bias_attr=True)
        self.encoders = nn.LayerList()
        self.decoders = nn.LayerList()
        self.middle_blks = nn.LayerList()
        self.ups = nn.LayerList()
        self.downs = nn.LayerList()

        chan = width
        for num in enc_blk_nums:
            self.encoders.append(
                nn.Sequential(
                    *[NAFBlock(chan) for _ in range(num)]
                )
            )
            self.downs.append(
                nn.Conv2D(chan, 2 * chan, 2, 2)
            )
            chan = chan * 2

        self.middle_blks = \
            nn.Sequential(
                *[NAFBlock(chan) for _ in range(middle_blk_num)]
            )

        for num in dec_blk_nums:
            self.ups.append(
                nn.Sequential(
                    nn.Conv2D(chan, chan * 2, 1, bias_attr=False),
                    nn.PixelShuffle(2)
                )
            )
            chan = chan // 2
            self.decoders.append(
                nn.Sequential(
                    *[NAFBlock(chan) for _ in range(num)]
                )
            )

        self.padder_size = 2 ** len(self.encoders)

    def forward(self, inp):
        B, C, H, W = inp.shape
        inp = self.check_image_size(inp)

        x = self.intro(inp)

        encs = []

        for encoder, down in zip(self.encoders, self.downs):
            x = encoder(x)
            encs.append(x)
            x = down(x)

        x = self.middle_blks(x)

        for decoder, up, enc_skip in zip(self.decoders, self.ups, encs[::-1]):
            x = up(x)
            x = x + enc_skip
            x = decoder(x)

        x = self.ending(x)
        x = x + inp

        return x[:, :, :H, :W]

    def check_image_size(self, x):
        _, _, h, w = x.shape
        mod_pad_h = (self.padder_size - h % self.padder_size) % self.padder_size
        mod_pad_w = (self.padder_size - w % self.padder_size) % self.padder_size
        x = F.pad(x, (0, mod_pad_w, 0, mod_pad_h))
        return x

class NAFNetLocal(Local_Base, NAFNet):
    def __init__(self, *args, train_size=(1, 3, 512, 512), fast_imp=True, **kwargs):
        Local_Base.__init__(self)
        NAFNet.__init__(self, *args, **kwargs)

        N, C, H, W = train_size
        base_size = (int(H * 1.5), int(W * 1.5))

        self.eval()
        with paddle.no_grad():
            self.convert(base_size=base_size, train_size=train_size, fast_imp=fast_imp)

def process(src_image_dir, save_dir):
    model = NAFNetLocal()
    paddle.set_device('gpu:0')
    param_dict = paddle.load('./iter_196000_weight.pdparams')
    # print(param_dict['generator'].keys())
    model.load_dict(param_dict['generator'])
    model.eval()
    image_paths = glob.glob(os.path.join(src_image_dir, "*.png"))
    with paddle.no_grad():
        for image_path in image_paths:
            # do something
            lr_img = cv2.imread(image_path)
            h, w = lr_img.shape[:2]
            lr_img = cv2.cvtColor(lr_img, cv2.COLOR_BGR2RGB)
            # resize
            # lr_img = paddle.vision.transforms.resize(lr_img, (512, 512), interpolation='bilinear')
            # lr_img = paddle.vision.transforms.functional.to_tensor(lr_img)
            # out = model(paddle.unsqueeze(lr_img, axis=0))
            # out_img = out[0][0]

            lr_img = paddle.vision.transforms.functional.to_tensor(lr_img)
            # paddle.set_printoptions(8, 100, 100,sci_mode = True)
            num_patch = 0
            out_img = paddle.zeros_like(lr_img)
            w1 = list(np.arange(0, w - patch_size, patch_size - overlap, dtype=np.int))
            h1 = list(np.arange(0, h - patch_size, patch_size - overlap, dtype=np.int))
            w1.append(w - patch_size)
            h1.append(h - patch_size)
            # import time
            # paddle.device.cuda.synchronize()
            # start = time.time()
            for i in h1:
                for j in w1:
                    num_patch += 1
                    lr_patch = lr_img[:,i:i + patch_size, j:j + patch_size]
                    # print(i,j)
                    out = model(paddle.unsqueeze(lr_patch,axis=0))
                    out_tensor = out[0][0]
                    # print(out)
                    # print(out_tensor.shape)
                    out_img[:,i:i + patch_size, j:j + patch_size] = out_tensor
            im = Image.fromarray(tensor2img(out_img,min_max=(0., 1.)))
            # im = paddle.vision.transforms.resize(im, (h, w), interpolation='bilinear')
            im.save(os.path.join(save_dir, os.path.basename(image_path)))
            # out_img
            # paddle.device.cuda.synchronize()
            # end = time.time()
            # total_time = end - start
            # print('total_time:{:.2f}'.format(total_time))
            # break

if __name__ == "__main__":
    assert len(sys.argv) == 3
    src_image_dir = sys.argv[1]
    save_dir = sys.argv[2]
    # src_image_dir = "../Datasets/deblur_testA/blur_image"
    # save_dir = "results"
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)
    process(src_image_dir, save_dir)