import os
import sys
import logging
import shutil
from datetime import datetime, timedelta
from logger import get_logger
from config import LOG_LEVEL_STDOUT, LOG_LEVEL_FILE, SAVED_DIR_PATH, BACKUP_DIR_PATH, FILES_TO_BACKUP

# catch unhandled exceptions
logger = get_logger('backup.log', log_level_stdout=LOG_LEVEL_STDOUT, log_level_file=LOG_LEVEL_FILE)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

# save current time
now = datetime.utcnow()
if LOG_LEVEL_STDOUT > logging.INFO:
    print("Checking if backup is required...")
logger.info("Checking if backup is required...")

# try getting the date of the last entry of the logfile
logfile = os.path.join(SAVED_DIR_PATH, 'Logs', 'ConanSandbox.log')
try:
    with open(logfile, 'r') as f:
        lines = f.readlines()
        log_dt = datetime.strptime(lines[-1][1:20], '%Y.%m.%d-%H.%M.%S')

# if file doesn't, exist exit with error message
except Exception as e:
    logger.error("Error:", e)
    sys.exit()

# get the list of files and directories at the configured backup path
try:
    directories = os.listdir(BACKUP_DIR_PATH)

# if path doesn't, exist exit with error message
except Exception as e:
    logger.error("Error:", e)
    sys.exit()

# check if an entry for the date of the crash exists starting from the last directory
backupdir = None
directories.sort(reverse=True)
for directory in directories:
    try:
        dir_d = datetime.strptime(directory, '%Y.%m.%d')
    except Exception:
        # not a date, skip over
        continue

    # directory for the date in the logfile already exists
    if dir_d.date() == log_dt.date():
        backupdir = os.path.join(BACKUP_DIR_PATH, directory)
        break

    # if the last directory is older than the logfile, a new directory has to be created
    elif dir_d < log_dt:
        backupdir = os.path.join(BACKUP_DIR_PATH, log_dt.strftime('%Y.%m.%d'))
        os.mkdir(backupdir)
        break

# if no backup directory was either found or created, exit script
if not backupdir:
    logger.error(f"Couldn't find or create backup folder {log_dt.strftime('%Y.%m.%d')} in {BACKUP_DIR_PATH}.")
    sys.exit()

# get the list of files and directories at the path matching the date
directories = os.listdir(backupdir)
directories.sort(reverse=True)
dir_t = None
for directory in directories:
    try:
        dir_t = datetime.strptime(directory, '%H.%M.%S').time()
        break
    except Exception:
        # not a date, skip over
        continue

# if no directory with a valid time is in backupdir, one has to be created
if not dir_t:
    dir_t = log_dt.strftime('%H.%M.%S')
    backupdir = os.path.join(backupdir, dir_t)
    os.mkdir(backupdir)
# if the latest valid timestamp is not within 5 minutes of the time of the log, create a new directory as well
elif (log_dt - timedelta(minutes=5)) > datetime.combine(dir_d, dir_t):
    dir_t = log_dt.strftime('%H.%M.%S')
    backupdir = os.path.join(backupdir, dir_t)
    os.mkdir(backupdir)
# a valid timestamp was found an it's within 5 minutes of the time of the log, DSL backup worked correctly
else:
    backupdir = None

# if a new folder was created, copy all the relevant files there
if backupdir:
    for file in FILES_TO_BACKUP:
        try:
            shutil.copy2(file, backupdir)
            logger.info(f"Copying {os.path.basename(file)} to backup folder.")
        except Exception as error:
            # Errno 2 is 'No such file or directory'
            if error.args[0] == 2:
                pass
            else:
                logger.error(str(error))

execTime = datetime.utcnow() - now
execTimeStr = str(execTime.seconds) + "." + str(execTime.microseconds)
if LOG_LEVEL_STDOUT > logging.INFO:
    print(f"Done! Required time: {execTimeStr} sec.")
logger.info(f"Done! Required time: {execTimeStr} sec.")


import bpy

class T113D_OT_AverageWeight(bpy.types.Operator):
    """Average the weights of the selected vertices"""
    bl_idname = "paint_weight.average"
    bl_label = "Average weight"
    bl_description = "Average the weights of the selected vertices"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        active = context.active_object
        return (
            active is not None
            and active.type == "MESH"
            and len(active.vertex_groups) > 0
            and active.data.use_paint_mask
            or active.data.use_paint_mask_vertex
            and active.vertex_groups.active is not None
        )

    def execute(self, context):

        active = context.active_object
        if active.data.use_paint_mask or active.data.use_paint_mask_vertex:
            group = active.vertex_groups.active

            selected_verts = [v for v in active.data.vertices if v.select]

            weight = 0
            indices = []
            for v in selected_verts:
                try:
                    weight += group.weight(v.index)
                except RuntimeError:
                    weight = weight
                indices.append(v.index)

            weight /= len(selected_verts)

            for v in selected_verts:
                group.add(indices, weight, 'REPLACE')

        return {'FINISHED'}


from django.shortcuts import render, redirect
from .models import Dojo, Ninja

# Create your views here.
def index(request):
    print('-----------this is the index----------')

    context ={
        "all_dojos": Dojo.objects.all(),
        "all_ninjas": Ninja.objects.all()
    }
    return render(request,'index.html', context)

def add_dojo(request):
    Dojo.objects.create(
        name=request.GET['dojo_name'],
        city=request.GET['dojo_city'],
        state=request.GET['dojo_state'])
    return redirect('/')

def add_ninja(request):
    dojo1 = Dojo.objects.get(id=request.GET['dojo_id'])
    print('**************************', dojo1)
    Ninja.objects.create(
        first_name=request.GET['ninja_first_name'],
        last_name=request.GET['ninja_last_name'],
        dojo=dojo1)

    return redirect('/')

    #**next steps**
    # when user enters info
        #create methods to add users
    # add that info into list
        #create a route from both Dojos and Ninjas


import json
import csv
import sys
import os
import random

import json
from ibm_watson import NaturalLanguageUnderstandingV1
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
from ibm_watson.natural_language_understanding_v1 import Features, SentimentOptions, EntitiesOptions

import nltk
from nltk.tokenize.toktok import ToktokTokenizer
from nltk.corpus import stopwords
from nltk.stem.snowball import SnowballStemmer
import re
import ntpath
import json
import tqdm

authenticator = IAMAuthenticator('P6Y6IwvlXMt50mYGmmFXUsbWdgoihk07pc_1WnxQ0h64')
natural_language_understanding = NaturalLanguageUnderstandingV1(
    version='2020-08-01',
    authenticator=authenticator
)

natural_language_understanding.set_service_url('https://api.eu-de.natural-language-understanding.watson.cloud.ibm.com/instances/2fec67fb-e992-4f60-8403-f6f41aad1346')

def path_leaf(path):
    head, tail = ntpath.split(path)
    return tail or ntpath.basename(head)

def gen_dict_extract(key, var):
    if hasattr(var, 'items'):
        for k, v in var.items():
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in gen_dict_extract(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in gen_dict_extract(key, d):
                        yield result

def read_csv( path ):
  try:
    with open(path, encoding='utf-8') as data:
      reader = csv.DictReader(data)
      for row in reader:
        yield row
  except IOError:
    print("ERROR: file [" + path + "] not found/accessible")
    yield ""
    return
  data.close()

if __name__ == "__main__":

  # Some rows exceed the default max python csv field_size_limit of 131072.
  csv.field_size_limit(2147483647)
  sos={}
  sname = os.path.basename(__file__)

  scall = sys.argv[0]
  argc = len(sys.argv)
  if argc < 2:
    print ("Usage: " + scall + " <filename1> [filename2, ...]")
    sys.exit(-2)

  # file_dicts = []
  if not os.path.exists('sentiments_all.json'):
    with open('sentiments_all.json', 'a') as f:
      f.write('[')
  else:
    with open('sentiments_all.json') as f:
      downloaded_sentiments = json.load(f)

  for f in range(1, argc):
    csv_path = sys.argv[f]
    print ( "parsing file [" + csv_path + "]:" )
    for idx, row in tqdm.tqdm(enumerate( read_csv(csv_path) )):
      if len([x for x in downloaded_sentiments if x['file_name'] == os.path.basename(csv_path) and x['file_idx'] == idx]) > 0:
        continue
      try:
          tx = json.loads(row["tx"])
      except Exception as e:
        print("ERROR: failed to parse row #" + str(idx) + " from [" + csv_path + "]")
        break
      so = str(row["so_txt"])
      ht = str(row["ht"])
      date = str(row["pubDateTime"])
      lang = str(row["la"])
      whole_text = ""
      for tx_elem in tx:
        for elem in gen_dict_extract('text', tx_elem):
          whole_text += elem
      whole_text.replace('\t', '')
      whole_text = ' '.join(whole_text.split())
      if len(whole_text) == 0:
        continue
      # language = 'german' if lang == 'de' else 'english'
      # tokenizer = ToktokTokenizer()
      # text_tokens = tokenizer.tokenize(whole_text)
      # pattern = re.compile('[\W_]+')
      # text_tokens = [pattern.sub('', x) for x in text_tokens]
      # text_tokens = [x for x in text_tokens if x.lower() not in stopwords.words(language) and x.isalnum()]
      # stemmer = SnowballStemmer(language)
      # text_tokens = [stemmer.stem(x) for x in text_tokens]
      # file_dicts.append({
      #   'type': 'N',
      #   'file_name': path_leaf(csv_path),
      #   'file_idx': idx,
      #   'lang': lang,
      #   'text': ' '.join(text_tokens)
      # })
      # print(whole_text)
      response_sentiment = natural_language_understanding.analyze(text=whole_text,
                                                                  language='de',
                                                                  features=Features(sentiment=SentimentOptions())).get_result()
      # response_entities = natural_language_understanding.analyze(text=whole_text,
      #                                                             features=Features(entities=EntitiesOptions(mentions=True))).get_result()
      # print(json.dumps(response_sentiment, indent=2))
      # print(json.dumps(response_entities, indent=2))
      # input()
      file_sentiment = {
        'file_name': path_leaf(csv_path),
        'file_idx': idx,
        'sentiment': response_sentiment['sentiment']['document']['label'],
        'score': response_sentiment['sentiment']['document']['score']
      }
      with open('sentiments_all.json', 'a') as f:
        f.write(json.dumps(file_sentiment) + ',\n')
      #print("Current index: {}, so_txt: {}, ht: {}, date: {}".format(idx,so,ht,date))
      #print("{},{},{}".format(idx,date,ht))
      #if (idx > 0) and (idx % 30 == 0):
      #  break
    print ( "parsing file [" + csv_path + "]: found #" + str(idx) + " rows" )
  print("\n------")
  #print("\nTotals:")
  #print(sos)
  #print("\n------")
  #print(eng_arts)


import logging
import platform
from PySide6 import QtWidgets
from os.path import expanduser
from src.Controller.PathHandler import resource_path
import pandas as pd

pd.options.mode.chained_assignment = None  # default='warn'

class MachineLearningDataSelectionOptions(QtWidgets.QWidget):
    def __init__(self):
        """
        Initialise the class
        """
        QtWidgets.QWidget.__init__(self)

        # Create the main layout
        self.main_layout = QtWidgets.QVBoxLayout()

        # Get the stylesheet
        if platform.system() == 'Darwin':
            self.stylesheet_path = "res/stylesheet.qss"
        else:
            self.stylesheet_path = "res/stylesheet-win-linux.qss"
        self.stylesheet = open(resource_path(self.stylesheet_path)).read()

        label_dvh = QtWidgets.QLabel(
            "Please choose the input CSV file location for DVH Data:"
        )
        label_dvh.setStyleSheet(self.stylesheet)

        label_pyrad = QtWidgets.QLabel(
            "Please choose the input CSV file location for Pyradiomics Data:"
        )
        label_pyrad.setStyleSheet(self.stylesheet)

        self.directory_layout = QtWidgets.QFormLayout()

        # Directory text box for DVH Data
        self.directory_input_dvh_data = QtWidgets.QLineEdit(
            "No file selected")
        self.directory_input_dvh_data.setStyleSheet(self.stylesheet)
        self.directory_input_dvh_data.setEnabled(False)

        # Button For DVH Data to set location
        self.change_button_dvh_data = QtWidgets.QPushButton("Change")
        self.change_button_dvh_data.setMaximumWidth(100)
        self.change_button_dvh_data.clicked.connect(
            self.show_file_browser_dvh_data)
        self.change_button_dvh_data.setObjectName("NormalButton")
        self.change_button_dvh_data.setStyleSheet(self.stylesheet)

        # Directory text box for Pyradiomics Data
        self.directory_input_pyrad = QtWidgets.QLineEdit(
            "No file selected")
        self.directory_input_pyrad.setStyleSheet(self.stylesheet)
        self.directory_input_pyrad.setEnabled(False)

        # Button For Pyradiomics Data to set location
        self.change_button_pyradiomics_data = QtWidgets.QPushButton(
            "Change")
        self.change_button_pyradiomics_data.setMaximumWidth(100)
        self.change_button_pyradiomics_data.clicked.connect(
            self.show_file_browser_pyrad)
        self.change_button_pyradiomics_data.setObjectName("NormalButton")
        self.change_button_pyradiomics_data.setStyleSheet(self.stylesheet)

        # Set DVH data
        self.directory_layout.addWidget(label_dvh)
        self.directory_layout.addRow(self.directory_input_dvh_data)
        self.directory_layout.addRow(self.change_button_dvh_data)

        # Set Pyradiomics data
        self.directory_layout.addWidget(label_pyrad)
        self.directory_layout.addRow(self.directory_input_pyrad)
        self.directory_layout.addRow(self.change_button_pyradiomics_data)

        # create dropdown menu for dvh values to select from
        self.dvh_dropdown_menu = QtWidgets.QComboBox()
        self.dvh_dropdown_menu.setStyleSheet(self.stylesheet)
        self.dvh_dropdown_menu.setEditable(True)
        self.dvh_dropdown_menu.lineEdit().setReadOnly(True)

        # create dropdown menu for dvh values to select from
        self.pyrad_dropdown_menu = QtWidgets.QComboBox()
        self.pyrad_dropdown_menu.setStyleSheet(self.stylesheet)
        self.pyrad_dropdown_menu.setEditable(True)
        self.pyrad_dropdown_menu.lineEdit().setReadOnly(True)

        # create labels for dropdown menus
        self.dvh_dropdown_label = QtWidgets.QLabel(
            "Please select the DVH ROI to filter by:")
        self.dvh_dropdown_label.setStyleSheet(self.stylesheet)
        self.pyrad_dropdown_label = QtWidgets.QLabel(
            "Please select the PyRad ROI to filter by:")
        self.pyrad_dropdown_label.setStyleSheet(self.stylesheet)

        # add dropdowns to main_layout
        self.directory_layout.addRow(self.dvh_dropdown_label)
        self.directory_layout.addRow(self.dvh_dropdown_menu)
        self.directory_layout.addRow(self.pyrad_dropdown_label)
        self.directory_layout.addRow(self.pyrad_dropdown_menu)

        self.main_layout.addLayout(self.directory_layout)
        self.setLayout(self.main_layout)

    def set_dvh_dropdown_menu_data(self, options):
        self.dvh_dropdown_menu.clear()
        self.dvh_dropdown_menu.addItems(options)

    def set_pyrad_dropdown_menu_data(self, options):
        self.pyrad_dropdown_menu.clear()
        self.pyrad_dropdown_menu.addItems(options)

    def read_in_dvh_data(self):
        if self.get_csv_input_location_dvh_data() is not None:
            data_dvh = pd.read_csv(
                f'{self.get_csv_input_location_dvh_data()}',
                on_bad_lines='skip')
            return list(
                data_dvh[data_dvh['ROI'].str.contains('PTV')]['ROI'].unique()
            )
        else:
            return "-"

    def read_in_pyrad_data(self):
        if self.get_csv_input_location_pyrad() is not None:
            data_Py = pd.read_csv(
                f'{self.get_csv_input_location_pyrad()}')
            return list(
                data_Py[data_Py['ROI'].str.contains('GTV')]['ROI'].unique()
            )
        else:
            return "-"

    def _on_dvh_path_changed(self):
        options = self.read_in_dvh_data()
        self.set_dvh_dropdown_menu_data(options)

    def _on_pyrad_path_changed(self):
        options = self.read_in_pyrad_data()
        self.set_pyrad_dropdown_menu_data(options)

    # Function for DVH Data
    def set_csv_input_location_dvh_data(self, path, enable=True,
                                        change_if_modified=False):
        """
        Set the location for the dvh .csv file.
        :param path: desired path.
        :param enable: Enable the directory text bar.
        :param change_if_modified: Change the directory if already been
                                   changed.
        """
        if not self.directory_input_dvh_data.isEnabled():
            self.directory_input_dvh_data.setText(path)
            self.directory_input_dvh_data.setEnabled(enable)
        elif change_if_modified:
            self.directory_input_dvh_data.setText(path)
            self.directory_input_dvh_data.setEnabled(enable)

        self._on_dvh_path_changed()

    def get_csv_input_location_dvh_data(self):
        """
        Get the location of the desired output directory.
        """
        if (self.directory_input_dvh_data.text() != 'No file selected'
                and self.directory_input_dvh_data.text() != expanduser("~")):
            return self.directory_input_dvh_data.text()
        else:
            return None

    def show_file_browser_dvh_data(self):
        """
        Show the file browser for selecting a folder for the Onko
        default directory.
        """
        # Open a file dialog and return chosen directory
        path = QtWidgets.QFileDialog.getOpenFileName(
            None, "Open DVH File", "",
            "CSV data files (*.csv *.CSV)")[0]

        # If chosen directory is nothing (user clicked cancel) set to
        # user home
        if path == "" and \
                self.directory_input_dvh_data.text() == 'No file selected':
            path = expanduser("~")
        elif (path == ""
              and
              (self.directory_input_dvh_data.text() != 'No file selected'
               or self.directory_input_dvh_data.text() != expanduser("~"))):
            path = self.directory_input_dvh_data.text()

        # Update file path
        self.set_csv_input_location_dvh_data(path, change_if_modified=True)

    # Function for Pyrad Data
    def set_csv_input_location_pyrad(self, path, enable=True,
                                     change_if_modified=False):
        """
        Set the location for the pyradiomics .csv file.
        :param path: desired path.
        :param enable: Enable the directory text bar.
        :param change_if_modified: Change the directory if already been
                                   changed.
        """
        if not self.directory_input_pyrad.isEnabled():
            self.directory_input_pyrad.setText(path)
            self.directory_input_pyrad.setEnabled(enable)
        elif change_if_modified:
            self.directory_input_pyrad.setText(path)
            self.directory_input_pyrad.setEnabled(enable)

        self._on_pyrad_path_changed()

    def get_csv_input_location_pyrad(self):
        """
        Get the location of the desired output directory.
        """
        if (self.directory_input_pyrad.text() != 'No file selected'
                and self.directory_input_pyrad.text() != expanduser("~")):
            return self.directory_input_pyrad.text()
        else:
            return None

    def show_file_browser_pyrad(self):
        """
        Show the file browser for selecting a folder for the Onko
        default directory.
        """
        # Open a file dialog and return chosen directory
        path = QtWidgets.QFileDialog.getOpenFileName(
            None, "Open PyRad Data File", "",
            "CSV data files (*.csv *.CSV)")[0]

        # If chosen directory is nothing (user clicked cancel) set to
        # user home
        if path == "" and \
                self.directory_input_pyrad.text() == 'No file selected':
            path = expanduser("~")
        elif (path == ""
              and
              (self.directory_input_pyrad.text() != 'No file selected'
               or self.directory_input_pyrad.text() != expanduser("~"))):
            path = self.directory_input_pyrad.text()

        # Update file path
        self.set_csv_input_location_pyrad(path, change_if_modified=True)

    def get_selected_dvh_value(self):
        return self.dvh_dropdown_menu.currentText()

    def get_selected_pyrad_value(self):
        return self.pyrad_dropdown_menu.currentText()

    def get_selected_options(self):
        selected_options = {
            "dvh_path": self.get_csv_input_location_dvh_data(),
            "pyrad_path": self.get_csv_input_location_pyrad(),
            "dvh_value": self.get_selected_dvh_value(),
            "pyrad_value": self.get_selected_pyrad_value()
        }

        logging.debug("MachineLearningDataSelectionOptions"
                      ".get_selected_options() called")
        logging.debug(f"returning: {selected_options}")
        return selected_options


import abc
import json
import math
import re
import sys
import threading
import warnings
from collections import OrderedDict
from copy import deepcopy
from os import path
from textwrap import wrap
from time import sleep
from typing import (
    List,
    Optional,
)

import lark

from .bolt_protocol import (
    get_bolt_protocol,
    verify_script_messages,
)
from .errors import (
    BoltMissingVersionError,
    BoltUnknownMessageError,
    BoltUnknownVersionError,
    ServerExit,
)
from .packstream import Structure
from .simple_jolt.common.types import (
    JoltType,
    JoltWildcard,
)
from .util import EvalContext

def load_parser():
    grammar_path = path.join(path.dirname(__file__), "grammar.lark")
    with open(grammar_path, "r", encoding="utf-8") as fd:
        return lark.Lark(
            fd, propagate_positions=True  # , ambiguity="explicit"
        )

parser = load_parser()

class CopyableRLock:
    def __init__(self):
        self._l = threading.RLock()

    def __deepcopy__(self, memodict=None):
        return CopyableRLock()

    def acquire(self, blocking=True, timeout=-1):
        return self._l.acquire(blocking=blocking, timeout=timeout)

    def release(self):
        return self._l.release()

    def __enter__(self):
        return self._l.__enter__()

    def __exit__(self, exc_type, exc_val, exc_tb):
        return self._l.__exit__(exc_type, exc_val, exc_tb)

class LineError(lark.GrammarError):
    def __init__(self, line, *args, **kwargs):
        assert isinstance(line, Line)
        self.line = line
        if args and isinstance(args[0], str):
            args = (args[0] + ": {}".format(line),) + args[1:]
        else:
            args = ("{}".format(line),) + args
        super().__init__(*args, **kwargs)

    def __repr__(self):
        str(self.args)

class Line(str, abc.ABC):
    def __new__(cls, line_number: int, raw_line, content: str):
        obj = super(Line, cls).__new__(cls, raw_line)
        obj.line_number = line_number
        obj.content = content
        return obj

    def __str__(self):
        return "({:3}) {}".format(self.line_number,
                                  super(Line, self).__str__())

    def __repr__(self):
        return "<{}>{}".format(self.__class__.__name__, self.__str__())

    def __getnewargs__(self):
        return self.line_number, super(Line, self).__str__(), self.content

    @abc.abstractmethod
    def canonical(self):
        pass

    def parse_jolt(self, jolt_package):
        pass

class BangLine(Line):
    TYPE_AUTO = "auto"
    TYPE_BOLT = "bolt"
    TYPE_RESTART = "restart"
    TYPE_CONCURRENT = "concurrent"
    TYPE_HANDSHAKE = "handshake"
    TYPE_HANDSHAKE_DELAY = "handshake_delay"
    TYPE_PYTHON = "python"

    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        if re.match(r"^AUTO\s", obj.content):
            obj._type = BangLine.TYPE_AUTO
            obj._arg = obj.content[5:].strip()
        elif re.match(r"^BOLT\s", obj.content):
            obj._type = BangLine.TYPE_BOLT
            raw_arg = obj.content[5:].strip()
            try:
                obj._arg = tuple(map(int, raw_arg.split(".")))
            except ValueError:
                raise LineError(
                    obj,
                    "invalid argument for bolt version, must be semantic "
                    "version (e.g. 'BOLT 4.2')"
                )
        elif re.match(r"^ALLOW\s+RESTART$", obj.content):
            obj._type = BangLine.TYPE_RESTART
            obj._arg = None
        elif re.match(r"^ALLOW\s+CONCURRENT", obj.content):
            obj._type = BangLine.TYPE_CONCURRENT
            obj._arg = None
        elif re.match(r"^HANDSHAKE\s", obj.content):
            obj._type = BangLine.TYPE_HANDSHAKE
            arg = re.sub(r"\s", "", obj.content[10:])
            if not re.match(r"^([0-9a-fA-F]{2})+$", arg):
                raise LineError(
                    obj,
                    "invalid argument for handshake, must be list of 2-digit "
                    "hex encoded bytes, whitespace is ignored (e.g. "
                    "'HANDSHAKE 00 FF 02 04 F0'"
                )
            obj._arg = bytearray(int(b, 16) for b in wrap(arg, 2))
        elif re.match(r"^HANDSHAKE_DELAY\s", obj.content):
            obj._type = BangLine.TYPE_HANDSHAKE_DELAY
            raw_arg = obj.content[16:].strip()
            try:
                obj._arg = float(raw_arg)
            except ValueError:
                raise LineError(
                    obj,
                    "invalid argument for handshake delay, must be a number "
                    "number (e.g. 'HANDSHAKE_DELAY 0.5')"
                )
            if obj._arg < 0:
                raise LineError(
                    obj,
                    "invalid argument for handshake delay, must be a positive "
                    "number (e.g. 'HANDSHAKE_DELAY 0.5')"
                )
        elif re.match(r"^PY\s", obj.content):
            obj._type = BangLine.TYPE_PYTHON
            obj._arg = obj.content[3:].strip()
        else:
            raise LineError(obj, 'unsupported Bang line: "{}"'.format(obj))
        return obj

    def canonical(self):
        return "!: {}".format(self.content)

    def update_context(self, ctx: "ScriptContext"):
        if self._type == BangLine.TYPE_AUTO:
            if self._arg in ctx.auto:
                warnings.warn(  # noqa: B028
                    f'Specified AUTO for "{self._arg}" multiple times'
                )
            ctx.auto.add(self._arg)
            ctx.bang_lines["auto"][self._arg] = self
        elif self._type == BangLine.TYPE_BOLT:
            if ctx.bolt_version is not None:
                raise LineError(self, "repeated definition of bolt version")
            ctx.bolt_version = self._arg
            ctx.bang_lines["bolt_version"] = self
        elif self._type == BangLine.TYPE_RESTART:
            if ctx.restarting:
                warnings.warn(  # noqa: B028
                    'Specified "!: ALLOW RESTART" multiple times'
                )
            ctx.restarting = True
            ctx.bang_lines["restarting"] = self
        elif self._type == BangLine.TYPE_CONCURRENT:
            if ctx.concurrent:
                warnings.warn(  # noqa: B028
                    'Specified "!: ALLOW CONCURRENT" multiple times'
                )
            ctx.concurrent = True
            ctx.bang_lines["concurrent"] = self
        elif self._type == BangLine.TYPE_HANDSHAKE:
            if ctx.handshake:
                warnings.warn(  # noqa: B028
                    'Specified "!: HANDSHAKE" multiple times'
                )
            ctx.handshake = self._arg
            ctx.bang_lines["handshake"] = self
        elif self._type == BangLine.TYPE_HANDSHAKE_DELAY:
            if ctx.handshake_delay is not None:
                warnings.warn(  # noqa: B028
                    'Specified "!: HANDSHAKE_DELAY" multiple times'
                )
            ctx.handshake_delay = self._arg
            ctx.bang_lines["handshake_delay"] = self
        elif self._type == BangLine.TYPE_PYTHON:
            ctx.bang_lines["python"].append(self)
            ctx.python.append(self._arg)
        if ctx.restarting and ctx.concurrent:
            warnings.warn(  # noqa: B028
                'Specified "!: ALLOW RESTART" and "!: ALLOW CONCURRENT" '
                "(concurrent scripts are implicitly restarting)"
            )

class MessageLine(Line):
    allow_jolt_wildcard = False
    always_parse = True

    def __new__(cls, line_number: int, raw_line, content: str):
        obj = super().__new__(cls, line_number, raw_line, content)
        if cls.always_parse:
            obj.parsed = cls._parse_line(obj)
        else:
            obj.parsed = None, []
        obj.jolt_parsed = None
        return obj

    @staticmethod
    def _parse_line(line):
        def splart(s):
            parts = s.split(maxsplit=1)
            while len(parts) < 2:
                parts.append("")
            return parts

        content = line.content
        name, data = splart(content.strip())
        fields = []
        decoder = json.JSONDecoder()
        while data:
            data = data.lstrip()
            try:
                decoded, end = decoder.raw_decode(data)
            except json.JSONDecodeError as e:
                raise LineError(
                    line,
                    "message fields must be white space separated json"
                ) from e
            fields.append(decoded)
            data = data[end:]
        return name, fields

    def parse_jolt(self, jolt_package):
        jolt_fields = []
        for field in self.parsed[1]:
            try:
                decoded = jolt_package.codec.decode(field)
            except (ValueError, AssertionError) as e:
                raise LineError(
                    self,
                    "message fields failed JOLT parser"
                ) from e
            decoded = self._jolt_to_struct(decoded)
            jolt_fields.append(decoded)
        self.jolt_parsed = self.parsed[0], jolt_fields
        return self.jolt_parsed

    def _jolt_to_struct(self, decoded):
        if isinstance(decoded, JoltWildcard):
            if not self.allow_jolt_wildcard:
                raise LineError(
                    self, "{} does not allow for JOLT wildcard values"
                          .format(self.__class__.__name__)
                )
            else:
                return decoded
        if isinstance(decoded, JoltType):
            return Structure.from_jolt_type(decoded)
        if isinstance(decoded, (list, tuple)):
            return type(decoded)(self._jolt_to_struct(d) for d in decoded)
        if isinstance(decoded, dict):
            return {k: self._jolt_to_struct(v) for k, v in decoded.items()}
        return decoded

class ClientLine(MessageLine):
    allow_jolt_wildcard = True

    def canonical(self):
        return " ".join(("C:", self.parsed[0],
                         *map(json.dumps, self.parsed[1])))

    def match_message(self, name, fields):
        assert self.jolt_parsed
        line_name, line_fields = self.jolt_parsed
        if line_name != name:
            return False
        return self._field_match(line_fields, fields)

    @classmethod
    def _dict_match(cls, should, is_):
        accepted_keys = set()
        for should_key in should:
            should_key_unescaped = re.sub(r"\\([\[\]\\\{\}])", r"\1",
                                          should_key)
            should_value = should[should_key]
            optional = re.match(r"^\[.*\]$", should_key)
            if optional:
                should_key_unescaped = should_key_unescaped[1:-1]
            ordered = re.match(r"^(?:\[.*\{\}\]|.*\{\})$", should_key)
            if ordered:
                should_key_unescaped = should_key_unescaped[:-2]
                if isinstance(should_value, list):
                    should_value = sorted(should_value)
            accepted_keys.add(should_key_unescaped)
            if should_key_unescaped in is_:
                is_value = is_[should_key_unescaped]
                if ordered and isinstance(is_value, list):
                    is_value = sorted(is_value)
                if not cls._field_match(should_value, is_value):
                    return False
            elif not optional:
                return False
        return not set(is_.keys()).difference(accepted_keys)

    @classmethod
    def _field_match(cls, should, is_):
        if isinstance(should, str):
            if should == "*":
                return True
            should = re.sub(r"\\([\\*])", r"\1", should)
        if isinstance(should, JoltWildcard):
            if isinstance(is_, Structure):
                return is_.match_jolt_wildcard(should)
            return type(is_) in should.types
        if isinstance(is_, Structure):
            return is_ == should
        if type(should) != type(is_):
            return False
        if isinstance(should, (list, tuple)):
            if len(should) != len(is_):
                return False
            return all(cls._field_match(a, b)
                       for a, b in zip(should, is_))
        if isinstance(should, dict):
            return cls._dict_match(should, is_)
        if isinstance(should, float) and math.isnan(should):
            return isinstance(is_, float) and math.isnan(is_)
        return is_ == should

class AutoLine(ClientLine):
    def canonical(self):
        return " ".join(("A:", self.parsed[0],
                         *map(json.dumps, self.parsed[1])))

class ServerLine(MessageLine):
    always_parse = False

    def __new__(cls, *args, **kwargs):
        obj = super(ServerLine, cls).__new__(cls, *args, **kwargs)
        obj.command_match = re.match(r"^<(.+?)>(.*)$", obj.content)
        obj.is_command = bool(obj.command_match)
        if not obj.is_command:
            obj.parsed = cls._parse_line(obj)
        else:
            cls._verify_command(obj)
        return obj

    def parse_jolt(self, simple_jolt):
        if not self.is_command:
            super().parse_jolt(simple_jolt)
        else:
            self._verify_command(self)
        return self

    @staticmethod
    def _verify_command(obj):
        if obj.command_match:
            tag, args = obj.command_match.groups()
            args = args.strip()
            if tag == "EXIT":
                if args:
                    raise LineError(obj, "EXIT takes no arguments")
            elif tag == "NOOP":
                if args:
                    raise LineError(obj, "NOOP takes no arguments")
            elif tag == "RAW":
                try:
                    bytearray(int(_, 16) for _ in wrap(args, 2))
                except ValueError as e:
                    raise LineError(obj, "Invalid raw data") from e
            elif tag == "SLEEP":
                try:
                    f = float(args)
                    if f < 0:
                        raise LineError(obj, "Duration must be non-negative")
                except ValueError as e:
                    raise LineError(obj, "Invalid duration") from e
            elif tag == "ASSERT ORDER":
                if not args.strip():
                    return
                try:
                    f = float(args)
                    if f < 0:
                        raise LineError(obj, "Duration must be non-negative")
                except ValueError as e:
                    raise LineError(obj, "Invalid duration") from e
            else:
                raise LineError(obj, "Unknown command %r" % (tag,))

    def canonical(self):
        if self.is_command:
            return "S: {}".format(self.content)
        else:
            return " ".join(("S:", self.parsed[0],
                             *map(json.dumps, self.parsed[1])))

    def try_run_command(self, channel):
        if self.command_match:
            tag, args = self.command_match.groups()
            args = args.strip()
            if tag == "EXIT":
                raise ServerExit(
                    "server exit as part of the script: {}".format(self)
                )
            elif tag == "NOOP":
                channel.send_raw(b"\x00\x00")
            elif tag == "RAW":
                channel.send_raw(bytearray(int(_, 16) for _ in wrap(args, 2)))
            elif tag == "SLEEP":
                sleep(float(args))
            elif tag == "ASSERT ORDER":
                sleep(float(args.strip() or 1))
                channel.assert_no_input()
            else:
                raise ValueError("Unknown command %r" % (tag,))
            return True
        return False

class PythonLine(Line):
    def canonical(self):
        return "PY: {}".format(self.content)

    def exec(self, eval_context: EvalContext):
        eval_context.exec(self.content.strip())

class Block(abc.ABC):
    def __init__(self, line_number: int):
        self.line_number = line_number

    @abc.abstractmethod
    def accepted_messages(self, channel) -> List[ClientLine]:
        pass

    @abc.abstractmethod
    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        pass

    @abc.abstractmethod
    def assert_no_init(self):
        """Raise error if `init()` would send messages."""

    @abc.abstractmethod
    def done(self, channel) -> bool:
        pass

    def can_be_skipped(self, channel):
        return False

    @abc.abstractmethod
    def can_consume(self, channel) -> bool:
        pass

    @abc.abstractmethod
    def can_consume_after_reset(self, channel) -> bool:
        pass

    def consume(self, channel):
        assert self.try_consume(channel)

    @abc.abstractmethod
    def has_deterministic_end(self):
        pass

    @abc.abstractmethod
    def init(self, channel):
        pass

    @abc.abstractmethod
    def reset(self):
        pass

    @abc.abstractmethod
    def try_consume(self, channel) -> bool:
        pass

    @property
    @abc.abstractmethod
    def all_lines(self):
        pass

    @property
    @abc.abstractmethod
    def client_lines(self):
        pass

    @property
    @abc.abstractmethod
    def server_lines(self):
        pass

    @abc.abstractmethod
    def parse_jolt(self, simple_jolt):
        pass

class ClientBlock(Block):
    def __init__(self, lines: List[ClientLine], line_number: int):
        super().__init__(line_number)
        self.lines = lines
        self.index = 0

    def accepted_messages(self, channel) -> List[ClientLine]:
        return self.lines[self.index:(self.index + 1)]

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        return self.lines[0:1]

    def assert_no_init(self):
        return

    def can_consume(self, channel) -> bool:
        if self.done(channel):
            return False
        return channel.match_client_line(self.lines[self.index],
                                         channel.peek())

    def can_consume_after_reset(self, channel) -> bool:
        return self.lines and channel.match_client_line(self.lines[0],
                                                        channel.peek())

    def _consume(self, channel):
        channel.consume(self.lines[self.index].line_number)
        self.index += 1

    def done(self, channel):
        return self.index >= len(self.lines)

    def has_deterministic_end(self) -> bool:
        return True

    def init(self, channel):
        return

    def reset(self):
        self.index = 0

    def try_consume(self, channel) -> bool:
        if self.can_consume(channel):
            self._consume(channel)
            return True
        return False

    @property
    def all_lines(self):
        yield from map(deepcopy, self.lines)

    @property
    def client_lines(self):
        yield from map(deepcopy, self.lines)

    @property
    def server_lines(self):
        yield from ()

    def parse_jolt(self, simple_jolt):
        for line in self.lines:
            line.parse_jolt(simple_jolt)

class AutoBlock(ClientBlock):
    def __init__(self, line: AutoLine, line_number: int):
        # AutoBlocks always have exactly one line. E.g., this syntax is invalid
        #   A: HELLO "*"
        #      RESET
        # Instead, it must be
        #   A: HELLO "*"
        #   A: RESET
        # This is to avoid ambiguity when it comes to `?:`, `*:`, and `+:`
        # macros.
        super(AutoBlock, self).__init__([line], line_number)

    def _consume(self, channel):
        msg = channel.consume(self.lines[self.index].line_number)
        channel.auto_respond(msg)
        self.index += 1

class ServerBlock(Block):
    def __init__(self, lines: List[ServerLine], line_number: int):
        super().__init__(line_number)
        self.lines = lines
        self.index = 0

    def accepted_messages(self, channel) -> List[ClientLine]:
        return []

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        return []

    def assert_no_init(self):
        if self.lines:
            raise LineError(
                self.lines[0],
                "ambiguity of script does not allow for server response here"
            )

    def can_consume(self, channel) -> bool:
        return False

    def can_consume_after_reset(self, channel) -> bool:
        return False

    def done(self, channel):
        return self.index >= len(self.lines)

    def has_deterministic_end(self) -> bool:
        return True

    def init(self, channel):
        self.respond(channel)

    def respond(self, channel):
        while not self.done(channel):
            line = self.lines[self.index]
            if not line.try_run_command(channel):
                channel.send_server_line(line)
            self.index += 1

    def reset(self):
        self.index = 0

    def try_consume(self, channel) -> bool:
        return False

    @property
    def all_lines(self):
        yield from map(deepcopy, self.lines)

    @property
    def client_lines(self):
        yield from ()

    @property
    def server_lines(self):
        yield from map(deepcopy, self.lines)

    def parse_jolt(self, simple_jolt):
        for line in self.lines:
            line.parse_jolt(simple_jolt)

class PythonBlock(ServerBlock):
    def __init__(self, lines: List[PythonLine], line_number: int):
        super().__init__(lines, line_number)
        self.lines = lines
        self.index = 0

    def respond(self, channel):
        while not self.done(channel):
            line = self.lines[self.index]
            line.exec(channel.eval_context)
            self.index += 1

    def parse_jolt(self, simple_jolt):
        pass

    def assert_no_init(self):
        if self.lines:
            raise LineError(
                self.lines[0],
                "ambiguity of script does not allow for python execution here"
            )

    @property
    def server_lines(self):
        yield from ()

class AlternativeBlock(Block):
    def __init__(self, block_lists: List["BlockList"], line_number: int):
        super().__init__(line_number)
        self.block_lists = block_lists
        self.selection = None
        self.assert_no_init()

    def accepted_messages(self, channel) -> List[ClientLine]:
        if self.selection is None:
            return sum((b.accepted_messages(channel)
                        for b in self.block_lists), [])
        return self.block_lists[self.selection].accepted_messages(channel)

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        return sum((b.accepted_messages_after_reset(channel)
                    for b in self.block_lists),
                   [])

    def assert_no_init(self):
        for block in self.block_lists:
            block.assert_no_init()

    def can_be_skipped(self, channel):
        if self.selection is None:
            return any(b.can_be_skipped(channel) for b in self.block_lists)
        return self.block_lists[self.selection].can_be_skipped(channel)

    def can_consume(self, channel) -> bool:
        if self.selection is None:
            return any(b.can_consume(channel) for b in self.block_lists)
        return self.block_lists[self.selection].can_consume(channel)

    def can_consume_after_reset(self, channel) -> bool:
        return any(b.can_consume_after_reset(channel)
                   for b in self.block_lists)

    def done(self, channel):
        return (self.selection is not None
                and self.block_lists[self.selection].done(channel))

    def has_deterministic_end(self) -> bool:
        return all(b.has_deterministic_end() for b in self.block_lists)

    def init(self, channel):
        # self.assert_no_init()
        pass

    def reset(self):
        self.selection = None
        for block in self.block_lists:
            block.reset()

    def try_consume(self, channel) -> bool:
        if self.selection is not None:
            return self.block_lists[self.selection].try_consume(channel)
        for i in range(len(self.block_lists)):
            if self.block_lists[i].try_consume(channel):
                self.selection = i
                return True
        return False

    @property
    def all_lines(self):
        for block_list in self.block_lists:
            yield from block_list.all_lines

    @property
    def client_lines(self):
        for block_list in self.block_lists:
            yield from block_list.client_lines

    @property
    def server_lines(self):
        for block_list in self.block_lists:
            yield from block_list.server_lines

    def parse_jolt(self, simple_jolt):
        for block_list in self.block_lists:
            block_list.parse_jolt(simple_jolt)

class ParallelBlock(Block):
    def __init__(self, block_lists: List["BlockList"], line_number: int):
        super().__init__(line_number)
        self.block_lists = block_lists
        self.assert_no_init()

    def accepted_messages(self, channel) -> List[ClientLine]:
        return sum((b.accepted_messages(channel)
                    for b in self.block_lists), [])

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        return sum((b.accepted_messages_after_reset(channel)
                    for b in self.block_lists), [])

    def assert_no_init(self):
        for b in self.block_lists:
            b.assert_no_init()

    def done(self, channel) -> bool:
        return all(b.done(channel) for b in self.block_lists)

    def can_be_skipped(self, channel):
        return all(b.can_be_skipped(channel) for b in self.block_lists)

    def can_consume(self, channel) -> bool:
        return any(b.can_consume(channel) for b in self.block_lists)

    def can_consume_after_reset(self, channel) -> bool:
        return any(b.can_consume_after_reset(channel)
                   for b in self.block_lists)

    def has_deterministic_end(self) -> bool:
        return all(b.has_deterministic_end() for b in self.block_lists)

    def init(self, channel):
        # self.assert_no_init()
        pass

    def reset(self):
        for block in self.block_lists:
            block.reset()

    def try_consume(self, channel) -> bool:
        for block in self.block_lists:
            if block.try_consume(channel):
                return True
        return False

    @property
    def all_lines(self):
        for block_list in self.block_lists:
            yield from block_list.all_lines

    @property
    def client_lines(self):
        for block_list in self.block_lists:
            yield from block_list.client_lines

    @property
    def server_lines(self):
        for block_list in self.block_lists:
            yield from block_list.server_lines

    def parse_jolt(self, simple_jolt):
        for block_list in self.block_lists:
            block_list.parse_jolt(simple_jolt)

class OptionalBlock(Block):
    def __init__(self, block_list: "BlockList", line_number: int):
        super().__init__(line_number)
        self.started = False
        self.block_list = block_list
        self.assert_no_init()

    def accepted_messages(self, channel) -> List[ClientLine]:
        return self.block_list.accepted_messages(channel)

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        return self.block_list.accepted_messages_after_reset(channel)

    def assert_no_init(self):
        self.block_list.assert_no_init()

    def can_be_skipped(self, channel):
        if self.started:
            if self.block_list.has_deterministic_end():
                return self.block_list.done(channel)
            return self.block_list.can_be_skipped(channel)
        return True

    def can_consume(self, channel) -> bool:
        return self.block_list.can_consume(channel)

    def can_consume_after_reset(self, channel) -> bool:
        return self.block_list.can_consume_after_reset(channel)

    def done(self, channel) -> bool:
        if self.started and self.block_list.has_deterministic_end():
            return self.block_list.done(channel)
        raise RuntimeError("it's nondeterministic!")

    def has_deterministic_end(self) -> bool:
        if not self.started:
            return False
        return self.block_list.has_deterministic_end()

    def init(self, channel):
        # self.assert_no_init()
        pass

    def reset(self):
        self.started = False
        self.block_list.reset()

    def try_consume(self, channel) -> bool:
        if self.block_list.try_consume(channel):
            self.started = True
            return True
        return False

    @property
    def all_lines(self):
        return self.block_list.all_lines

    @property
    def client_lines(self):
        return self.block_list.client_lines

    @property
    def server_lines(self):
        return self.block_list.server_lines

    def parse_jolt(self, simple_jolt):
        self.block_list.parse_jolt(simple_jolt)

class _RepeatBlock(Block, abc.ABC):
    def __init__(self, block_list, line_number: int):
        super().__init__(line_number)
        self.in_block = False
        self.iteration_count = 0
        self.block_list = block_list
        self.assert_no_init()

    def accepted_messages(self, channel) -> List[ClientLine]:
        res = OrderedDict((m, True)
                          for m in self.block_list.accepted_messages(channel))
        if ((self.has_deterministic_end() and self.done(channel))
                or self.block_list.can_be_skipped(channel)):
            res.update(
                (m, True)
                for m in self.block_list.accepted_messages_after_reset(channel)
            )
        return list(res.keys())

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        return self.block_list.accepted_messages_after_reset(channel)

    def assert_no_init(self):
        self.block_list.assert_no_init()

    @abc.abstractmethod
    def can_be_skipped(self, channel):
        pass

    def can_consume(self, channel) -> bool:
        return self.block_list.can_consume(channel)

    def can_consume_after_reset(self, channel) -> bool:
        return self.block_list.can_consume_after_reset(channel)

    def done(self, channel) -> bool:
        raise RuntimeError("it's nondeterministic!")

    def has_deterministic_end(self) -> bool:
        return False

    def init(self, channel):
        # self.assert_no_init()
        pass

    def reset(self):
        self.in_block = False
        self.iteration_count = 0
        self.block_list.reset()

    def _consume_after_jump_to_top(self, channel):
        self.block_list.reset()
        self.iteration_count += 1
        return self.try_consume(channel)

    def _try_consume_deterministic(self, channel):
        if self.block_list.done(channel):
            return self._consume_after_jump_to_top(channel)
        if self.block_list.try_consume(channel):
            self.in_block = not self.block_list.done(channel)
            return True
        return False

    def _try_consume_nondeterministic(self, channel):
        if self.block_list.try_consume(channel):
            self.in_block = not self.block_list.can_be_skipped(channel)
            return True
        elif (self.block_list.can_be_skipped(channel)
                and self.block_list.can_consume_after_reset(channel)):
            assert self._consume_after_jump_to_top(channel)
            return True
        return False

    def try_consume(self, channel) -> bool:
        if self.block_list.has_deterministic_end():
            return self._try_consume_deterministic(channel)
        return self._try_consume_nondeterministic(channel)

    @property
    def all_lines(self):
        return self.block_list.all_lines

    @property
    def client_lines(self):
        return self.block_list.client_lines

    @property
    def server_lines(self):
        return self.block_list.server_lines

    def parse_jolt(self, simple_jolt):
        self.block_list.parse_jolt(simple_jolt)

class Repeat0Block(_RepeatBlock):
    def can_be_skipped(self, channel):
        return not self.in_block

class Repeat1Block(_RepeatBlock):
    def can_be_skipped(self, channel):
        if self.in_block:
            return False
        return (
            self.block_list.can_be_skipped(channel)
            or self.iteration_count >= 1
        )

class ConditionalBlock(Block):
    def __init__(self, conditions: List[str], blocks: List[Block],
                 line_number: int):
        super().__init__(line_number)
        self.conditions = conditions
        self.blocks = blocks
        self.selection = None

    def _get_selected_block(
        self, channel, selection, probing
    ) -> Optional[int]:
        if selection is not None:
            return selection
        for i, condition in enumerate(self.conditions):
            if channel.eval_context.eval(condition, probing=probing):
                return i
        if len(self.blocks) > len(self.conditions):
            return len(self.conditions)
        return None

    def _probe_selection(self, channel, selection) -> Optional[Block]:
        selection = self._get_selected_block(channel, selection, probing=True)
        if selection is None:
            return None
        return self.blocks[selection]

    def _get_selection(self, channel, selection) -> Optional[Block]:
        selection = self._get_selected_block(channel, selection, probing=False)
        self.selection = selection
        if selection is None:
            return None
        return self.blocks[selection]

    def accepted_messages(self, channel) -> List[ClientLine]:
        block = self._probe_selection(channel, self.selection)
        if not block:
            return []
        return block.accepted_messages(channel)

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        block = self._probe_selection(channel, None)
        if not block:
            return []
        return block.accepted_messages_after_reset(channel)

    def assert_no_init(self):
        return

    def done(self, channel) -> bool:
        block = self._probe_selection(channel, self.selection)
        if not block:
            return True
        return block.done(channel)

    def can_be_skipped(self, channel):
        block = self._probe_selection(channel, self.selection)
        if not block:
            return True
        return block.can_be_skipped(channel)

    def can_consume(self, channel) -> bool:
        block = self._probe_selection(channel, self.selection)
        if not block:
            return False
        return block.can_consume(channel)

    def can_consume_after_reset(self, channel) -> bool:
        block = self._probe_selection(channel, None)
        if not block:
            return False
        return block.can_consume_after_reset(channel)
        pass

    def has_deterministic_end(self):
        return all(b.has_deterministic_end() for b in self.blocks)

    def init(self, channel):
        block = self._get_selection(channel, self.selection)
        if not block:
            return
        block.init(channel)

    def reset(self):
        self.selection = None
        for block in self.blocks:
            block.reset()

    def try_consume(self, channel) -> bool:
        block = self._get_selection(channel, self.selection)
        if not block:
            return False
        return block.try_consume(channel)

    @property
    def all_lines(self):
        for block in self.blocks:
            yield from block.all_lines

    @property
    def client_lines(self):
        for block in self.blocks:
            yield from block.client_lines

    @property
    def server_lines(self):
        for block in self.blocks:
            yield from block.server_lines

    def parse_jolt(self, simple_jolt):
        for block in self.blocks:
            block.parse_jolt(simple_jolt)

class BlockList(Block):
    def __init__(self, blocks: List[Block], line_number: int):
        super().__init__(line_number)
        self.blocks = blocks
        self.index = 0
        for prev_block, next_block in zip(self.blocks, self.blocks[1:]):
            if not prev_block.has_deterministic_end():
                next_block.assert_no_init()

    def accepted_messages(self, channel) -> List[ClientLine]:
        res = []
        if self.index >= len(self.blocks):
            return res

        for i in range(self.index, len(self.blocks)):
            res += self.blocks[i].accepted_messages(channel)
            if not self.blocks[i].can_be_skipped(channel):
                break
        return res

    def accepted_messages_after_reset(self, channel) -> List[ClientLine]:
        res = []
        if not self.blocks:
            return res

        for i in range(len(self.blocks)):
            res += self.blocks[i].accepted_messages_after_reset(channel)
            if not self.blocks[i].can_be_skipped(channel):
                break
        return res

    def assert_no_init(self):
        self.blocks[0].assert_no_init()

    def can_be_skipped(self, channel):
        return all(b.can_be_skipped(channel)
                   for b in self.blocks[self.index:len(self.blocks)])

    def can_consume(self, channel) -> bool:
        for i in range(self.index, len(self.blocks)):
            if self.blocks[i].can_consume(channel):
                return True
            if not self.blocks[i].can_be_skipped(channel):
                break
        return False

    def can_consume_after_reset(self, channel) -> bool:
        for i in range(len(self.blocks)):
            if self.blocks[i].can_consume_after_reset(channel):
                return True
            if not self.blocks[i].can_be_skipped(channel):
                break
        return False

    def done(self, channel) -> bool:
        if not self.has_deterministic_end():
            raise RuntimeError("it's nondeterministic!")
        return self.index >= len(self.blocks)

    def has_deterministic_end(self) -> bool:
        return self.blocks[-1].has_deterministic_end()

    def init(self, channel):
        while self.index < len(self.blocks):
            block = self.blocks[self.index]
            block.init(channel)
            if not block.has_deterministic_end() or not block.done(channel):
                break
            self.index += 1

    def reset(self):
        for block in self.blocks:
            block.reset()
        self.index = 0

    def try_consume(self, channel) -> bool:
        for i in range(self.index, len(self.blocks)):
            block = self.blocks[i]
            if block.try_consume(channel):
                self.index = i
                while block.has_deterministic_end() and block.done(channel):
                    self.index += 1
                    if self.index < len(self.blocks):
                        block = self.blocks[self.index]
                        block.init(channel)
                    else:
                        break
                return True
            if (
                not block.can_be_skipped(channel)
                and block.has_deterministic_end()
                and not block.done(channel)
            ):
                break
        return False

    @property
    def all_lines(self):
        for block in self.blocks:
            yield from block.all_lines

    @property
    def client_lines(self):
        for block in self.blocks:
            yield from block.client_lines

    @property
    def server_lines(self):
        for block in self.blocks:
            yield from block.server_lines

    def parse_jolt(self, simple_jolt):
        for block in self.blocks:
            block.parse_jolt(simple_jolt)

class ScriptFailure(RuntimeError):
    pass

class ScriptDeviation(ScriptFailure):
    def __init__(self, expected_lines: List[Line], received: Line):
        assert expected_lines
        self.expected_lines = expected_lines
        self.received = received

    def __str__(self):
        res = "Expected"
        if len(self.expected_lines) > 1:
            res += " one of"
        res += ":\n"
        res += "\n".join(map(str, self.expected_lines))
        res += "\n\nReceived:\n" + str(self.received)
        res += "\n => " + repr(self.received)
        return res

class ScriptContext:
    def __init__(self):
        self.bolt_version = None
        self.auto = set()
        self.restarting = False
        self.concurrent = False
        self.handshake = None
        self.handshake_delay = None
        self.python = []
        self.bang_lines = {
            "bolt_version": None,
            "auto": {},
            "restarting": None,
            "concurrent": None,
            "handshake": None,
            "handshake_delay": None,
            "python": [],
        }

    def create_eval_context(self) -> EvalContext:
        context = EvalContext()
        for cmd in self.python:
            context.exec(cmd)
        return context

class Script:
    def __init__(self, bang_lines: List[BangLine], block_list: BlockList,
                 filename=None):
        self.context = ScriptContext()
        self._consume_bang_lines(bang_lines)
        self.block_list = block_list
        self.filename = filename or ""
        self._skipped = False
        self._set_bolt_protocol()
        self._post_process()
        self._verify_script()
        self._lock = CopyableRLock()

    def _set_bolt_protocol(self):
        try:
            self._bolt_protocol = get_bolt_protocol(self.context.bolt_version)
        except BoltMissingVersionError as e:
            raise lark.GrammarError(
                'Missing bolt version bang line (e.g. "!: BOLT 4.3")'
            ) from e
        except BoltUnknownVersionError as e:
            raise LineError(
                self.context.bang_lines["bolt_version"], *e.args[:1]
            ) from e

    def _post_process(self):
        self.block_list.parse_jolt(self._bolt_protocol.get_jolt_package())

    def _verify_script(self):
        try:
            verify_script_messages(self)
        except BoltUnknownMessageError as e:
            raise LineError(e.line, e.msg) from e

    def _consume_bang_lines(self, bang_lines):
        for bl in bang_lines:
            bl.update_context(self.context)

    def init(self, channel):
        with self._lock:
            self.block_list.init(channel)

    def consume(self, channel):
        with self._lock:
            if not self.block_list.try_consume(channel):
                if not channel.try_auto_consume(self.context.auto):
                    raise ScriptDeviation(
                        self.block_list.accepted_messages(channel),
                        channel.peek()
                    )

    def done(self, channel):
        with self._lock:
            if self._skipped:
                return True
            if self.block_list.has_deterministic_end():
                return self.block_list.done(channel)
            return False

    def try_skip_to_end(self, channel):
        with self._lock:
            if self.block_list.can_be_skipped(channel):
                self._skipped = True

    @property
    def all_lines(self):
        return self.block_list.all_lines

    @property
    def client_lines(self):
        return self.block_list.client_lines

    @property
    def server_lines(self):
        return self.block_list.server_lines

class ScriptTransformer(lark.Transformer):
    @lark.v_args(meta=True)
    def bang_line(self, meta, children):
        return BangLine(meta.line, "".join(children),
                        children[-1].strip())

    @lark.v_args(meta=True)
    def client_line(self, meta, children):
        return ClientLine(meta.line, "".join(children),
                          children[-1].strip())

    @lark.v_args(meta=True)
    def auto_line(self, meta, children):
        return AutoLine(meta.line, "".join(children),
                        children[-1].strip())

    @lark.v_args(meta=True)
    def server_line(self, meta, children):
        return ServerLine(meta.line, "".join(children),
                          children[-1].strip())

    @lark.v_args(meta=True)
    def python_line(self, meta, children):
        return PythonLine(meta.line, "".join(children),
                          children[-1].strip())

    def start(self, children):
        bang_lines = []
        block_list = None
        for child in children:
            if isinstance(child, BangLine):
                bang_lines.append(child)
            elif isinstance(child, BlockList):
                block_list = child
                break
        return Script(bang_lines, block_list)

    @lark.v_args(meta=True)
    def block_list(self, meta, children):
        blocks = []
        for child in children:
            if isinstance(child, lark.Token):
                continue
            if (blocks
                    and ((child.__class__ == ClientBlock
                          and blocks[-1].__class__ == ClientBlock)
                         or (child.__class__ == ServerBlock
                             and blocks[-1].__class__ == ServerBlock))):
                blocks[-1].lines.extend(child.lines)
            else:
                blocks.append(child)

        return BlockList(blocks, meta.line)

    @lark.v_args(meta=True)
    def client_block(self, meta, children):
        return ClientBlock(
            [
                child for child in children
                if child.__class__ == ClientLine
            ],
            meta.line
        )

    @lark.v_args(meta=True)
    def auto_block(self, meta, children):
        assert len(children) == 1
        assert children[0].__class__ == AutoLine
        return AutoBlock(children[0], meta.line)

    def _wrapped_auto_block(self, wrapper, meta, children):
        assert len(children) == 1
        assert children[0].__class__ == AutoLine
        return wrapper(
            BlockList(
                [AutoBlock(children[0], meta.line)],
                meta.line
            ),
            meta.line
        )

    @lark.v_args(meta=True)
    def auto_optional_block(self, meta, children):
        return self._wrapped_auto_block(OptionalBlock, meta, children)

    @lark.v_args(meta=True)
    def auto_loop0_block(self, meta, children):
        return self._wrapped_auto_block(Repeat0Block, meta, children)

    @lark.v_args(meta=True)
    def auto_loop1_block(self, meta, children):
        return self._wrapped_auto_block(Repeat1Block, meta, children)

    @lark.v_args(meta=True)
    def server_block(self, meta, children):
        return ServerBlock(
            [
                child for child in children
                if child.__class__ == ServerLine
            ],
            meta.line
        )

    @lark.v_args(meta=True)
    def python_block(self, meta, children):
        return PythonBlock(
            [
                child for child in children
                if child.__class__ == PythonLine
            ],
            meta.line
        )

    @lark.v_args()
    def simple_block(self, children):
        children = [c for c in children if not isinstance(c, lark.Token)]
        assert len(children) == 1
        assert isinstance(children[0], BlockList)
        return children[0]

    @lark.v_args(meta=True)
    def alternative_block(self, meta, children):
        return AlternativeBlock(
            [c for c in children if not isinstance(c, lark.Token)],
            meta.line
        )

    @lark.v_args(meta=True)
    def optional_block(self, meta, children):
        return OptionalBlock(children[2], meta.line)

    @lark.v_args(meta=True)
    def parallel_block(self, meta, children):
        return ParallelBlock(
            [c for c in children if not isinstance(c, lark.Token)],
            meta.line
        )

    @lark.v_args(meta=True)
    def repeat_0_block(self, meta, children):
        return Repeat0Block(children[2], meta.line)

    @lark.v_args(meta=True)
    def repeat_1_block(self, meta, children):
        return Repeat1Block(children[2], meta.line)

    @lark.v_args(meta=True)
    def conditional_block(self, meta, children):
        conditions = [children[0].children[-1].strip()]
        blocks = [children[2]]
        for child in children[4:]:
            if isinstance(child, lark.Tree) and child.data == "elif_line":
                conditions.append(child.children[-1].strip())
            elif isinstance(child, Block):
                blocks.append(child)

        return ConditionalBlock(conditions, blocks, meta.line)

def parse(script: str, substitutions: Optional[dict] = None) -> Script:
    if substitutions:
        for match, replacement in substitutions.items():
            script = script.replace(match, replacement)
    return ScriptTransformer().transform(parser.parse(script))

def parse_file(filename):
    with open(filename, encoding="utf-8") as fd:
        try:
            script = parse(fd.read())
        except Exception:
            print("Error while parsing %s" % filename, file=sys.stderr)
            raise
    script.filename = filename
    return script


"""
给你一个未排序的整数数组 nums ，请你找出其中没有出现的最小的正整数。

进阶：你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗？

示例 1：

输入：nums = [1,2,0]
输出：3
示例 2：

输入：nums = [3,4,-1,1]
输出：2
示例 3：

输入：nums = [7,8,9,11,12]
输出：1

来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/first-missing-positive
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
"""
class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int: #原地哈希
        # 将对应的正数换到正数-1的位置。然后从头开始遍历，如果有正数-1的位置不等于正数，这说明缺了这个正数
        # 若全都不缺，则说明数组里全都是符合1到n的正数，返回n+1
        index=0
        n=len(nums)
        while index<n:
            tmp=nums[index]
            if tmp>0 and tmp<n and nums[tmp-1]!=tmp:
                nums[tmp-1],nums[index]=nums[index],nums[tmp-1]
            else:
                index+=1
        for j in range(1,n+1):
            if nums[j-1]!=j:
                return j
        return n+1

分类/排序/41.缺失的第一个正数.py

41.缺失的第一个正数.py


import glob
import numpy as np
import cv2 as cv
# Внутренняя
K = np.array([[6.05304389e+03, 0.00000000e+00, 2.54106629e+03],
 [0.00000000e+00, 6.03790773e+03, 1.30293219e+03],
 [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
from matplotlib import pyplot as plt
first = cv.imread('img_2/DSC02694.JPG', 0)
second = cv.imread('img_2/DSC02692.JPG', 0)
fig, ax = plt.subplots(1,2, figsize=(15,15))
ax[0].imshow(first, cmap='gray')
ax[1].imshow(second, cmap='gray')
# Ключевые точки
# Инициализация SIFT детектора
sift = cv.SIFT_create()
# Определение ключевых точек SIFT
kp1, des1 = sift.detectAndCompute(first, None)
kp2, des2 = sift.detectAndCompute(second, None)
# Сопоставление на основе FLANN параметров
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# фильрация Лоу
matchesLowe = []
for m, n in matches:
    if m.distance < 0.7 * n.distance:
        matchesLowe.append(m)
left_pts = np.float32([kp1[m.queryIdx].pt for m in matchesLowe]).reshape(-1, 1, 2)
right_pts = np.float32([kp2[m.trainIdx].pt for m in matchesLowe]).reshape(-1, 1, 2)
# Матрицы
E, _ = cv.findEssentialMat(left_pts, right_pts, K)
F, _ = cv.findFundamentalMat(left_pts, right_pts, cv.FM_LMEDS)
# Точки
left_cupboard  = np.array([[533, 1523], [1643, 1562], [1608, 4312], [570, 4483]])
right_cupboard = np.array([[366, 1526], [1363, 1486], [1379, 4182], [447, 4070]])
left_door = np.array([[113, 1117], [167, 4310]])
right_door = np.array([[352, 1168], [434, 3892]])
white = (255, 255, 255)
black = (0, 0, 0)
for index in range(len(left_cupboard)):
    cv.circle(first,  tuple(left_cupboard[index]),  25, white, -1)
    cv.circle(second, tuple(right_cupboard[index]), 25, white, -1)

for index in range(len(left_door)):
    cv.circle(first,  tuple(left_door[index]),  25, black, -1)
    cv.circle(second, tuple(right_door[index]), 25, black, -1)
fig, ax = plt.subplots(1, 2, figsize=(15, 15))
ax[0].imshow(first, cmap='gray')
ax[1].imshow(second, cmap='gray')
# Переход к мировой системе координат
# Первая матрица проекций
rt1 = np.array([[1, 0, 0, 0],
                [0, 1, 0, 0],
                [0, 0, 1, 0]])
pr1 = np.dot(K, rt1)
# Вторая матрица проекций
_, R, T, _ = cv.recoverPose(E, left_pts, right_pts, K)
rt2 = np.concatenate((R, T), axis=1)
pr2 = np.dot(K, rt2)

def triangulate(left, right, i):
    res = cv.triangulatePoints(pr1, pr2, left[i], right[i])
    return res[:3] / res[-1]

global_cupboard = []
global_door = []

for i in range(len(left_cupboard)):
    global_cupboard.append(triangulate(left_cupboard.astype(float), right_cupboard.astype(float), i))

for i in range(len(left_door)):
    global_door.append(triangulate(left_door.astype(float), right_door.astype(float), i))

global_cupboard = np.array(global_cupboard)
global_door = np.array(global_door)
# Пусть высота двери 2 метра
scale = 2 / cv.norm(global_door[0] - global_door[1], cv.NORM_L2)
distances = np.array([scale * cv.norm(global_cupboard[i] - global_cupboard[(i + 1) % len(global_cupboard)], cv.NORM_L2) for i in range(len(global_cupboard))])
distances
scale


"""Add Contact.company

Revision ID: 9d8fa0473dfa
Revises: a8e5e4442b83
Create Date: 2019-04-21 22:46:42.225537

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '9d8fa0473dfa'
down_revision = 'a8e5e4442b83'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('contact', sa.Column('company', sa.String(), nullable=True))
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('contact', 'company')
    # ### end Alembic commands ###


#! python3
# -*- coding: utf-8 -*-

def make_label(filename):
    with open(filename, 'r') as f:
        content = f.read()
    origin_list = content.split('/')
    print(origin_list)
    pure_list = [x for x in origin_list if x != '\n' and x != ' ' and x != '']
    print(pure_list)

if __name__ == '__main__':
    make_label('slash_test.txt')


from dataset import get_dataset
import torch.utils.data
import torch
import torchvision.transforms.functional as TF
import network_new
import os

join = os.path.join
from PIL import Image

def show_result(G, x_):
    G.eval()
    G.cpu()
    x_ = x_.cpu()
    test_images = G(x_)

    for i in range(x_.shape[0]):
        # img_np = (test_images[i].cpu().data.numpy().transpose(1, 2, 0) + 1) / 2
        # img = Image.fromarray(img_np)
        img = TF.to_pil_image(test_images[i])
        img.save('./save{:0>4}.png'.format(i))
        img.show()

pixel_set = get_dataset('new_data/train/pixel', 256)
photo_set = get_dataset('new_data/train/photo', 256)

fixed_xs = []
for i in range(6):
    fixed_xs.append(photo_set[i].unsqueeze(0))
fixed_x_ = torch.cat(fixed_xs, dim=0)

filter_count = 64
G = network_new.generator(d=filter_count)

stat_dict = torch.load('./generator_param1.pkl')
G.load_state_dict(stat_dict)

show_result(G, fixed_x_)


import random
# computer
computer = random.randint(0, 2)
# player
player = int(input("请输入(0--石头，1--剪刀，2--布)"))
if ((player == 0)and(computer == 1)) or ((player == 1)and(computer == 0)) or ((player == 2)and(computer == 1)):
    print("win")
elif player == computer:
    print("平")
else:
    print("loss")
print(computer)


import math
import csv

def load_csv(filename):
    lines = csv.reader(open(filename, "r"));
    dataset = list(lines)
    headers = dataset.pop(0)
    return dataset, headers

class Node:
    def __init__(self, attribute):
        self.attribute = attribute
        self.children = []
        self.answer = ""  # NULL indicates children exists. # Not Null indicates this is a Leaf Node

def subtables(data, col, delete):
    dic = {}
    coldata = [ row[col] for row in data]
    attr = list(set(coldata)) # All values of attribute retrived
    for k in attr:
        dic[k] = []

    for y in range(len(data)):
        key = data[y][col]
        if delete:
            del data[y][col]
        dic[key].append(data[y])
    return attr, dic

def entropy(S):
    attr = list(set(S))
    if len(attr) == 1: #if all are +ve/-ve then entropy = 0
        return 0

    counts = [0,0] # Only two values possible 'yes' or 'no'
    for i in range(2):
        counts[i] = sum( [1 for x in S if attr[i] == x] ) / (len(S) * 1.0)

    sums = 0
    for cnt in counts:
        sums += -1 * cnt * math.log(cnt, 2)
    return sums

def compute_gain(data, col):
    attValues, dic = subtables(data, col, delete=False)
    total_entropy = entropy([row[-1] for row in data])
    for x in range(len(attValues)):
        ratio = len(dic[attValues[x]]) / ( len(data) * 1.0)
        entro = entropy([row[-1] for row in dic[attValues[x]]])
        total_entropy -= ratio*entro

    return total_entropy

def build_tree(data, features):
    lastcol = [row[-1] for row in data]
    if (len(set(lastcol))) == 1: # If all samples have same labels return that label
        node=Node("")
        node.answer = lastcol[0]
        return node

    n = len(data[0])-1
    gains = [compute_gain(data, col) for col in range(n) ]

    split = gains.index(max(gains)) # Find max gains and returns index
    node = Node(features[split]) # 'node' stores attribute selected #del (features[split])
    fea = features[:split]+features[split+1:]

    attr, dic = subtables(data, split, delete=True) # Data will be spilt in subtables
    for x in range(len(attr)):
        child = build_tree(dic[attr[x]], fea)
        node.children.append((attr[x], child))

    return node

def print_tree(node, level):
    if node.answer != "":
        print("     "*level, node.answer) # Displays leaf node yes/no
        return

    print("       "*level, node.attribute) # Displays attribute Name
    for value, n in node.children:
        print("     "*(level+1), value)
        print_tree(n, level + 2)

def classify(node,x_test,features):
    if node.answer != "":
        print(node.answer)
        return

    pos = features.index(node.attribute)
    for value, n in node.children:
        if x_test[pos]==value:
            classify(n,x_test,features)

#''' Main program '''
dataset, features = load_csv("PlayTennis.csv") # Read Tennis data
node = build_tree(dataset, features) # Build decision tree

print("The decision tree for the dataset using ID3 algorithm is ")
print_tree(node, 0)

testdata, features = load_csv("PlayTennisTestData.csv")
for xtest in testdata:
    print("The test instance : ",xtest)
    print("The predicted label : ", end="")
    classify(node,xtest,features)


# Constantes utilizadas por el resto de módulos

JSON_FILE_REFERENCES = "legislacion.json"
JSON_FILE_COLLECTIONS = "colecciones.json"
VIGENTE = "vigente"
DEROGADO = "derogado"
VENCIDO = "vencido"
DIR_SITE = "docs"
DIR_FILES = "files"
DIR_IMAGES = "img"
FILE_INDEX = "index.html"
FILE_FICHAS = "listados/cronologico.html"
FILE_CSS = "varios/legislacion.css"
MES = [
    "",
    "enero",
    "febrero",
    "marzo",
    "abril",
    "mayo",
    "junio",
    "julio",
    "agosto",
    "septiembre",
    "octubre",
    "noviembre",
    "diciembre",
]
CODIGOS_ISO_3166 = {
    "Unión Europea": "eu",
    "España": "es",
    "Ministerio de Educación": "es-min",
    "Andalucía": "es-an",
    "Aragón": "es-ar",
    "Principado de Asturias": "es-as",
    "Canarias": "es-cn",
    "Cantabria": "es-cb",
    "Castilla-La Mancha": "es-cm",
    "Castilla y León": "es-cl",
    "Cataluña": "es-ct",
    "Extremadura": "es-ex",
    "Galicia": "es-gl",
    "Islas Baleares": "es-ib",
    "La Rioja": "es-ri",
    "Comunidad de Madrid": "es-md",
    "Región de Murcia": "es-mc",
    "Comunidad Foral de Navarra": "es-nc",
    "País Vasco": "es-pv",
    "Comunidad Valenciana": "es-vc",
}
DOCUMENTOS_TIPOS = {
    "original": "Original: ",
    "consolidado": "Consolidado: ",
    "anexo": "Anexo: ",
    "borrador": "Borrador: "
}


import os
import pathlib
import re

from matplotlib import pyplot as plt

import config

def file_name_with_path_returner(root_dir, extension):
    filelist = []

    for root, dirs, files in os.walk(root_dir):
        for file in files:
            file_extension = pathlib.Path(file).suffix
            if file_extension in extension:
                filelist.append(os.path.join(root, file))
    return filelist

def file_name_with_path_returner_(root_dir):
    filelist = []
    for root, dirs, files in os.walk(root_dir):
        if not re.search(config.IGNORE_FOLDERS, root):
            for file in files:
                file_extension = pathlib.Path(file).suffix
                if file_extension not in config.IGNORE_EXTENSIONS:
                    filelist.append(os.path.join(root, file))
    return filelist

def generic_count_returner(file, line, line_number, reg, filename_with_expect_and_assert_count):
    # print("reg is ", reg)
    count = len(reg.findall(line))
    # print(line)
    # print(f'count is {count}')
    if count > 0:
        if filename_with_expect_and_assert_count.__contains__(file):
            filename_with_expect_and_assert_count[file][0] += 1
            filename_with_expect_and_assert_count[file][1].append(line_number + 1)
        else:
            filename_with_expect_and_assert_count[file] = [1, [line_number + 1]]

    return filename_with_expect_and_assert_count

def generic_count_returner_with_line_numbers(files, testStatements, param=''):
    print(f"param is {param}")
    filename_with_expect_and_assert_count = {}
    reg = re.compile(r' | '.join(testStatements), flags=re.I | re.X)
    for file in files:
        print(file)
        current_file = open(file, 'r', encoding='utf8', errors="ignore")
        for line_number, line in enumerate(current_file):
            if not (re.search(config.COMMENTS_REGEX, line)):
                if param == 'assert':
                    if not re.search(config.ASSERT_STATEMENTS_COMMENTS_REGEX, line, flags=re.I | re.X):
                        filename_with_expect_and_assert_count = generic_count_returner(file, line, line_number, reg,
                                                                                filename_with_expect_and_assert_count)

                elif param == 'debug':
                    if not re.search(
                            config.DEBUG_STATEMENTS_COMMENTS_REGEX, line, flags=re.I | re.X):
                        filename_with_expect_and_assert_count = generic_count_returner(file, line, line_number, reg,
                                                                                filename_with_expect_and_assert_count)

        if param == 'assert':
            if file not in filename_with_expect_and_assert_count:
                filename_with_expect_and_assert_count[file] = [0, [0]]
        elif param == 'debug':
            if file not in filename_with_expect_and_assert_count:
                filename_with_expect_and_assert_count[file] = [0, [0]]

        current_file.close()

    return filename_with_expect_and_assert_count

def folder_files_count(folder_path):
    return len(file_name_with_path_returner_(folder_path))

# Python code to merge dict using update() method
def merge_dict(dict1, dict2):
    return dict2.update(dict1)

def unit_test_files_returner():
    unittests_path = f"{os.path.normpath(os.getcwd() + os.sep + os.pardir)}{os.path.sep}unittests"
    return file_name_with_path_returner(unittests_path, '.cpp')

def regression_test_files_returner():
    regression_tests_path = f"{os.path.normpath(os.getcwd() + os.sep + os.pardir)}{os.path.sep}test"
    return file_name_with_path_returner(regression_tests_path, ['.cpp', '.ll', '.test', '.py'])

def unit_case_results():
    return [47479, 161, 503, 7]

def regression_case_results():
    return [6689, 0, 0, 7]

def subfolder_name_returner(path, type=''):
    if type == 'test':
        os.chdir(path)
    folder_names_with_path = [os.path.abspath(name) for name in os.listdir(".") if os.path.isdir(name)]
    print(folder_names_with_path)
    os.chdir("../PAT") if type == 'test' else None
    return folder_names_with_path

def folder_wise_file_returner(root, type='', extensions=['.cpp']):
    folder_file_dict = {}
    for folder in subfolder_name_returner(root, type):
        folder_file_dict[folder] = file_name_with_path_returner(folder, extensions)
    return folder_file_dict

    # unit_test_asserts_dict = \
    #     generic_count_returner_with_line_numbers(i, assert_statements, 'assert')

def test_files_folderwise_file_returner():
    return folder_wise_file_returner('../unittests', 'test', ['.cpp']), folder_wise_file_returner('../test', 'test',
                                                                                config.REGRESSION_INCLUDE_EXTENSIONS)

def dict_summer(input_dict):
    summer = 0
    for key, value in input_dict.items():
        summer = summer + value[0]
    return summer

def generic_folder_wise_assert_doer(folderwise_assert_dict):
    test_folderwise_assert_dict = {}
    for key, value in folderwise_assert_dict.items():
        print("the key is ", key)
        test_folderwise_assert_dict[key] = dict_summer(
            generic_count_returner_with_line_numbers(value, [config.ASSERT_STATEMENT_REGEX], 'assert'))
    return test_folderwise_assert_dict

def assert_count_returner_folder_wise():
    unit_test_files_folderwise, reg_test_files_folderwise = test_files_folderwise_file_returner()

    return generic_folder_wise_assert_doer(unit_test_files_folderwise), generic_folder_wise_assert_doer(
        reg_test_files_folderwise)

def subfolder_name_non_abs(path, type=''):
    if type == 'test':
        os.chdir(path)
    folder_names = [name for name in os.listdir(".") if os.path.isdir(name)]
    print(folder_names)
    os.chdir("../PAT") if type == 'test' else None
    return folder_names

def common_pie(test_dict, labels, title, explode):
    lister = []

    for key, value in test_dict.items():
        lister.append(value)

    fig1, ax1 = plt.subplots()
    ax1.pie(lister,
            shadow=True, startangle=90, explode=explode)
    plt.title(f'Folder level Assertion Distribution for {title}')
    plt.legend(labels, loc='upper left', prop={'size': 6})
    ax1.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.
    plt.savefig(f'Plots{os.path.sep}{title}pieplot.png')
    return lister


def COMPUTE_TRANSITION_FUNCTION(P, E):
    """
    转移函数
    :param P: 匹配模式
    :param E: 输入字母表
    :return:
    """
    o = {}
    m = len(P)
    q = 0
    while q < m:

        for a in E:
            k = min(m + 1, q + 2)
            while k > 0:
                k -= 1
                if P[0:k] == P[q - k + 1:q] + a:
                    break
            o["{}-{}".format(q, a)] = k
        q += 1

    def O(q, a):
        return o["{}-{}".format(q, a)]

    return O, m

def FINITE_AUTOMATION_MATCHER(T, O, m):
    """
    文本匹配状态机
    :param T: 文本
    :param o: 状态转移函数
    :param m: 唯一接收状态
    :return:
    """
    global j
    n = len(T)
    q = 0
    for i in range(0, n):
        q = O(q, T[i])
        if q == 0:
            j = i + 1
        if q == m:
            print(j, i, T[j:i + 1])

if __name__ == '__main__':
    T = "kdkdslzhaoyu"
    P = 'zhaoyu'
    O, m = COMPUTE_TRANSITION_FUNCTION(P, set(T))
    FINITE_AUTOMATION_MATCHER(T, O, m)


from Deque import Deque

class Array_Deque(Deque):

  def __init__(self):
    # capacity starts at 1; we will grow on demand.
    self.__capacity = 1
    self.__contents = [None] * self.__capacity
    self.__size = 0
    self.__front = None
    self.__back = None
    # TODO replace pass with any additional initializations you need.

  def __str__(self):
    # TODO replace pass with an implementation that returns a string of
    # exactly the same format as the __str__ method in the Linked_List_Deque.
    if self.__size == 0:
      return '[ ]'
    list_str = '[ '
    cur = self.__front
    if self.__size == self.__capacity:
      for i in range(self.__size):
        list_str = list_str + str(self.__contents[cur])
        cur += 1
        cur = cur % self.__capacity
        if i != self.__size - 1:
          list_str = list_str + ', '
    else:
      while self.__contents[cur] is not None:
        list_str = list_str + str(self.__contents[cur])
        cur += 1
        cur = cur % self.__capacity
        if self.__contents[cur % self.__capacity] is not None:
          list_str = list_str + ', '
    list_str = list_str + ' ]'
    return list_str

  def __len__(self):
    # TODO replace pass with an implementation that returns the number of
    # items in the deque. This method must run in constant time.
    return self.__size

  def __grow(self):
    # TODO replace pass with an implementation that doubles the capacity
    # and positions existing items in the deque starting in cell 0 (why is
    # necessary?)
    self.__capacity = self.__capacity * 2
    temp = [None] * self.__capacity
    cur = self.__front
    for i in range(self.__size):
      temp[i] = self.__contents[(cur % self.__size)]
      cur += 1
    self.__contents = temp
    self.__front = 0
    self.__back = self.__size - 1

  def push_front(self, val):
    # TODO replace pass with your implementation, growing the array before
    # pushing if necessary.
    if self.__size == 0:
      self.__front = 0
      self.__back = 0
      self.__contents[self.__front] = val
    else:
      if self.__capacity == self.__size:
        self.__grow()
      loc = (self.__front + self.__capacity - 1) % self.__capacity
      self.__contents[loc] = val
    self.__size += 1
    if self.__size != 1:
      self.__front = self.__front - 1
      self.__front = (self.__front + self.__capacity) % self.__capacity

  def pop_front(self):
    # TODO replace pass with your implementation. Do not reduce the capacity.
    if self.__size == 0:
      return
    remove = self.__contents[self.__front]
    self.__contents[self.__front] = None
    self.__front = (self.__front + 1) % self.__capacity
    self.__size -= 1
    return remove

  def peek_front(self):
    # TODO replace pass with your implementation.
    if self.__size==0:
      return
    return self.__contents[self.__front]

  def push_back(self, val):
    # TODO replace pass with your implementation, growing the array before
    # pushing if necessary.
    if self.__size == 0:
      self.__front = 0
      self.__back = 0
      self.__contents[self.__back] = val
    else:
      if self.__capacity == self.__size:
        self.__grow()
      loc = (self.__back + self.__capacity + 1) % self.__capacity
      self.__contents[loc] = val
    self.__size += 1
    if self.__size != 1:
      self.__back = self.__back + 1
      self.__back = (self.__back + self.__capacity) % self.__capacity

  def pop_back(self):
    # TODO replace pass with your implementation. Do not reduce the capacity.
    if self.__size == 0:
      return
    remove = self.__contents[self.__back]
    self.__contents[self.__back] = None
    self.__back = (self.__back - 1) % len(self.__contents)
    self.__size -= 1
    return remove

  def peek_back(self):
    # TODO replace pass with your implementation.
    if self.__size == 0:
      return
    return self.__contents[self.__back]

# No main section is necessary. Unit tests take its place.
#if __name__ == '__main__':
#  pass


#Zadanie 8
#Zadeklaruj klasę Student, która posiada atrybuty: imie, nazwisko, kierunek_studiow, rok_studiow, indeks. Klasa powinna posiadać
#konstruktor i metodę wypisz_mnie() która będzie wyświetlała informacje w postaci:
#Student <imie> <nazwisko>: <rok_studiow> roku na kierunku <kierunek_studiow>. Indeks: <indeks>. Stwórz 4 instancje klasy
#Student i zapisz wynik metody wypisz_mnie() do pliku, każdy student w oddzielnej linii.
class Student:
    def __init__(self, imie, nazwisko, kierunek_studiow, rok_studiow, indeks):
        self.imie = imie
        self.nazwisko = nazwisko
        self.kierunek_studiow = kierunek_studiow
        self.rok_studiow = rok_studiow
        self.indeks = indeks
    def wypisz_mnie(self):
        print(f'Student {self.imie} {self.nazwisko}: {self.rok_studiow} roku na kierunku {self.kierunek_studiow}. Indeks: {self.indeks}')
s1 = Student("Jan", "Nowak", "Informatyka", 2, 32131)
s2 = Student("Ewa", "Kowalska", "Ekonomia", 1, 54432)
s3 = Student("Kamil", "Wiśniewski", "Medycyna", 3, 44343)
s4 = Student("Paulina", "Kwaśniewska", "Weterynaria", 1, 43322)
print(s1.wypisz_mnie())
print(s2.wypisz_mnie())
print(s3.wypisz_mnie())
print(s4.wypisz_mnie())