""" Classes for basic manipulation of GraphNet """
import numpy as np
import tensorflow as tf

def _copy_any_ds(val):
    """
    Copy semantics for different datatypes accepted.
    This affects what happens when copying nodes, edges and graphs.
    In order to trace gradients,
    and defines a consistent interface regardless of the input data-structure.
    """
    valout = val
    if isinstance(val , np.ndarray) or isinstance(val, list):
        valout = val.copy()

    if isinstance(val, tf.Variable) or isinstance(val,tf.Tensor):
        valout = tf.identity(val) # TODO: maybe have a flag to override this? Adding more ops does not always make sense.

    return valout

class Node:
    def __init__(self, node_attr_tensor):
        if len(node_attr_tensor.shape) <2:
            raise ValueError("The shape of the input for nodes and edges should have at least 2 dimensions!")
        self.node_attr_tensor = node_attr_tensor
        self.incoming_edges = [];
        self.shape = self.node_attr_tensor.shape

    def get_state(self):
        return self.node_attr_tensor

    def set_tensor(self, tensor):
        self.node_attr_tensor = tensor
        self.shape = self.shape = tensor.shape

    def copy(self):
        return Node(_copy_any_ds(self.node_attr_tensor))

    def __add__(self, n):
        return Node(self.node_attr_tensor + n.node_attr_tensor)

    def __sub__(self, n):
        return Node(self.node_attr_tensor  - n.node_attr_tensor)

class Edge:
    def __init__(self, edge_attr_tensor, node_from, node_to):
        self.edge_tensor = edge_attr_tensor
        self.node_from = node_from
        self.node_to = node_to
        self.shape = self.edge_tensor.shape

        # Keep a reference to this edge since it is needed for aggregation afterwards.
        node_to.incoming_edges.append(self)

    def set_tensor(self, edge_tensor):
        self.edge_tensor = edge_tensor
        self.shape = edge_tensor.shape

    def copy(self, nodes_correspondence):
        edge_tensor = _copy_any_ds(self.edge_tensor)
        node_from = nodes_correspondence[self.node_from]
        node_to   = nodes_correspondence[self.node_to]
        return Edge(edge_tensor, node_from, node_to)

    def __add__(self, edge):
        Exception("Edge addition is not implemented! This is due to potentially unclear semantics. Perform this manually.")

class Graph:
    def __init__(self, nodes, edges, global_attr = None,NO_VALIDATION=True):
        """
        Creates a graph from a set of edges and nodes
        """
        self.nodes = nodes
        self.edges = edges
        self.global_attr = global_attr
        self.has_global = self.global_attr is not None

        if not NO_VALIDATION:
            self.validate_graph()

    def is_equal_by_value(self,g2):
        """
        Checks if the graphs have the same values for node and edge attributes
        """
        is_equal = True
        for n1,n2 in zip(self.nodes, g2.nodes):
            is_equal = is_equal and tf.reduce_all(n1.node_attr_tensor == n2.node_attr_tensor)

        for e1, e2 in zip(self.edges, g2.edges):
            is_equal = is_equal and tf.reduce_all(e1.edge_tensor== e2.edge_tensor)
        if self.has_global:
            is_equal = is_equal and (g2.global_attr == self.global_attr)
        return bool(is_equal)

    def compare_connectivity(self,g2):
        """
        Checks if the connectivity of two graphs is the same.
        """
        g1 = self
        nodes_from_match = [(g1.nodes.index(e1.node_from) == g2.nodes.index(e2.node_from)) for e1,e2 in zip(g1.edges,g2.edges)]
        nodes_to_match = [(g1.nodes.index(e1.node_to) == g2.nodes.index(e2.node_to)) for e1,e2 in zip(g1.edges,g2.edges)]
        all_matching = True
        for matches in [*nodes_from_match, *nodes_to_match]:
            all_matching = all_matching and matches
        return all_matching

    @staticmethod
    def validate_graph(self):

        # validate that the edges are all
        for e in self.edges:

            if ((e.node_from in self.nodes)):
                raise AssertionError("The source node {nn} for edge {ee} is not in the graph!".format(nn = e.node_from, ee = e))
            if (e.node_to in self.nodes):
                raise AssertionError("The destination node {nn} for edge {ee} is not in the graph!".format(nn = e.node_to, ee = e))

    def copy(self):
        # copy attributes of nodes and edges and re-create graph connectivity:
        nodes_coppied = [n.copy() for n in self.nodes]
        nodes_correspondence = {s:c for s , c in zip(self.nodes,nodes_coppied)}
        # Instantiate the new edges:
        coppied_edge_instances = []
        for e in self.edges:
            enew = e.copy(nodes_correspondence)
            coppied_edge_instances.append(enew)
        return Graph(nodes_coppied, coppied_edge_instances)

    def get_subgraph_from_nodes(self, nodes, edge_trimming_mode = "+from+to"):
        """
        Node should belong to graph. Creates a new graph with coppied edge and
        node properties, defined from a sub-graph of the original graph.
        parameters:
          self (type = Graph): the graph we want a sub-graph from
          nodes: the nodes of the graph we want the subgraph of.
          mode:  "+from+to" - keep an edge if there is a "from" node or a "to" node at that edge (and the corresponding node)
                 "-from-to" - keep an edge if there is NOT a "from" node and NOT a "to" node at that edge (and the corresponding node)
                 "+from"    - keep an edge only if it has a "from" node that coincides with any of the nodes in the list (not implemented)
                 "+to"      - keep an edge only if it has a "to" node that coincides with any of the nodes in the list (not implemented)
                 "-from"    - keep an edge only if it DOESN't have a "from" node that concides with any of the nodes in the list (not implemented)
        """

        def check_edge_trimming_condition(e_):
            if edge_trimming_mode == "+from+to":
                return (e.node_from in nodes) and (e.node_to in nodes)

            if edge_trimming_mode == "-from+to":
                return (e.node_from not in nodes) and (e.node_to not in nodes)

        sg_nodes_copy = [n.copy() for n in nodes]
        original_copy_nodes_correspondence = {n:nc for n, nc in zip(nodes, sg_nodes_copy)}
        sg_edges_copy = [];
        if len(self.edges) > 0:
            for e in self.edges:
                if check_edge_trimming_condition(e):
                    sg_edges_copy.append(e.copy(original_copy_nodes_correspondence))

        g = Graph(sg_nodes_copy, sg_edges_copy)
        return g

    def __add__(self, graph):
        """
        This should only work with graphs that have compatible node and edge features
        Assumed also that the two graphs have the same connectivity (otherwise this will fail ugly)
        """
        nodes = [nself + n for nself,n in zip(self.nodes,graph.nodes)]
        correspondence = {s:t for s, t in zip(self.nodes,nodes)}
        added_edges = [];
        for eself,e in zip(self.edges, graph.edges):
            enew = Edge(eself.edge_tensor +  e.edge_tensor,
                    correspondence[eself.node_from],
                    correspondence[eself.node_to])
            added_edges.append(enew);

        return Graph(nodes, added_edges)

def make_graph_tuple_from_graph_list(list_of_graphs):
    """
    Takes in a list of graphs (with consistent sizes - not checked)
    and creates a graph tuple (input tensors + some book keeping)

    Because there is some initial functionality I don't want to throw away currently, that implements special treatment for nodes and edges
    coming from graphs with the same topology, it is currently required that the first dimension of nodes and edges
    for the list of graphs that are entered in this function is always 1 (this dimension is the batch dimension in the previous implementation.)
    """

    # check the first dimension is 1 - instruct to split graphs if not.
    problematic_graphs = []

    # TODO: Support splitting a list of same graphs with the first dimension of node and edge
    #       features different than one and constructing a GraphTuple. Currently the first
    #       dimension is required to be "1" (but squeezed later on!)
    for g_index,g in enumerate(list_of_graphs):
        problem = ''
        all_sizes_same = True
        if g.nodes[0].get_state().shape[0] != 1:
            problem += 'First size of node attributes should be 1 - found %i '%g.edges[0].get_state().shape[0]
        if g.edges[0].edge_tensor.shape[0] != 1:
            problem += 'First size of node attributes should be 1 - found %i '%g.edges[0].get_state().shape[0]

    # graph_id = [id_ for id_, dummy in enumerate(list_of_graphs)]
    all_edges, all_nodes, n_nodes,n_edges =[[],[],[],[]]
    for g in list_of_graphs:
        all_edges.extend(g.edges)
        all_nodes.extend(g.nodes)
        n_nodes.append(len(g.nodes))
        n_edges.append(len(g.edges))

    edge_attr_tensor, nodes_attr_tensor, senders, receivers = [[],[],[],[]];
    for e in all_edges:
        edge_attr_tensor.append(e.edge_tensor)
        senders.append(all_nodes.index(e.node_from))
        receivers.append(all_nodes.index(e.node_to))

    for n in all_nodes:
        nodes_attr_tensor.append(n.node_attr_tensor)

    # The 2nd dimension (dimension index 1) should be of size 1 (there is a test in the start of the constructor).
    # The same framework supports efficient computation on graphs of the same topology batched together where the first dimension
    # is the batched size. It is required that such graphs were provided for the construction (or at least the first dimension is "1").
    edges_attr_stacked = tf.squeeze(tf.stack(edge_attr_tensor,0),1)
    nodes_attr_stacked = tf.squeeze(tf.stack(nodes_attr_tensor,0),1)
    return GraphTuple(nodes_attr_stacked, edges_attr_stacked,senders, receivers, n_nodes, n_edges)# , graph_id)

class GraphTuple:
    def __init__(self, nodes, edges,senders,receivers, n_nodes, n_edges, global_attr = None,sort_receivers_to_edges  = False , global_reps_for_nodes = None, global_reps_for_edges = None, n_graphs = None):
        """
        A graph tuple contains multiple graphs for faster batched computation.

        parameters:
            nodes      : a `tf.Tensor` containing all the node attributes
            edges      : a `tf.Tensor` containing all the edge attributes
            senders    : a list of sender node indices defining the graph connectivity. The indices are unique accross graphs
            receivers  : a list of receiver node indices defining the graph connectivity. The indices are unique accross graphs
            n_nodes    : a list, a numpy array or a tf.Tensor containing how many nodes are in each graph represented by the nodes and edges in the object
            n_edges    : a list,a numpy array or a tf.Tensor containing how many edges are in each graph represented by the nodes and edges in the object
            global_attr: (optional) a `tf.Tensor` or a `np.array` containing global attributes (first size - self.n_graphs)
            sort_receivers :  (optional) whether to sort the edges on construction, allowing for not needing to sort the output of the node receiver aggregators.
            global_reps_for_edges : (optional) used for the aggregation of the global var.
            global_reps_for_nodes : (optional) used for the aggregation of the global var.
            n_graphs   : (optional)
        """
        # Sort edges according to receivers and sort receivers:
        assert(len(n_nodes) == len(n_edges))

        self.nodes = nodes # floats tensor
        self.edges = edges # floats tensor
        self.senders = senders     # integers
        self.receivers = receivers # integers
        self.n_nodes = n_nodes     # integers
        self.n_edges = n_edges     # integers
        if n_graphs is None:
            self.n_graphs = len(self.n_nodes) # assuming the n_nodes is a list containing the number of nodes for each graph.

        self.global_attr = global_attr
        self.has_global = self.global_attr is not None

        graph_indices_nodes = []
        for k_,k in enumerate(self.n_nodes):
            graph_indices_nodes.extend(np.ones(k).astype("int")*k_)

        graph_indices_edges = []
        for k_,k in enumerate(self.n_edges):
            graph_indices_edges.extend(np.ones(k).astype("int")*k_)

        if self.has_global: # <- default global is "None". If it was provided, set the global variable (together with some aggregator indices for convenience and performance).
            self.assign_global(global_attr)

        self.graph_indices_nodes , self.graph_indices_edges = graph_indices_nodes, graph_indices_edges

        if (global_reps_for_edges is None ) and (global_reps_for_nodes is None):
            self.update_reps_for_globals()
        self.n_graphs = len(self.n_nodes)

    def update_reps_for_globals(self):
        """
        Some flat vectors for segment sums when dealing with global variables.
        This is created even when there are no globals (one just needs the node
        and edge counts for each graph.)
        """
        global_reps_for_edges = [] # <- used to cast the global tensor to a compatible size for the edges.
        for k, e in enumerate(self.n_edges):
            global_reps_for_edges.extend([k]*int(e))
        self._global_reps_for_edges = global_reps_for_edges

        global_reps_for_nodes = [] # <- similarly for nodes:
        for k, e in enumerate(self.n_nodes):
            global_reps_for_nodes.extend([k]*int(e))

        self._global_reps_for_nodes = global_reps_for_nodes

    def assign_global(self, global_attr, check_shape = False):
        self.has_global = True
        if check_shape:
            assert(tf.shape(global_attr)[0] == self.n_graphs)
        self.global_attr = global_attr

    def is_equal_by_value(self, other_graph_tuple):
        v1 = self.edges,self.nodes, self.receivers,self.senders, self.n_nodes, self.n_edges, self.n_graphs
        v2 = other_graph_tuple.edges,other_graph_tuple.nodes, other_graph_tuple.receivers,other_graph_tuple.senders, other_graph_tuple.n_nodes, other_graph_tuple.n_edges, other_graph_tuple.n_graphs
        def _equals_or_all_equals(v1_,v2_):
            if isinstance(v1_, list) and isinstance(v2_, list):
                return v1_ == v2_
            if isinstance(v1_, tf.Variable) and isinstance(v2_, tf.Variable):
                return all(v1_ == v2_)
            if isinstance(v1_, np.array) and isinstance(v2_. np.array):
                return all(v1_ == v2_)
        if self.has_global:
            global_same = _equals_or_all_equals(other_graph_tuple.global_attr,self.global_attr)
            assert(other_graph_tuple.has_global)
        else:
            global_same = True

        return all([_equals_or_all_equals(v1__,v2__) for v1__, v2__ in zip(v1,v2)]) and global_same

    def copy(self):
        n = _copy_any_ds(self.nodes)
        e = _copy_any_ds(self.edges)
        s = _copy_any_ds(self.senders)
        r = _copy_any_ds(self.receivers)
        nnodes = _copy_any_ds(self.n_nodes)
        nedges = _copy_any_ds(self.n_edges)
        ngraphs = _copy_any_ds(self.n_graphs)
        return GraphTuple(n,e,s,r,nnodes,nedges, global_attr = self.global_attr)

    def __add__(self, g2):
        nodes = self.nodes + g2.nodes
        edges = self.edges + g2.edges
        s = self.senders
        r = self.receivers
        n_nodes = self.n_nodes
        n_edges = g2.n_edges
        if self.has_global and g2.has_global:
            new_global = self.global_attr + g2.global_attr
            gt = GraphTuple(nodes,edges,s,r,n_nodes, n_edges, global_attr = new_global)
            gt._global_reps_for_edges = self._global_reps_for_edges
            gt._global_reps_for_nodes = self._global_reps_for_nodes
        else:
            gt = GraphTuple(nodes, edges, s,r,n_nodes, n_edges)

        return gt

    def get_graph(self, graph_index):
        """
        Returns a new graph with the same properties as the original  graph.
        gradients are not traced through this operation.
        """
        assert(graph_index >=0 )
        if graph_index > self.n_graphs:
            raise ValueError("The provided index is larger than the available graphs in this GraphTuple object.")

        get_start_stop_index = lambda sizes_list, index : np.cumsum([0,*sizes_list[0:index+1]])[-2:]
        start_idx_nodes , end_idx_nodes = get_start_stop_index(self.n_nodes, graph_index)
        start_idx_edges , end_idx_edges = get_start_stop_index(self.n_edges, graph_index)
        nodes_attrs = self.nodes[start_idx_nodes:end_idx_nodes]
        senders, receivers, edge_attr = [v[start_idx_edges:end_idx_edges] for v in [self.senders, self.receivers,self.edges]]
        senders = senders-start_idx_nodes
        receivers = receivers - start_idx_nodes
        nodes = [Node(node_attr[tf.newaxis]) for node_attr in nodes_attrs]
        edges = [Edge(edge_attr_tensor[tf.newaxis], nodes[node_from_idx], nodes[node_to_idx]) for edge_attr_tensor, node_from_idx, node_to_idx in zip(edge_attr, senders,receivers)]
        if self.has_global:
            global_attr = self.global_attr[graph_index]
        else:
            global_attr = None
        return Graph(nodes, edges, global_attr = global_attr)

    def to_tensor_dict(self):
        return _graphtuple_to_tensor_dict(self)

def _graphtuple_to_tensor_dict(gt_):
    """
    Transform a GT to a dictionary.
    Used for employing the traceable graph_dict evaluation function.
    """
    def _tf_constant_or_none(v):
        if v is None:
            return None
        else:
            return tf.constant(v)

    return {'edges' : _tf_constant_or_none(gt_.edges),
            'nodes' : _tf_constant_or_none(gt_.nodes),
            'senders' : _tf_constant_or_none(gt_.senders),
            'receivers' :_tf_constant_or_none(gt_.receivers),
            'n_edges' : _tf_constant_or_none(gt_.n_edges),
            'n_nodes' : _tf_constant_or_none(gt_.n_nodes),
            'n_graphs' : _tf_constant_or_none(gt_.n_graphs),
            'global_attr' : _tf_constant_or_none(gt_.global_attr),
            'global_reps_for_edges' : _tf_constant_or_none(gt_._global_reps_for_edges),
            'global_reps_for_nodes' : _tf_constant_or_none(gt_._global_reps_for_nodes)}


from fastapi import APIRouter, Depends, HTTPException, UploadFile
from sqlalchemy.orm import Session
from typing import List

from db.database import get_db
from security.auth import oauth2_scheme, get_current_user

from . import schemas, crud

router = APIRouter()

@router.post("/events/add")
async def add_event(text: str, image: UploadFile, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
    user = get_current_user(db, token)
    if not user.is_admin:
        raise HTTPException(status_code=400, detail="No permision")
    return crud.add_event(db, text, image)

@router.delete("/events/{event_id}/delete")
async def delete_event(event_id: int, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
    user = get_current_user(db, token)
    if not user.is_admin:
        raise HTTPException(status_code=400, detail="No permision")
    return crud.delete_event(db, event_id)

@router.get("/events", response_model=List[schemas.Events])
async def read_events(db: Session = Depends(get_db)):
    return crud.get_events(db)


"""
CartoonPhoto
Yotam Levit
Date: 13/11/2020
"""
import cv2

def read_image(image_name):
    return cv2.imread(image_name)

def get_edged(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    grey = cv2.medianBlur(gray, 5)
    edges = cv2.adaptiveThreshold(gray, 255,
                                  cv2.ADAPTIVE_THRESH_MEAN_C,
                                  cv2.THRESH_BINARY, 9, 9)
    return edges

def cartoonization(image, edges):
    color = cv2.bilateralFilter(image, 9, 250, 250)
    cartoon = cv2.bitwise_and(color, color, mask=edges)
    return cartoon

def show_images(image, edges, cartoon):
    cv2.imshow("Image" ,image)
    cv2.imshow("edges", edges)
    cv2.imshow("Cartoon", cartoon)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def convert(image_name):
    image = read_image(image_name)
    edges = get_edged(image)
    cartoon = cartoonization(image, edges)
    show_images(image, edges, cartoon)

convert("dana4.png")


from chessboard import *
import pygame
import sys

def redraw(screen, board, pieces, square_size, WHITE, GREY):
    # Draw the chess board
    for row in range(8):
        for col in range(8):
            if (row + col) % 2 == 0:
                color = WHITE
            else:
                color = GREY
            pygame.draw.rect(screen, color, [col * square_size, row * square_size, square_size, square_size])
            if board[row][col] != 0:
                # images are 55x55
                piece_image = pieces[str(board[row][col])]
                piece_width, piece_height = piece_image.get_size()
                max_size = min(square_size - 25, piece_width, piece_height)
                # If bigger than square_size, resize it to 55x55
                piece_image = pygame.transform.smoothscale(piece_image, (max_size, max_size))
                # Make sure the chess pieces are centered on the squares
                center = (col * square_size + square_size // 2 - max_size // 2, row * square_size + square_size // 2 - max_size // 2)

                screen.blit(piece_image, center)

    # Update the display
    pygame.display.flip()

def main():

    chessboard = ChessBoard()

    # Define some colors
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    GREY = (128, 128, 128)

    # Set up the display
    pygame.init()
    screen = pygame.display.set_mode((640, 640))
    pygame.display.set_caption("Chess")

    # Load the chess pieces
    pieces = {
        str(Pawn("white")): pygame.image.load("images/white_pawn.png"),
        str(Rook("white")): pygame.image.load("images/white_rook.png"),
        str(Knight("white")): pygame.image.load("images/white_knight.png"),
        str(Bishop("white")): pygame.image.load("images/white_bishop.png"),
        str(Queen("white")): pygame.image.load("images/white_queen.png"),
        str(King("white")): pygame.image.load("images/white_king.png"),
        str(Pawn("black")): pygame.image.load("images/black_pawn.png"),
        str(Rook("black")): pygame.image.load("images/black_rook.png"),
        str(Knight("black")): pygame.image.load("images/black_knight.png"),
        str(Bishop("black")): pygame.image.load("images/black_bishop.png"),
        str(Queen("black")): pygame.image.load("images/black_queen.png"),
        str(King("black")): pygame.image.load("images/black_king.png"),
    }

    # Define the chess board
    board = chessboard.current_position

    # Define the square size
    square_size = 80

    # Draw the chess board
    redraw(screen, board, pieces, square_size, WHITE, GREY)

    # Update the display
    pygame.display.flip()

    # Main game loop
    selected_piece = 0
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                # if playr is in check and checkmate, game over

                # Get the clicked square
                x, y = pygame.mouse.get_pos()
                row = y // square_size
                col = x // square_size

                if selected_piece == 0:
                    # If no piece is selected, select the piece on the clicked square
                    selected_piece = board[row][col]

                    if selected_piece != 0 and selected_piece.color != chessboard.current_player:
                        selected_piece = 0

                    elif selected_piece != 0 and selected_piece.color == chessboard.current_player:
                        # Highlight the possible moves for the selected piece
                        moves, caps = selected_piece.possible_moves(chessboard, pre_check =True)
                        for move in (moves+caps):
                            # Highlight the possible moves for the selected piece and lower the saturation of the highlighted squares
                            pygame.draw.rect(screen, (255, 204, 229), [move[1] * square_size, move[0] * square_size, square_size, square_size])

                        pygame.display.flip()
                    else:
                        # If no white piece is on the clicked square, do nothing
                        pass
                elif selected_piece != 0 and selected_piece.color == chessboard.current_player:
                    # If a piece is already selected, try to move the selected piece to the clicked square
                    # moves, caps = selected_piece.possible_moves(row, col, selected_piece, chessboard)
                    if (row, col) in moves or caps:
                        # Move the selected piece to the clicked square
                        selected_piece.move(row, col, chessboard)
                        # Switch players

                        chessboard.current_player = "black" if chessboard.current_player == "white" else "white"
                        if chessboard.is_check():
                            print("Check!")
                            print(chessboard.is_checkmate())
                            if chessboard.is_check() and chessboard.is_checkmate():
                                print(f"GG, {chessboard.current_player} wins")
                                pygame.quit()
                                sys.exit()
                        selected_piece = 0
                        # Redraw the board
                        redraw(screen, board, pieces, square_size, WHITE, GREY)
                    else:
                        # If the clicked square is not a valid move for the selected piece, do nothing
                        selected_piece = 0
                        redraw(screen, board, pieces, square_size, WHITE, GREY)
                else:
                    selected_piece = 0
                    redraw(screen, board, pieces, square_size, WHITE, GREY)

if __name__ == '__main__':
    main()


from typing import Any, List, Dict

class Config:
    """
    Contains parsed yaml config
    """
    def __init__(self, config_yaml: Any) -> None:
        # self.query: Dict[str, TableConfig] = {}

        self._parse_conf(config_yaml)

    def _parse_conf(self, conf_yaml: Any) -> None:
        """
        Parses yaml config and init python structures
        :param conf_yaml: config
        :return: None
        """
        for conf_name, conf_dict in conf_yaml.items():
            if conf_name == 'sources':
                self._parse_sources_conf(conf_dict)

    def _parse_sources_conf(self, conf_yaml: dict):
        """
        Parses "sources" config params
        :param conf_yaml: config
        :return: None
        """
        for conf_name, conf_dict in conf_yaml.items():
            if conf_name == 'relational_db':
                self.querys = _get_key_2_conf(conf_dict, QueryConfig)

class QueryConfig:
    """
    Parses table config. Example:
        user_table:
          db: 'datatp'
          schema: 'detail'
          connector_type: 'mysql_db'
          query: 'select * from [schema].[name]'
    """
    db: str
    schema: str
    connector_type: str
    query: str

    def __init__(self, conf: Dict):
        self.schema = conf.get('schema', '')
        self.name = conf.get('name', '')
        self.storage_key = conf.get('storage', '')
        self.storage_type = conf.get('connector_type', '')
        self.query_template = conf.get('query_template', '')
        self.expected_columns = conf.get('expected_columns', [])
        self.allow_empty = True if conf.get('allow_empty', 'no') == 'yes' else False

class TableConfig:
    """
    Parses table config. Example:
        user_table:
          schema: 'trading_2018'
          name: 'All_Users_Table'
          storage: 'trading_db'
          connector_type: 'mock'
          expected_columns: [ 'LOGIN', 'NAME' ]
          query_template: 'select * from [schema].[name]'
    """
    schema: str
    name: str
    storage_key: str
    storage_type: str
    query_template: str
    expected_columns: List[str]

    def __init__(self, conf: Dict):
        self.schema = conf.get('schema', '')
        self.name = conf.get('name', '')
        self.storage_key = conf.get('storage', '')
        self.storage_type = conf.get('connector_type', '')
        self.query_template = conf.get('query_template', '')
        self.expected_columns = conf.get('expected_columns', [])
        self.allow_empty = True if conf.get('allow_empty', 'no') == 'yes' else False

def _get_key_2_conf(conf_dict: dict, class_name: Any) -> Dict[str, Any]:
    """
    Parses deep yaml structures into key-class_object structure
    :param conf_dict: structures config
    :param class_name: structure, that describes in config
    :return: key-class_object
    """
    key_2_conf_obj = {}
    for key, conf in conf_dict.items():
        key_2_conf_obj[key] = class_name(conf)
    return key_2_conf_obj


from accounts.serializers import UserSerializer
from django.shortcuts import redirect
from django.conf import settings
from django.contrib.auth import get_user_model

from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework_jwt.settings import api_settings

from .utils import google_obtain_access_token, google_get_user_info, user_get_or_create, jwt_login, get_user_info

BASE_URL = settings.BASE_URL
BASE_FRONTEND_URL= settings.BASE_FRONTEND_URL
LOGIN_URL = f'{BASE_URL}/accounts/auth/login'

UserModel = get_user_model()

class GetUserApi(APIView):
    """
    Determine current user. Return user name, email
    and profile image
    """
    permission_classes = [AllowAny]

    def get(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            return Response(get_user_info(user=request.user))
        return Response(status=status.HTTP_204_NO_CONTENT)

class GoogleLoginAPI(APIView):
    """
    Manage login with Google
    Get token from request and obtain user information: email,
    user name and profile image
    """
    permission_classes = []

    class InputSerializer(serializers.Serializer):
        code = serializers.CharField(required=True)

    def get(self, request, *args, **kwargs):
        input_serializer = self.InputSerializer(data=request.GET)
        input_serializer.is_valid(raise_exception=True)

        validated_data = input_serializer.validated_data
        code = validated_data.get('code')

        if not code:
            return redirect(f'{LOGIN_URL}?error')

        redirect_uri = f'{LOGIN_URL}/google'
        access_token = google_obtain_access_token(
            code=code, redirect_uri=redirect_uri)

        user_info = google_get_user_info(access_token)

        profile_data = {
            'first_name': user_info.get('given_name'),
            'last_name': user_info.get('family_name'),
            'email': user_info.get('email'),
            'profile_image': user_info.get('picture'),
        }

        user = user_get_or_create(profile_data)

        res = redirect(BASE_FRONTEND_URL)
        res = jwt_login(response=res, user=user)
        return res

class LogoutAPI(APIView):
    """
    Log out user by removing JWT cookie header
    """
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        response = Response(status=status.HTTP_202_ACCEPTED)
        params = {
            'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
            'domain': api_settings.JWT_AUTH_COOKIE_DOMAIN,
            'path': api_settings.JWT_AUTH_COOKIE_PATH,
            'secure': api_settings.JWT_AUTH_COOKIE_SECURE,
            'samesite': api_settings.JWT_AUTH_COOKIE_SAMESITE,
            'httponly': True
        }
        response.set_cookie(api_settings.JWT_AUTH_COOKIE, **params)
        return response

class SignUpUserApi(CreateAPIView):
    """
    Create new user with email and password and log user in
    """
    serializer_class = UserSerializer
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)

        email = serializer.data['email']
        user = UserModel.objects.get(email=email)

        res = Response(serializer.data, status=status.HTTP_201_CREATED)
        res = jwt_login(response=res, user=user)

        return res


from tkinter import *

import Mediator
from Model.BoardModel import BoardModel
from View.BoardView import BoardView
from Controller.GemController import GemController
from random import randint
from Handler import Handler

class BoardController:
    """
    The main class that is responsible for the board.
    The functions of the class work both on the graphical interface and on the logical part.

    Creates classes BoardModel, BoardView and controls them from the outside.
    """
    _BOARD_HEIGHT = 288
    _BOARD_WIDTH = 288
    _BACKGROUND_COLOR = "blue"
    _GEM_TYPE_MIN_VALUE = 1
    _GEM_TYPE_MAX_VALUE = 7

    def __init__(self, root: Tk, width: int, height: int, mediator: Mediator):
        """
        initializes the canvas for further work with it.
        creates instances of Board classes.
        Generates the correct random playing field
        """
        self._root = root
        self._width = width
        self._height = height

        self._create_canvas()
        self._score_points = 0

        self.board_model = BoardModel(width, height)
        self._board_view = BoardView(root, width, height, self._canvas, mediator)

        self._control = Handler(self)
        self._set_random_board()

    def draw(self):
        """shows graphic elements"""
        self._board_view.draw_board()

    def hide(self):
        """hides graphic elements"""
        self._board_view.hide_board()

    def swap_gems(self, gem1, gem2):
        """"Replaces gems, removes steaks and updates statistics"""
        self.board_model.swap_gems(gem2, gem1)
        self._update_points()

    def _update_points(self):
        self._score_points = self.board_model.get_points()  # receives data processing
        self._board_view.update_score(self._score_points)

    def _create_canvas(self):
        self._canvas = Canvas(self._root, background=self._BACKGROUND_COLOR, width=self._BOARD_WIDTH,
                              height=self._BOARD_HEIGHT, relief=SOLID, borderwidth=2)

    def _set_gem(self, row, column, gem_controller):
        self.board_model.set_gem(row, column, gem_controller)
        self._board_view.set_gem(row, column, gem_controller)

    def _set_random_board(self):
        for i in range(self._width):
            for j in range(self._height):
                rand_type = self._get_random_gem_type()
                gem_controller = GemController(self._root, rand_type, i, j, self._control, self._canvas)
                self._set_gem(i, j, gem_controller)  # uses a random stone to initialize the field

        self.board_model.fix_board()  # removes groups of gems more than 3

    def _get_random_gem_type(self):
        return randint(self._GEM_TYPE_MIN_VALUE, self._GEM_TYPE_MAX_VALUE)

    def destroy(self):
        """cleans and destroys the game board"""
        self._canvas.destroy()


"""
Module containing the constants needed for the numerical solution of the laplace equation.
"""

# DIMENSIONS OF THE PLATES [CM]

l_y = 5     # Side 1
l_z = 10    # Side 2
d = 1       # Separation in between plates

# DIMENSIONS OF THE BOX [CM]

L_X = 10
L_Y = 15
L_Z = 30

# POTENTIALS [V]

V_1 = 10
V_2 = -5

# MESHING PARAMETERS

ds = 0.1

x_min = -5
x_max = 5
M_X = 101

y_min = -7.5
y_max = 7.5
M_Y = 151

z_min = -15
z_max = 15
M_Z = 301

r_tol = 10e-2


#!/usr/bin/env python3
import os
import sys
from pathlib import Path
import logging

from pdf_tool import PDF_Tool

from form import *
from PySide2.QtWidgets import QApplication, QMainWindow
from PySide2.QtCore import Qt, QObject, QEvent
from PySide2.QtGui import QIcon, QMouseEvent

os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
logger = logging.getLogger()
logger.setLevel(logging.INFO)

class TestListView(QListWidget):
    fileDropped = Signal(list)

    def __init__(self, parent=None):
        super(TestListView, self).__init__(parent)
        self.setAcceptDrops(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setIconSize(QSize(72, 72))
        self.file_paths = []
        self.files = []

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls:
            event.accept()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls:
            event.setDropAction(Qt.CopyAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        if event.mimeData().hasUrls:
            event.setDropAction(Qt.CopyAction)
            event.accept()

            self.files = [u.toLocalFile() for u in event.mimeData().urls() if u.toLocalFile()[-4:] == '.pdf']
            difference = list(set(self.files) - set(self.file_paths))
            if difference:
                self.fileDropped.emit(difference)
                self.file_paths.extend(difference)
        else:
            event.ignore()

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self)
        self.old_position = None
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.header.installEventFilter(self)
        self.ui.view.installEventFilter(self)
        self.setWindowIcon(QIcon('icons/pdf.ico'))

        # frameless window
        flags = Qt.WindowFlags(Qt.FramelessWindowHint | Qt.WindowMaximizeButtonHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setWindowFlags(flags)

        # button click events
        self.ui.maximize_button.clicked.connect(self.window_full_screen)
        self.ui.exit_button.clicked.connect(self.close)
        self.ui.minimize_button.clicked.connect(self.showMinimized)
        self.ui.search_button.clicked.connect(self.get_files)
        self.ui.word_button.clicked.connect(self.extract_to_docx)
        self.ui.image_button.clicked.connect(self.extract_images)
        self.ui.text_botton.clicked.connect(self.extract_text)
        self.ui.view.fileDropped.connect(self.picture_dropped)
        self.ui.split_button.clicked.connect(self.split_files)
        self.ui.merge_button.clicked.connect(self.merge_files)

    # event filter
    def eventFilter(self, object: QObject, event: QMouseEvent) -> bool:
        if object.objectName() == 'header':
            if event.type() == QEvent.MouseButtonDblClick:
                self.window_full_screen()
                return True

            if event.type() == QEvent.MouseButtonPress:
                self.old_position = event.globalPos()
                return True

            if event.type() == QEvent.MouseMove:
                delta = QPoint(event.globalPos() - self.old_position)
                self.move(self.x() + delta.x(), self.y() + delta.y())
                self.old_position = event.globalPos()
                return True
        if event.type() == QEvent.KeyPress:
            key = event.key()
            if key == Qt.Key_Backspace or key == Qt.Key_Delete:
                self.delete_from_list()
                return True

        return QMainWindow.eventFilter(self, object, event)

    def window_full_screen(self):
        self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)

    def get_files(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.ExistingFiles)
        dlg.setNameFilters(["Pdf files (*.pdf)"])

        if dlg.exec_():
            self.ui.view.files = dlg.selectedFiles()
            difference = list(set(self.ui.view.files) - set(self.ui.view.file_paths))
            if difference:
                self.ui.view.fileDropped.emit(difference)
                self.ui.view.file_paths.extend(difference)

    def extract_to_docx(self):
        error = False
        if self.ui.view.file_paths:
            for index, file in enumerate(self.ui.view.file_paths):
                path = Path(file)
                output_path = '{}/{}-output/'.format(path.parent, path.stem)

                if not os.path.exists(output_path):
                    os.makedirs(output_path)

                docx_file = '{}{}.docx'.format(output_path, path.stem)
                try:
                    PDF_Tool.convert_to_docx(file, docx_file)
                except Exception as e:
                    logger.error(e)
                    error = True
                    QMessageBox.critical(self, 'Fehler!', 'Es ist ein Fehler aufgetreten')

            if not error:
                error = False
                QMessageBox.information(self, 'Info', "Alles erfolgreich erstellt")
        else:
            QMessageBox.warning(
                self,
                "Fehler!",
                "Es ist kein Pfad ausgewählt",
                defaultButton=QMessageBox.Ok,
            )

    def extract_images(self):
        error = False
        if self.ui.view.file_paths:
            for index, file in enumerate(self.ui.view.file_paths):
                path = Path(file)
                output_path = '{}/{}-output/images'.format(path.parent, path.stem)

                if not os.path.exists(output_path):
                    os.makedirs(output_path)

                try:
                    PDF_Tool.extract_images(file, output_path)
                except Exception as e:
                    logger.error(e)
                    error = True
                    QMessageBox.critical(self, 'Fehler!', 'Es ist ein Fehler aufgetreten')
            if not error:
                error = False
                QMessageBox.information(self, 'Info', "Alles erfolgreich erstellt")
        else:
            QMessageBox.warning(
                self,
                "Fehler!",
                "Es ist kein Pfad ausgewählt",
                defaultButton=QMessageBox.Ok,
            )

    def extract_text(self):
        error = False
        if self.ui.view.file_paths:
            for index, file in enumerate(self.ui.view.file_paths):
                path = Path(file)
                output_path = '{}/{}-output/'.format(path.parent, path.stem)

                text_file = '{}{}.txt'.format(output_path, path.stem)
                if os.path.exists(text_file):
                    os.remove(text_file)

                try:
                    PDF_Tool.convert_to_txt(file, text_file)
                except Exception as e:
                    error = True
                    logger.error(e)
                    QMessageBox.critical(self, 'Fehler!', 'Es ist ein Fehler aufgetreten')
            if not error:
                error = False
                QMessageBox.information(self, 'Info', "Alles erfolgreich erstellt")
        else:
            QMessageBox.warning(
                self,
                "Fehler!",
                "Es ist kein Pfad ausgewählt",
                defaultButton=QMessageBox.Ok,
            )

    def split_files(self):
        error = False
        if self.ui.view.file_paths:
            for index, file in enumerate(self.ui.view.file_paths):
                output_path = Path(file)
                output_path = '{}/{}-output/einzelne-seiten'.format(output_path.parent, output_path.stem)
                if not os.path.exists(output_path):
                    os.makedirs(output_path)
                try:
                    PDF_Tool.split_files(file, output_path)

                except Exception as e:
                    error = True
                    logger.error(e)
                    QMessageBox.critical(self, 'Fehler!', 'Es ist ein Fehler aufgetreten')

            if not error:
                error = False
                QMessageBox.information(self, 'Info', "Alles erfolgreich erstellt")

        else:
            QMessageBox.warning(
                self,
                "Fehler!",
                "Es ist kein Pfad ausgewählt",
                defaultButton=QMessageBox.Ok,
            )

    def merge_files(self):

        if self.ui.view.file_paths:
            path = Path(self.ui.view.file_paths[0])
            text, ok = QInputDialog.getText(self, 'Pdf-Files vereinen', 'Name eingeben')
            if ok:
                try:
                    output_path = '{}/{}.pdf'.format(str(path.parent), text)
                    PDF_Tool.merge_files(self.ui.view.file_paths, output_path)
                    QMessageBox.information(self, 'Info', "Alles erfolgreich erstellt")
                except Exception as e:
                    logger.error(e)
                    QMessageBox.critical(self, 'Fehler!', 'Es ist ein Fehler aufgetreten')

        else:
            QMessageBox.warning(
                self,
                "Fehler!",
                "Es ist kein Pfad ausgewählt",
                defaultButton=QMessageBox.Ok,
            )

    def delete_from_list(self):
        items = self.ui.view.selectedItems()
        if items:
            for index, item in reversed(list(enumerate(items))):
                item_text = str(self.ui.view.selectedItems()[index].text())
                list_index = self.ui.view.file_paths.index(item_text)
                self.ui.view.takeItem(list_index)
                self.ui.view.file_paths.remove(item_text)
        print(self.ui.view.file_paths)

    def picture_dropped(self, files):

        for url in files:
            if os.path.exists(url):
                icon = QIcon(url)
                pixmap = icon.pixmap(72, 72)
                icon = QIcon(pixmap)
                item = QListWidgetItem(url, self.ui.view)
                item.setIcon(icon)
                item.setStatusTip(url)

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


from il_utils import *

def make_cfg(insFile,varsFile,printIns=False):
    cfg={}
    #load all instructions
    instructions,varmap=load_il(insFile,varsFile)

    gen_ins=('Plus','Minus','Times','Greater','And','Or','GreaterEq','Equal','Not','Move')

    check_gen=lambda i,f: True if (f(i)!='null' and op(i) in gen_ins and f(i) in list(varmap)) else False
    check_kill=lambda i: True if (output(i)!='null' and output(i) in list(varmap)) else False
    gen=lambda b: set([varmap[i0(i)] for i in instructions_in_block(b,instructions) if check_gen(i,i0)]+
                           [varmap[i1(i)] for i in instructions_in_block(b,instructions) if check_gen(i,i1)])
    kill=lambda b: set([varmap[output(i)] for i in instructions_in_block(b,instructions) if check_kill(i)])

    def connect(i,x,edge_annotation=''):
        assert i!=x,'self-connection detected'
        if block(i) not in cfg: cfg[block(i)]={'succ':[],'succ_labels':[]}
#         if x not in [z[0] for z in cfg[block(i)]]:
        cfg[block(i)]['succ'].append(x)
        cfg[block(i)]['succ_labels'].append(edge_annotation)

    if printIns: print_ins(instructions)

    #main loop
    for ni,i in enumerate(instructions[:-1]):
        next_i=instructions[ni+1]
        if ni>0: prev_i=instructions[ni-1]
        if op(i) == 'Jump': connect(i,jumpto_uncond(i,instructions),edge_annotation=' j ')
        if op(i) in ('JumpIfNonzero','JumpIfZero'):
            connect(i,jumpto_cond(i,instructions),edge_annotation=' if ')
            connect(i,block(next_i),edge_annotation=' else ')
        if (block(i) != block(next_i)) and (op(i)[:4]!='Jump'): connect(i,block(next_i))

    for i in set([block(j) for j in instructions]):
        if i not in cfg:cfg[i]={'succ':[],'succ_labels':[]}
        cfg[i]['gen']=gen(i)
        cfg[i]['kill']=kill(i)

    return cfg


# import tcod
from random import randint
from game_messages import Message

class BasicMonster:
    def take_turn(self, target, game_map, entities):
        results = []

        monster = self.owner
        if monster.distance_to(target) >= 2:
            # monster.move_astar(target, entities, game_map)
            monster.move_towards(target.x, target.y, game_map, entities)

        elif target.fighter.hp > 0:
            attack_results = monster.fighter.attack(target)
            results.extend(attack_results)

        return results

class ConfusedMonster:
    def __init__(self, previous_ai, number_of_turns=10):
        self.previous_ai = previous_ai
        self.number_of_turns = number_of_turns

    def take_turn(self, target, game_map, entities):
        results = []
#        if self.owner.name == 'wall':
#            game_map.tiles[self.owner.x][self.owner.y].blocked = False
#            game_map.tiles[self.owner.x][self.owner.y].block_sight = False
        if self.number_of_turns > 0:
            random_x = self.owner.x + randint(0, 2) - 1
            random_y = self.owner.y + randint(0, 2) - 1

            if random_x != self.owner.x and random_y != self.owner.y:
                self.owner.move_towards(random_x, random_y, game_map, entities)

            # self.number_of_turns -= 1
        else:
            self.owner.ai = self.previous_ai
            results.append({'message': Message(
                'The {0} is no longer confused'.format(self.owner.name),
                'red')})
        return results


from nltk import CFG
from nltk import ChartParser # parse_cfg, ChartParser
from random import choice
import re
from enum import Enum, auto
from argparse import ArgumentParser
from os import listdir
from os.path import isfile, join
import os

this_dir = os.path.dirname(os.path.abspath(__file__))
name_segment_folder = join(this_dir, "../../name-segments/")

class EnumAutoName(Enum):
    # An enum where auto() will default to the enum name
    def _generate_next_value_(name, start, count, last_values):
        return name

    def __str__(self):
        return self.value

def StringToEnum(s):
    if s in [v.name for v in Name.NameBank]:
        return Name.NameBank[s]
    else:
        raise ValueError

def get_files_from_path(path, extension_filter=".txt"):
    files = []
    for f in listdir(path):
        full_f = join(path, f)
        if extension_filter in full_f:
            if isfile(full_f):
                files.append(f)
    return files

def get_available_namebanks_and_syllables():
    namebanks = get_available_namebanks()
    syllables = get_available_syllables()
    # return namebanks | syllables   # Nicer 3.9 Python syntax
    return {**namebanks, **syllables}

def get_available_syllables(where="syllables"):
    global name_segment_folder
    path = join(name_segment_folder,where)
    onlyfiles = get_files_from_path(path)
    available = {}
    for f in onlyfiles:
        f = f.replace(".txt","")
        f = f.replace("-female","")
        f = f.replace("-male","")
        for x in range(10):
            f = f.replace(f"-{x}","")
        f = f.capitalize()
        available[f] = auto()
    return available

def get_available_namebanks(where="forenames"):
    global name_segment_folder
    path = join(name_segment_folder,where)
    onlyfiles = get_files_from_path(path)
    available = {}
    for f in onlyfiles:
        f = f.replace(".txt","")
        f = f.replace("-female","")
        f = f.replace("-male","")
        f = f.capitalize()
        available[f] = auto()
    return available

def get_available_origins(where="nouns"):
    global name_segment_folder
    path = join(name_segment_folder,where)
    onlyfiles = get_files_from_path(path)
    available = {}
    for f in onlyfiles:
        f = f.replace(".txt","")
        f = f.replace("-female","")
        f = f.replace("-male","")
        f = f.capitalize()
        available[f] = auto()
    return available

class Name:
    class NameOrder(EnumAutoName):
        Eastern = auto()
        Forename_Only = auto()
        Surname_Only = auto()
        Western = "Western"

    namebank_values = get_available_namebanks_and_syllables()
    NameBank = EnumAutoName('NameBank', namebank_values)

    origin_values = get_available_origins()
    Origin = EnumAutoName('Origin', origin_values)

    class NameType(EnumAutoName):
        Forename = auto()
        Surname = auto()

    class Origin(EnumAutoName):
        Aquatic = auto()
        Desert = auto()
        Mountain = auto()
        Tundra = auto()
        Urban = auto()
        Forest = auto()
        Air = auto()

    def __init__(self):
        self.gender_male = False
        self.gender_female = False
        self.gender_neutral = False

        self.has_position = False
        self.order = Name.NameOrder.Western

class FileFetcher():
    def __init__(self):
        pass

    def get_gender_endings(self, config, always_neutral=False):
        e = []
        if config.gender_male:
            e.append("male")
        if config.gender_female:
            e.append("female")
        if config.gender_neutral or always_neutral:
            e.append("")
        if len(e) == 0:
            print("No Gender Selection. Defaulting to gender neutral")
            config.gender_neutral = True
            e.append("")
        return e

    def get_position_files(self, config):
        ges = self.get_gender_endings(config)
        pt = []
        for g in ges:
            g = f"-{g}" if g != "" else g
            pt.append(f'prefixes/positions{g}.txt')

        return pt

    def SyllableLength(self, namebank):
        global name_segment_folder
        path = join(name_segment_folder,"syllables")
        onlyfiles = get_files_from_path(path)
        unique_syllables = {}
        for f in onlyfiles:
            if namebank in f:
                f = f.replace("-female","")
                f = f.replace("-male","")
                unique_syllables[f] = True
        return len(unique_syllables.items())

class Grammar:
    def __init__(self, config):
        self.config = config
        self.obj = {}
        self.root = "S"
        self.ff = FileFetcher()

    def initialize(self):
        self.obj[self.root]= ["PRE", "CORE", "POST"]

        self.basic_tokens()

    def basic_tokens(self):
        self.obj["SPC"] = ["' '"]
        self.obj["OF"] = ["'o'", "'f'"]

    def define_position(self, config, optional=False):

        # Prefix
        positions = self.ff.get_position_files(config)
        positions = [f"['{p}']" for p in positions]
        self.obj["PRE"] = ["TITLE", "SPC"]
        if optional:
            self.obj["PRE"].append(None)

        self.obj["TITLE"] = positions

        # Postfix
        origin = config.origin.name.lower()

        self.obj["POST"] = ["SPC", "OF", "SPC", "WHERE"]
        if optional:
            self.obj["POST"].append(None)
        # TODO: Allow multiple origins
        self.obj["WHERE"] = [f"['postfixes/{origin}.txt']",]

    def setNameOrder(self, order):
        if order == Name.NameOrder.Western:
            self.obj["CORE"] = ["FORENAME", "SPC", "SURNAME"]
        elif order == Name.NameOrder.Eastern:
            self.obj["CORE"] = ["SURNAME", "SPC", "FORENAME"]
        elif order == Name.NameOrder.Forename_Only:
            self.obj["CORE"] = ["FORENAME"]
        elif order == Name.NameOrder.Surname_Only:
            self.obj["CORE"] = ["FORENAME"]
        else:
            print("Unimplemented Name Order: ", order, ". Defaulting to Western")
            self.setNameOrder(Name.NameOrder.Western)

    def getNamesFromSyllables(self, config, name_type):
        ges = self.ff.get_gender_endings(config)
        namebank = config.namebank.name.lower()
        name_type = name_type.name.upper()

        global name_segment_folder

        # TODO: Check compatibile with namebanks
        syls = self.ff.SyllableLength(namebank)
        self.obj[name_type] = []
        for x in range(syls):
            self.obj[name_type].append(f"SYLLABLE{x}")

        for x in range(syls):
            pt = []
            for g in ges:
                g = f"-{g}" if g != "" else g
                f = f'syllables/{namebank}{g}-{x}.txt'
                if os.path.exists(join(name_segment_folder, f)):
                    pt.append(f)
                else:
                    print(f"Warn/Err: No syllable file found: {f}. May produce bad name.")
            self.obj[f"SYLLABLE{x}"] = [pt]

    def getNamesFromBank(self, config, name_type):
        ges = self.ff.get_gender_endings(config)
        namebank = config.namebank.name.lower()
        name_type = name_type.name.upper()

        pt = []
        for g in ges:
            g = f"-{g}" if g != "" else g
            # TODO: s shouldnt be there.
            pt.append(f'{name_type.lower()}s/{namebank}{g}.txt')
        self.obj[name_type] = [pt]

    def constructName(self, config, name_type):
        origin = config.origin.name.lower()
        name_type = name_type.name.upper()

        self.obj[name_type] = ["ADJ", "NOUN"]

        self.buildAdjBank(config)
        self.buildNounBank(config)

    def buildAdjBank(self, config):
        origin = config.origin.name.lower()

        pt = []
        # TODO: Dodginess/Alignment. John Bloodsword seems more evil than John Goldheart
        pt.append(f"['adjectives/{origin}.txt']")

        self.obj["ADJ"] = pt

    def buildNounBank(self, config):
        origin = config.origin.name.lower()

        pt = []
        # TODO: Dodginess/Alignment. John Poisonblood seems more evil than John Goldheart
        pt.append(f"['nouns/{origin}.txt']")
        self.obj["NOUN"] = pt

    def write(self, dir="", filename="custom.grammar"):
        # TODO: order carefully
        s = ""
        for key, value in self.obj.items():
            s += f"{key} -> "
            for i, v in enumerate(value):
                sep = " "
                if v is None:
                    v = " | "
                s += f"{v}{sep}"
            s += "\n"

        self.string_repr = s

        filename = os.path.join(dir, filename)
        print("---------------------")
        print(filename)
        print(filename)
        print(filename)
        print(filename)
        print("---------------------")

        f = open(filename, "w")
        f.write(s)
        f.close()
        filename = os.path.abspath(filename)
        return filename

    def __str__(self):
        if hasattr(self, "string_repr"):
            return self.string_repr
        else:
            return "Not Finalized"

def define_grammar(config, where=""):

    grammar = Grammar(config)
    grammar.initialize()
    if config.has_position:
        grammar.define_position(config)
    grammar.setNameOrder(config.order)

    # Prefer Forenames to be syllable generated
    if grammar.ff.SyllableLength(config.namebank.name.lower()) > 0:
        grammar.getNamesFromSyllables(config, Name.NameType.Forename)
    else:
        grammar.getNamesFromBank(config, Name.NameType.Forename)

    # TODO: Use namebank for Surnames
    grammar.constructName(config, Name.NameType.Surname)

    return grammar.write(where)

def resolve_grammar(G):
    def file_contents(s):
        global name_segment_folder
        filename = join(name_segment_folder,str(s.group(1)))

        try:
            terms = open(filename).readlines()
            s = ""
            for i, t in enumerate(terms):
                t = t.replace("\n","")
				# Allow Commenting
                if "#" not in t:
                    seperator = "|" if i > 0 else ""
                    s += f"{seperator} '{t}' "
        except FileNotFoundError:
            print("Warn/Err: File doesn't exist:", filename, ". May produce bad names.")
            s = ""
        return s

    G = re.sub(r"\[\'([a-zA-Z\-\.\/0-9]*)\'\]", file_contents, G)
    return G

def generate_name(G, ):
    grammar = CFG.fromstring(G)

    parser = ChartParser(grammar)

    gr = parser.grammar()
    tokens = produce(gr, gr.start())
    name = ''.join(tokens)
    return name.title()

def produce(grammar, symbol):
    words = []
    productions = grammar.productions(lhs = symbol)
    production = choice(productions)
    for sym in production.rhs():
        if isinstance(sym, str):
            words.append(sym)
        else:
            words.extend(produce(grammar, sym))
    return words

def generate(args, where=""):
    config = Name()
    config.has_position = True
    config.origin = args.origin
    config.namebank = args.namebank
    config.order = args.order
    config.gender_male = args.gender_male
    config.gender_female = args.gender_female
    config.gender_neutral = args.gender_neutral
    grammar_file = define_grammar(config, where)

    G = resolve_grammar(open(grammar_file).read())
    name = generate_name(G)

    if args.verbose:
        print("Your Character:", name)
    return name

def parse_args():
    ap = ArgumentParser(description="Generate a character name")

    # Gender
    ap.add_argument('--gender-male', action="store_true")
    ap.add_argument('--gender-female', action="store_true")
    ap.add_argument('--gender-neutral', action="store_true")

    # Origins
    ap.add_argument('--order', type=Name.NameOrder, choices=list(Name.NameOrder), nargs="?", default=Name.NameOrder.Western)
    ap.add_argument('--origin', type=Name.Origin, choices=list(Name.Origin), nargs="?", default=Name.Origin.Mountain)
    ap.add_argument('--namebank', type=Name.NameBank, choices=Name.NameBank, nargs="?", default=Name.NameBank.Dwarf)
    args = ap.parse_args()
    args.verbose = True
    return args

if __name__ == "__main__":
    a = parse_args()
    generate(a)


import re as _re
from argparse import *

from .. import path as _path

_ArgumentParser = ArgumentParser
_Action = Action

# Add some simple wrappers to make it easier to specify shell-completion
# behaviors.

def _add_complete(argument, complete):
    if complete is not None:
        argument.complete = complete
    elif isinstance(argument.type, File):
        argument.complete = 'file'
    elif isinstance(argument.type, Directory):
        argument.complete = 'directory'
    return argument

class Action(_Action):
    def __init__(self, *args, complete=None, **kwargs):
        super().__init__(*args, **kwargs)
        _add_complete(self, complete)

class ToggleAction(Action):
    def __init__(self, option_strings, dest, default=False, required=False,
                 complete=None, help=None):
        if len(option_strings) == 0:
            raise ValueError('option string must begin with "--"')

        self.true_strings = [self._prefix(i, self._true_prefix)
                             for i in option_strings]
        self.false_strings = [self._prefix(i, self._false_prefix)
                              for i in option_strings]

        option_strings = self.true_strings + self.false_strings
        super().__init__(option_strings, dest=dest, nargs=0, default=default,
                         required=required, complete=complete, help=help)

    def __call__(self, parser, namespace, values, option_string=None):
        value = option_string in self.true_strings
        setattr(namespace, self.dest, value)

    @staticmethod
    def _prefix(s, prefix):
        if not s.startswith('--'):
            raise ValueError('option string must begin with "--"')
        return _re.sub('(^--(x-)?)', r'\1' + prefix, s)

class EnableAction(ToggleAction):
    _true_prefix = 'enable-'
    _false_prefix = 'disable-'

class WithAction(ToggleAction):
    _true_prefix = 'with-'
    _false_prefix = 'without-'

class BaseFile:
    def __init__(self, must_exist=False):
        self.must_exist = must_exist

    def __call__(self, string):
        p = self._abspath(string)
        if _path.exists(p):
            if not self._check_type(p):
                raise ArgumentTypeError("'{}' is not a {}"
                                        .format(string, self._kind))
        elif self.must_exist:
            raise ArgumentTypeError("'{}' does not exist".format(string))

        return p

class Directory(BaseFile):
    _kind = 'directory'

    @staticmethod
    def _abspath(p):
        return _path.abspath(p, directory=True, absdrive=False)

    @staticmethod
    def _check_type(p):
        return _path.isdir(p)

class File(BaseFile):
    _kind = 'file'

    @staticmethod
    def _abspath(p):
        return _path.abspath(p, directory=False, absdrive=False)

    @staticmethod
    def _check_type(p):
        return _path.isfile(p)

class ArgumentParser(_ArgumentParser):
    @staticmethod
    def _wrap_complete(action):
        def wrapper(*args, complete=None, **kwargs):
            return _add_complete(action(*args, **kwargs), complete)

        return wrapper

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for k, v in self._registries['action'].items():
            self._registries['action'][k] = self._wrap_complete(v)
        self.register('action', 'enable', EnableAction)
        self.register('action', 'with', WithAction)

    def _get_option_tuples(self, option_string):
        # Don't try to check prefixes for long options; this is similar to
        # Python 3.5's `allow_abbrev=False`, except this doesn't break combined
        # short options. See <https://bugs.python.org/issue26967>.
        if option_string[:2] == self.prefix_chars * 2:
            return []

        return super()._get_option_tuples(option_string)

# It'd be nice to just have a UserArgumentParser class with this method but it
# wouldn't propagate to argument groups, so it's easier to just do it this way.
def add_user_argument(parser, *names, **kwargs):
    if any(not i.startswith('--') for i in names):
        raise ValueError('option string must begin with "--"')
    if any(i.startswith('--x-') for i in names):
        raise ValueError('"x-" prefix is reserved')

    if parser.usage == 'parse':
        names += tuple('--x-' + i[2:] for i in names)
    return parser.add_argument(*names, **kwargs)