"""
Disclaimer
This software was developed by employees of the National Institute of Standards and Technology (NIST), an agency of the Federal Government and is being made available as a public service. Pursuant to title 17 United States Code Section 105, works of NIST employees are not subject to copyright protection in the United States.  This software may be subject to foreign copyright.  Permission in the United States and in foreign countries, to the extent that NIST may hold copyright, to use, copy, modify, create derivative works, and distribute this software and its documentation without fee is hereby granted on a non-exclusive basis, provided that this notice and disclaimer of warranty appears in all copies.
THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
"""
__author__ = "Tim Blattner"
__copyright__ = "Copyright 2020, The IARPA funded TrojAI project"
__credits__ = ["Peter Bajcsy", "Michael Majurski", "Tim Blattner", "Derek Juba", "Walid Keyrouz"]
__license__ = "GPL"
__version__ = "1.0.0"
__maintainer__ = "Peter Bajcsy"
__email__ = "peter.bajcsy@nist.gov"
__status__ = "Research"

import torch
from skimage import io, transform
from itertools import repeat

from nltk.corpus import wordnet
import random

"""
This class supports creating datasets in PyTorch
The code was adopted from https://stanford.edu/~shervine/blog/pytorch-how-to-generate-data-parallel
"""

class extended_dataset_ner(torch.utils.data.Dataset):
  'Characterizes a dataset for PyTorch'
  #def __init__(self, list_IDs, labels):
  def __init__(self, list_filenames, tokenizer, max_input_length, num_iterations=1):

      #device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

      input_ids = []
      attention_mask = []
      labels = []
      labels_mask = []
      original_words = []

      num_samples = 0
      for fn in list_filenames:
          # For this example we parse the raw txt file to demonstrate tokenization.
          if fn.endswith('_tokenized.txt'):
              continue

          # load the example
          _original_words = []
          original_labels = []
          with open(fn, 'r') as fh:
              lines = fh.readlines()
              for line in lines:
                  split_line = line.split('\t')
                  word = split_line[0].strip()
                  label = split_line[2].strip()

                  _original_words.append(word)
                  original_labels.append(int(label))

          # Select your preference for tokenization
          #input_ids, attention_mask, labels, labels_mask = tokenize_and_align_labels(tokenizer, original_words, original_labels, max_input_length)
          # input_ids, attention_mask, labels, labels_mask = manual_tokenize_and_align_labels(tokenizer, original_words,
          #                                                                                original_labels,
          #                                                                                max_input_length)
          _input_ids, _attention_mask, _labels, _labels_mask = manual_tokenize_and_align_labels(tokenizer, _original_words,
                                                                                original_labels,
                                                                                max_input_length)

          input_ids.append(_input_ids)
          attention_mask.append(_attention_mask)
          labels.append(_labels)
          labels_mask.append(_labels_mask)
          original_words.append((_original_words))
          num_samples += 1
          ###############################################
          # input_ids = torch.as_tensor(input_ids)
          # attention_mask = torch.as_tensor(attention_mask)
          # labels_tensor = torch.as_tensor(labels)
          #
          # if device != 'cpu':
          #     input_ids = input_ids.to(device)
          #     attention_mask = attention_mask.to(device)
          #     labels_tensor = labels_tensor.to(device)
          #
          # # Create just a single batch
          # input_ids = torch.unsqueeze(input_ids, axis=0)
          # attention_mask = torch.unsqueeze(attention_mask, axis=0)
          # labels_tensor = torch.unsqueeze(labels_tensor, axis=0)

      #############################################################
      'Initialization'
      self.input_ids = input_ids
      self.attention_mask = attention_mask
      #self.labels_tensor = labels_tensor
      self.original_words = original_words
      self.labels_mask = labels_mask
      self.labels = labels
      self.num_samples = num_samples

      self.tokenizer = tokenizer
      self.max_input_length = max_input_length
      self.num_iterations = num_iterations
      print('Total length = {}'.format(len(self.input_ids)))

  def __len__(self):
      'Denotes the total number of samples'
      return len(self.input_ids)

  def getarrayitem(self, index, device):
      if index < 0 or index >= self.num_samples:
          print("ERROR: index is out of range:", index, " range=[", 0, ", ", self.num_samples)

      input_ids = torch.as_tensor(self.input_ids[index])
      attention_mask = torch.as_tensor(self.attention_mask[index])
      labels_tensor = torch.as_tensor(self.labels[index])

      if device != 'cpu':
          input_ids = input_ids.to(device)
          attention_mask = attention_mask.to(device)
          labels_tensor = labels_tensor.to(device)

      # Create just a single batch
      input_ids = torch.unsqueeze(input_ids, axis=0)
      attention_mask = torch.unsqueeze(attention_mask, axis=0)
      labels_tensor = torch.unsqueeze(labels_tensor, axis=0)

      self.labels_tensor = labels_tensor

      return input_ids, attention_mask, self.labels[index],  self.labels_mask[index], self.labels_tensor, self.original_words[index]

# Adapted from: https://github.com/huggingface/transformers/blob/2d27900b5d74a84b4c6b95950fd26c9d794b2d57/examples/pytorch/token-classification/run_ner.py#L318
# Create labels list to match tokenization, only the first sub-word of a tokenized word is used in prediction
# label_mask is 0 to ignore label, 1 for correct label
# -100 is the ignore_index for the loss function (https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html)
# Note, this requires 'fast' tokenization
def tokenize_and_align_labels(tokenizer, original_words, original_labels, max_input_length):
    tokenized_inputs = tokenizer(original_words, padding=True, truncation=True, is_split_into_words=True,
                                 max_length=max_input_length)
    labels = []
    label_mask = []

    word_ids = tokenized_inputs.word_ids()
    previous_word_idx = None

    for word_idx in word_ids:
        if word_idx is not None:
            cur_label = original_labels[word_idx]
        if word_idx is None:
            labels.append(-100)
            label_mask.append(0)
        elif word_idx != previous_word_idx:
            labels.append(cur_label)
            label_mask.append(1)
        else:
            labels.append(-100)
            label_mask.append(0)
        previous_word_idx = word_idx

    return tokenized_inputs['input_ids'], tokenized_inputs['attention_mask'], labels, label_mask

# Alternate method for tokenization that does not require 'fast' tokenizer (all of our tokenizers for this round have fast though)
# Create labels list to match tokenization, only the first sub-word of a tokenized word is used in prediction
# label_mask is 0 to ignore label, 1 for correct label
# -100 is the ignore_index for the loss function (https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html)
# This is a similar version that is used in trojai.
def manual_tokenize_and_align_labels(tokenizer, original_words, original_labels, max_input_length):
    labels = []
    label_mask = []
    sep_token = tokenizer.sep_token
    cls_token = tokenizer.cls_token
    tokens = []
    attention_mask = []

    # Add cls token
    tokens.append(cls_token)
    attention_mask.append(1)
    labels.append(-100)
    label_mask.append(0)

    for i, word in enumerate(original_words):
        token = tokenizer.tokenize(word)
        tokens.extend(token)
        label = original_labels[i]

        # Variable to select which token to use for label.
        # All transformers for this round use bi-directional, so we use first token
        token_label_index = 0
        for m in range(len(token)):
            attention_mask.append(1)

            if m == token_label_index:
                labels.append(label)
                label_mask.append(1)
            else:
                labels.append(-100)
                label_mask.append(0)

    if len(tokens) > max_input_length - 1:
        tokens = tokens[0:(max_input_length - 1)]
        attention_mask = attention_mask[0:(max_input_length - 1)]
        labels = labels[0:(max_input_length - 1)]
        label_mask = label_mask[0:(max_input_length - 1)]

    # Add trailing sep token
    tokens.append(sep_token)
    attention_mask.append(1)
    labels.append(-100)
    label_mask.append(0)

    input_ids = tokenizer.convert_tokens_to_ids(tokens)

    return input_ids, attention_mask, labels, label_mask


from flask import Flask
from config.settings import config
from api.app1.api import blueprint as bp_app1
from api.app2.api import blueprint as bp_app2

class Application:

    def __init__(self):
        self.app = Flask(__name__)
        self.create()

    def __str__(self):
        pass

    def message(self):
        """
        Startup message
        """
        print("""
  _____  ______  _____ _______            _____ _____
 |  __ \|  ____|/ ____|__   __|     /\   |  __ \_   _|
 | |__) | |__  | (___    | |______ /  \  | |__) || |
 |  _  /|  __|  \___ \   | |______/ /\ \ |  ___/ | |
 | | \ \| |____ ____) |  | |     / ____ \| |    _| |_
 |_|  \_\______|_____/   |_|    /_/    \_\_|   |_____|
        """)

    def config(self):
        """
        Configure all the parameters required by Flask App
        """
        self.app.config['SWAGGER_UI_DOC_EXPANSION'] = config['RESTX']['RESTPLUS_SWAGGER_UI_DOC_EXPANSION']
        self.app.config['RESTPLUS_VALIDATE'] = config['RESTX']['RESTPLUS_VALIDATE']
        self.app.config['RESTPLUS_MASK_SWAGGER'] = config['RESTX']['RESTPLUS_MASK_SWAGGER']
        self.app.config['ERROR_404_HELP'] = config['RESTX']['RESTPLUS_ERROR_404_HELP']

    def create(self):
        """
        Flask app bootstrap
        """
        self.config()
        self.app.register_blueprint(bp_app1)
        self.app.register_blueprint(bp_app2)

    def run(self):
        self.message()
        self.app.run(host=config['FLASK']['HOSTNAME'], port=config['FLASK']['PORT'], debug=config['FLASK']['DEBUG'])


import sys
import librosa
from mir_eval.onset import f_measure
import numpy as np
import matplotlib.pyplot as plt
import math
from tensor_hero.preprocessing.audio import compute_mel_spectrogram_from_audio, filter_spec_by_amplitude
if not sys.warnoptions:
    import warnings
    warnings.simplefilter("ignore")

def ninos(audio, sr, spec=None, gamma=0.94):
    '''Calculates Normalized Identifying Note Onsets based on Spectral Sparsity (NINOS)
    over time for audio.

    Implementation as described in
        https://www.eurasip.org/Proceedings/Eusipco/Eusipco2016/papers/1570256369.pdf

    Each time bin in the returned frame corresponds to approx 4.6ms of audio data.

    Args:
        audio (1D numpy array): Raw audio samples
        sr (int): sample rate
        gamma (float in (0,1]): Proportion of frequency bins to keep

    Returns:
        ninos (1D numpy array): Normalized inverse-sparsity measure
        J (int): Number of retained frequency bins
        hop_length (int): Hop length used to compute spectrogram

    '''
    # Define spectrogram parameters
    if sr == 22050:
        n_fft = 1024
        hop_length = 102
    elif sr == 44100:
        n_fft = 2048
        hop_length = 205
    else:
        raise ValueError(f'ERROR: sr = {sr}, sr must be either 22050 or 44100')

    if spec is None:
        # Compute spectrogram if not provided
        spec = np.abs(librosa.stft(audio, n_fft, hop_length))

    # Order by magnitude within each time bin
    spec = np.sort(spec, axis=0)

    # Remove the highest energy frames, cut down by factor of gamma
    J = math.floor(spec.shape[0]*gamma)
    spec = spec[:J,:]

    # Compute squared l2 norm and l4 norm of spec along time axis
    l2_squared = np.square(np.linalg.norm(spec, ord=2, axis=0))
    l4 = np.linalg.norm(spec, ord=4, axis=0)

    # Convert to NINOS
    ninos = l2_squared / ((J**(1/4))*l4)

    return ninos, J, hop_length

def squeeze_idx(idx, min, max):
    '''Helper function that ensures the indices of the windows stays between 0 and max.

    Args:
        idx (int): Candidate index
        min (int): Minimum value to be assigned (typically 0)
        max (int): Maximum value to be assigned (typically len(arr) - 1)

    Returns:
        idx (int): Correct index to be used for window bounds

    '''
    if idx < min:
        return min
    elif idx > max:
        return max
    else:
        return idx

def onset_select(odf_arr, w1=3, w2=3, w3=7, w4=1, w5=0, delta=0, plot=False):
    '''Implements peak-picking for the results of ninos ODF data.

    Implementation as described in
        https://ismir2012.ismir.net/event/papers/049_ISMIR_2012.pdf

    Args:
        odf_arr (1D numpy array): Values of ninos ODF function
        w1 (int): Hyperparameter for left boundary of window for condition 1
        w2 (int): Hyperparameter for right boundary of window for condition 1
        w3 (int): Hyperparameter for left boundary of window for condition 2
        w4 (int): Hyperparameter for right boundary of window for condition 2
        w5 (int): Hyperparameter for onset threshold (how many windows to use as buffer before selecting a new onset)
        delta (float in [0,infinity)): Threshold for condition 2
        plot (bool): Whether or not to plot onsets overlaid on ninos data

    Returns:
        onsets (1D numpy array): Frame indices for onsets

    '''
    onsets = []

    for frame in range(len(odf_arr)):
        # Determine whether candidate frame is a local maximum
        idx1 = squeeze_idx(frame-w1, 0, len(odf_arr)-1)
        idx2 = squeeze_idx(frame+w2, 0, len(odf_arr)-1)
        max_frame = idx1 + np.argmax(odf_arr[idx1:idx2])
        cond1 = frame == max_frame
        # Determine whether candidate frame surpasses local average by delta
        idx1 = squeeze_idx(frame-w3, 0, len(odf_arr)-1)
        idx2 = squeeze_idx(frame+w4, 0, len(odf_arr)-1)
        mean_frame = np.mean(odf_arr[idx1:idx2]) + delta
        cond2 = odf_arr[frame] >= mean_frame
        # True by default if onsets is empty
        cond3 = True

        if len(onsets) > 0:
            # Determine whether candidate frame surpasses a threshold since last onset
            cond3 = frame - onsets[len(onsets) - 1] > w5
        if cond1 and cond2 and cond3:
            onsets.append(frame)

    onsets = np.array(onsets)
    if plot:
        plt.figure(figsize=(20, 15))
        plt.plot(odf_arr[:1000])
        plt.vlines(onsets[np.where(onsets < 1000)[0]], ymin=0, ymax=np.max(odf_arr[:1000]), colors=['red'])
        plt.show()
    return onsets

def onset_frames_to_time(onsets, sr, hop_len):
    '''Converts a list of onset frames to corresponding time

    Args:
        onsets (1D numpy array): Frames of onsets, as determined by hop_len parameter from ninos()
        sr (int): sample rate
        hop_len (int): hop length, determines how frames are converted to times

    Returns:
        _type_: _description_
    '''
    time_per_frame = hop_len/sr
    return [x*time_per_frame for x in onsets]

def onset_times_to_bins(onset_times):
    '''Converts onset times in seconds to onset times in 10ms bins

    Args:
        onset_times (1D numpy array): array of onset times in seconds

    Returns:
        onset_times (1D numpy array): array of onset times in rounded 10ms bins
    '''
    onset_times = [round(x*100) for x in onset_times]
    return onset_times

def get_10ms_onset_frames(audio, sr, odf, hop_len, p, gamma, w1=10, w2=1, w3=1, w4=8, w5=10, delta=1.0, spec=None):
    '''Takes raw audio and uses desired odf function and parameters to predict onsets, which
    are returned as 10ms time frames relative to start and end (full audio if start and end
    aren't specified)

    The default onset_select() hyperparameters were chosen as they performed the best on average
    for the tested segments during grid search

    Args:
        audio (1D numpy array): Raw audio waveform
        sr (int): sample rate
        odf (str): desired onset detection function
        w1 (int, optional): see onset_select(). Defaults to 10.
        w2 (int, optional): see onset_select(). Defaults to 1.
        w3 (int, optional): see onset_select(). Defaults to 1.
        w4 (int, optional): see onset_select(). Defaults to 8.
        w5 (int, optional): see onset_select(). Defaults to 10.
        delta (float, optional): see onset_select(). Defaults to 1.0.
        start (int, optional): start of portion of song to compute in seconds. Defaults to -1.
                               - If negative or zero, will assume start is beginning
        end (int, optional): end of portion of song to compute in seconds. Defaults to -1.
                             - If negative or zero, will assume end is end of audio.

    Returns:
        onset_time_bins (list of ints): predicted 10ms time bins corresponding to onsets
    '''
    assert odf in ['odf', 'energy', 'odf_energy', 'log_energy_novelty', 'spectral_novelty',
                   'odf_unnormalized', 'odf_sum_energy', 'log_energy_novelty_mult_odf_energy',
                   'd_energy', 'd_energy_mult_energy'], f'ERROR: {odf} is not a valid option'

    # Get spectrogram
    if spec is None:
        spec = compute_mel_spectrogram_from_audio(audio, sr)
    spec = filter_spec_by_amplitude(spec, p=p)

    if odf == 'odf':
        o = ninos(audio, sr, spec=spec, gamma=gamma)
        o = -(o - np.min(o)) / np.max(o - np.min(o)) + 1
    elif odf == 'odf_unnormalized':
        o = ninos(audio, sr, spec=spec, gamma=gamma)
        o = -o
    elif odf == 'energy':
        o = librosa.feature.rms(S=spec, frame_length=1022)[0]
        o = -(o - np.min(o)) / np.max(o - np.min(o)) + 1
    elif odf == 'odf_energy':
        o = ninos(audio, sr, spec=spec, gamma=gamma)
        o = -(o - np.min(o)) / np.max(o - np.min(o)) + 1
        e = librosa.feature.rms(S=spec, frame_length=1022)[0]
        e = -(e - np.min(e)) / np.max(e - np.min(e)) + 1
        o = np.multiply(o, e)
        o = (o - np.min(o)) / np.max(o - np.min(o))
    elif odf == 'log_energy_novelty':
        e = librosa.feature.rms(S=spec, frame_length=1022)[0]
        log_energy = np.log1p(10*e)
        log_energy_diff = np.zeros_like(log_energy)
        log_energy_diff[1:] = np.diff(log_energy)
        o = np.max([np.zeros_like(log_energy_diff), log_energy_diff], axis=0)
        o = (o - np.min(o)) / np.max(o - np.min(o))
    elif odf == 'spectral_novelty':
        o = librosa.onset.onset_strength(S=spec)
        o = (o - np.min(o)) / np.max(o - np.min(o))
    elif odf == 'odf_sum_energy':
        o = ninos(audio, sr, spec=spec, gamma=gamma)
        e = librosa.feature.rms(S=spec, frame_length=1022)[0]
        o = o+e
        o = -(o - np.min(o)) / np.max(o - np.min(o)) + 1
    elif odf == 'log_energy_novelty_mult_odf_energy':
        e = librosa.feature.rms(S=spec, frame_length=1022)[0]
        log_energy = np.log1p(10*e)
        log_energy_diff = np.zeros_like(log_energy)
        log_energy_diff[1:] = np.diff(log_energy)
        log_energy_novelty = np.max([np.zeros_like(log_energy_diff), log_energy_diff], axis=0)
        o = ninos(audio, sr, spec=spec, gamma=gamma)
        o = np.multiply(o, e)
        o = np.multiply(o, log_energy_novelty)
        o = (o - np.min(o)) / np.max(o - np.min(o))
    elif odf == 'd_energy':
        e = librosa.feature.rms(S=spec, frame_length=1022)[0]
        o = librosa.feature.delta(e)
        o = -(o - np.min(o)) / np.max(o - np.min(o)) + 1
    elif odf == 'd_energy_mult_energy':
        e = librosa.feature.rms(S=spec, frame_length=1022)[0]
        e = -(e - np.min(e)) / np.max(e - np.min(e)) + 1
        o = librosa.feature.delta(e)
        o = -(o - np.min(o)) / np.max(o - np.min(o)) + 1
        o = np.multiply(o, e)
        o = (o - np.min(o)) / np.max(o - np.min(o))
    # Peak pick
    onsets = onset_select(o, w1, w2, w3, w4, w5, delta, plot=False)
    onset_times = onset_frames_to_time(onsets, sr=44100, hop_len=hop_len)
    onset_time_bins = onset_times_to_bins(onset_times)  # convert to 10ms time bins

    return onset_time_bins

def compare_onsets(audio, sr, notes_array, start, end,
                   w1=3, w2=3, w3=7, w4=1, w5=0, delta=0,
                   plot= False):
    '''Takes onsets from ground truth notes array, computes them from corresponding
    audio, then compares using f1 measure. Plot optional

    Args:
        audio (1D numpy array): Raw waveform of audio
        sr (int): sample rate of audio
        notes_array (1D numpy array): notes_array corresponding to audio
        start (int): start of section to measure in seconds
        end (int): end of section to measure in seconds
        [w1:w5, delta] (ints): hyperparameters of onset_select
        plot (bool): if True, will print plot of compared onsets

    Returns:
        f1 (float): f1 score of predicted onsets vs ground truth onsets
    '''
    # Measure onsets using spectral sparsity
    odf, _, hop_len = ninos(audio[sr*start:sr*end], sr)
    onsets = onset_select(odf, w1, w2, w3, w4, w5, delta, plot=False)
    onset_times = onset_frames_to_time(onsets, sr, hop_len)
    onset_time_bins = onset_times_to_bins(onset_times)

    # Get ground truth clone hero onsets
    ch_onsets = np.where(notes_array[start*100:end*100] > 0)[0]
    ch_onset_times = [x/100 for x in ch_onsets]

    # Compare with f_measure
    f1, _, _ = f_measure(np.array(ch_onset_times), np.array(onset_times))

    # plot
    if plot:
        plt.figure(figsize=(15,5))
        for o in ch_onsets:
            plt.axvline(x=o, ymin=0, ymax=0.5, color='r')
        for o in onset_time_bins:
            plt.axvline(x=o, ymin=0.5, ymax=1, color='g')

    return f1

def notes_array_onset_f1(ground_truth, candidate):
    '''Generates the onset f1 score between a ground truth and predicted notes array

    Args:
        ground_truth (1D numpy array): ground truth notes array
        candidate (1D numpy array): candidate notes array

    Returns:
        _type_: _description_
    '''
    gt_onset_times = np.where(ground_truth>0)[0]/100
    candidate_onset_times = np.where(candidate>0)[0]/100
    f1, _, _ = f_measure(gt_onset_times, candidate_onset_times)
    return f1


import os,sys
import argparse

import random
from collections import defaultdict, deque

import signal
from multiprocessing import Pool

from time import time

'''
    Below awesome fast[a/q] reader function taken
    from https://github.com/lh3/readfq/blob/master/readfq.py
'''

def readfq(fp): # this is a generator function
    last = None # this is a buffer keeping the last unprocessed line
    while True: # mimic closure; is it a bad idea?
        if not last: # the first record or a record following a fastq
            for l in fp: # search for the start of the next record
                if l[0] in '>@': # fasta/q header line
                    last = l[:-1] # save this line
                    break
        if not last: break
        name, seqs, last = last[1:].split()[0], [], None
        for l in fp: # read the sequence
            if l[0] in '@+>':
                last = l[:-1]
                break
            seqs.append(l[:-1])
        if not last or last[0] != '+': # this is a fasta record
            yield name, (''.join(seqs), None) # yield a fasta record
            if not last: break
        else: # this is a fastq record
            seq, leng, seqs = ''.join(seqs), 0, []
            for l in fp: # read the quality
                seqs.append(l[:-1])
                leng += len(l) - 1
                if leng >= len(seq): # have read enough quality
                    last = None
                    yield name, (seq, ''.join(seqs)); # yield a fastq record
                    break
            if last: # reach EOF before reading enough quality
                yield name, (seq, None) # yield a fasta record instead
                break

def reverse_complement(string):
    rev_nuc = {'A':'T', 'C':'G', 'G':'C', 'T':'A', 'U':'A', 'u':'a', 'a':'t', 'c':'g', 'g':'c', 't':'a', 'N':'N', 'X':'X', 'n':'n', 'Y':'R', 'R':'Y', 'K':'M', 'M':'K', 'S':'S', 'W':'W', 'B':'V', 'V':'B', 'H':'D', 'D':'H', 'y':'r', 'r':'y', 'k':'m', 'm':'k', 's':'s', 'w':'w', 'b':'v', 'v':'b', 'h':'d', 'd':'h'}

    rev_comp = ''.join([rev_nuc[nucl] for nucl in reversed(string)])
    return(rev_comp)

def get_minimizers(seq, k_size, w, seed_counts):
    # kmers = [seq[i:i+k_size] for i in range(len(seq)-k_size) ]
    window_kmers = deque([hash(seq[i:i+k_size]) for i in range(w)])
    curr_min = min(window_kmers)
    j = list(window_kmers).index(curr_min)
    # minimizers = [ seq[j:j+k_size] ]

    kmer = seq[j:j+k_size]
    kmer_rc = reverse_complement(kmer)
    if kmer < kmer_rc:
        seed_counts[kmer] += 1
    else:
        seed_counts[kmer_rc] += 1

    for i in range(w+1,len(seq) - k_size):
        new_kmer = hash(seq[i:i+k_size])
        # updateing window
        discarded_kmer = window_kmers.popleft()
        window_kmers.append(new_kmer)

        # we have discarded previous windows minimizer, look for new minimizer brute force
        if curr_min == discarded_kmer:
            curr_min = min(window_kmers)
            j = list(window_kmers).index(curr_min) + i - w
            # minimizers.append( seq[j:j+k_size]  )
            # seed_counts[seq[j:j+k_size]] += 1
            kmer = seq[j:j+k_size]
            kmer_rc = reverse_complement(kmer)
            if kmer < kmer_rc:
                seed_counts[kmer] += 1
            else:
                seed_counts[kmer_rc] += 1

        # Previous minimizer still in window, we only need to compare with the recently added kmer
        elif new_kmer < curr_min:
            curr_min = new_kmer
            # minimizers.append( seq[i:i+k_size] )
            # seed_counts[seq[i:i+k_size]] += 1
            kmer = seq[i:i+k_size]
            kmer_rc = reverse_complement(kmer)
            if kmer < kmer_rc:
                seed_counts[kmer] += 1
            else:
                seed_counts[kmer_rc] += 1
    # return minimizers

def get_syncmers(seq, k, s, t, seed_counts):
    window_smers = deque([hash(seq[i:i+s]) for i in range(0, k - s + 1 )])
    curr_min = min(window_smers)
    pos_min =  window_smers.index(curr_min)
    syncmers = []
    if pos_min == t:
        kmer = seq[0 : k]
        kmer_rc = reverse_complement(kmer)
        if kmer < kmer_rc:
            seed_counts[kmer] += 1
        else:
            seed_counts[kmer_rc] += 1

    for i in range(k - s + 1, len(seq) - s):
        new_smer = hash(seq[i:i+s])
        # updating window
        discarded_smer = window_smers.popleft()
        window_smers.append(new_smer)

        # Make this faster by storing pos of minimum
        curr_min = min(window_smers)
        pos_min = window_smers.index(curr_min)
        if pos_min == t:
            kmer = seq[i - (k - s) : i - (k - s) + k]
            kmer_rc = reverse_complement(kmer)
            if kmer < kmer_rc:
                seed_counts[kmer] += 1
            else:
                seed_counts[kmer_rc] += 1

    # return syncmers

def print_stats(method, k, seed_counts):
    # seed_counts = defaultdict(int)
    # for minm_list in results:
    #     for m in minm_list:
    #         seed_counts[m] += 1

    total_seed_count_sq = 0
    total_seed_count = 0
    total_seed_count_sq_1000_lim = 0
    total_seed_count_1000_lim = 0
    for seed_id, cnt in seed_counts.items():
        total_seed_count += cnt
        total_seed_count_sq += cnt**2
        if cnt <= 1000:
            total_seed_count_1000_lim += cnt
            total_seed_count_sq_1000_lim += cnt**2
    frac_masked = 1 - total_seed_count_1000_lim/total_seed_count
    print("{0},{1},{2},{3},{4}".format(method, k, total_seed_count, int(round(total_seed_count_sq / total_seed_count,0)), round(100*frac_masked, 1) ))
    # print("{0},{1},{2},{3},{4}".format(method, k, total_seed_count_1000_lim, int(round(total_seed_count_sq_1000_lim / total_seed_count_1000_lim,0)), 1000))

def min_single_helper(arguments):
    return get_minimizers(*arguments)

def syncmers_single_helper(arguments):
    return get_syncmers(*arguments)

def main(args):

    genome = {acc: seq.upper() for (acc, (seq, _)) in readfq(open(args.fasta, 'r'))}
    n = 10000000
    for acc,seq in list(genome.items()):
        acc = acc.split()[0]
        # print(acc)
        genome[acc] = seq.replace("N", "") # remove Ns

    k = args.k
    if args.type == "minimizers":
        w = 9
        seed_counts = defaultdict(int)
        for acc, seq in genome.items():
            M = get_minimizers(seq, k, w, seed_counts)
        print_stats("minimizers", k, seed_counts)

    elif args.type == "syncmers":
        s = k-4
        t = 2 # creates open syncmer with mid point with is used in strobealign
        seed_counts = defaultdict(int)
        for acc, seq in genome.items():
            get_syncmers(seq, k, s, t, seed_counts)
        print_stats("syncmers", k, seed_counts)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Calc identity", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('fasta', type=str,  default=False, help='Path to genome')
    # parser.add_argument('n', type=int,  default=4, help='Nr cores')
    parser.add_argument('k', type=int,  default=20, help='k-mer size')
    parser.add_argument('--type', type=str,  default=False, help='Either syncmenrs or minimizers')

    args = parser.parse_args()

    if len(sys.argv)==1:
        parser.print_help()
        sys.exit()

    main(args)


import json
import os

class Landmarks:
    def __init__(self, name: str):
        self.name = name
        json_path = "landmarks/" + name + ".json"
        with open(json_path) as json_file:
            data = json.load(json_file)
            self.emotion, self.version = name.split("_")
            self.fps = data["fps"]
            self.frames = data["coords"]
            self.faces = data["face"]

class LandmarksMetadata:
    def __init__(self, filename):
        self.name = filename.split(".")[0]
        self.emotion = self.name.split("_")[0]
        self.version = self.name.split("_")[1]

    def serialize(self):
        return {
            "name": self.name,
            "emotion": self.emotion,
            "version": self.version
        }

def list_landmarks_meta():
    '''
    list_landmarks returns a list of all existing LandmarksMetadata
    '''
    return [LandmarksMetadata(l) for l in os.listdir("landmarks")]

def dict_landmarks_meta():
    '''
    dict_landmarks returns a dictionary of all landmarks' metadatas mapped by type and version
    '''

    out = {}
    for l in list_landmarks_meta():
        if l.emotion not in out:
            out[l.emotion] = {}

        out[l.emotion][l.version] =  l.serialize()

    return out

def get_landmarks(type, version):
    '''
    get_landmarks returns a Landmarks object based on name
    '''
    return Landmarks(get_name(type, version))

def get_name(type, version):
    return f"{type}_{version}"


import bs4 as bs
import urllib.request
from time import strptime
from datetime import datetime
import mysql.connector
import arrow
ghtorrentDb = mysql.connector.connect(
  host="localhost",
  user="root",
  passwd="",
  database="ghtorrent"
)
project_id = 34674827
cursor = ghtorrentDb.cursor()
cursor.execute("SELECT url FROM projects WHERE id={0}".format(project_id))
url = cursor.fetchone()[0].replace("api.","").replace("/repos","")
print(url)
repoName = url.replace("https://github.com","")
repoName += "/tree/"
#print(repoName)
page = urllib.request.urlopen(url).read()
dom = bs.BeautifulSoup(page,'lxml')
totalNoOfCommits  = int(dom.body.find_all('span',class_='text-emphasized')[0].text.replace("\n",""))
aList = dom.body.find_all('a')
for shaKey in aList:
    print(shaKey.get('href'))
    if("master" not in shaKey.get('href') and repoName in shaKey.get('href')):
        print(shaKey.get('href'))
        shaKey =  shaKey.get('href').split("/")[4]
        print(totalNoOfCommits)
        nextIndex = str(totalNoOfCommits - (totalNoOfCommits%34))
        page = urllib.request.urlopen(url + '/commits').read()
        dom = bs.BeautifulSoup(page,'lxml')
        dateString = dom.body.find_all('div',class_='commit-group-title')[0].text.replace("\n","").replace("Commits on ","").replace(",","").split(" ")
        Y1 = int(dateString[2])
        M1 = int(strptime(dateString[0],'%b').tm_mon)
        D1 = int(dateString[1])
        end = datetime(Y1,M1,D1)
        print(url+'/commits/master?after='+shaKey+"+"+nextIndex)
        page = urllib.request.urlopen(url+'/commits/master?after='+shaKey+"+"+nextIndex).read()
        dom = bs.BeautifulSoup(page,'lxml')
        domGroup = dom.body.find_all('div',class_='commit-group-title')
        dateString = domGroup[len(domGroup)-1].text.replace("\n","").replace("Commits on ","").replace(",","").split(" ")
        Y2 = int(dateString[2])
        M2 = int(strptime(dateString[0],'%b').tm_mon)
        D2 = int(dateString[1])
        start = datetime(Y2,M2,D2)
        numberOfMonths = 0
        for d in arrow.Arrow.range('month', start, end):
            numberOfMonths += 1
        print(numberOfMonths)
        avgNumberOfCommitsPerMonth = float(totalNoOfCommits)/(float(numberOfMonths)*1.0)
        print('avgNumberOfCommunity:',avgNumberOfCommitsPerMonth)
        break


from abc import ABC
from typing import List
from visualBase import GameBase
from visualization.conf import *
from graph.graphCreator import createGraphVertex
from graph.graphBase import GraphType
from graph.graphBase import Graph
from graph.graphCreator import createGraphEdge
from algorithm.graphSearch import breadthFirstSearchGenerator
from visualization.utils import findMouseNode
from graph.graphCreator import GridGraphCreator
from graph.graphCreator import GraphCreator
from visualization.visualBase import GameSurface
from visualization.visualBase import UserInterface
from visualization.visualBase import NormalSurface
from graph.graphImp import GameFeasible
from graph.graphBase import Feasible
from graph.graphBase import VertexType
from algorithm.graphSearch import getPath
from algorithm.graphSearch import SearchAlgorithm
from visualization.visualBase import GameState
import pygame

class Visual(GameBase, ABC):
    def __init__(self, graphCreator: GraphCreator, searchAlgo: SearchAlgorithm):
        super(Visual, self).__init__()
        self.graphManager = graphCreator
        self.algo = searchAlgo
        self.algo.iniGenerator(self.graphManager.getVertexByInd(15, 15))

        self.gameSurface = GameSurface((800, 600), self.graphManager)
        self.helpSurface = NormalSurface((800, 200))
        self.userInterface = UserInterface()

        self.currentNode = None
        self.comeFrom = None

        self.gameState = GameState

    def processInput(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self._running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    try:
                        currentNode, comeFrom = self.algo.next()
                        self.userInterface.updateComeFrom(comeFrom)
                        self.userInterface.updateCurrentVertex(currentNode)
                    except StopIteration as e:
                        if self.comeFrom is not None:
                            pass
                        else:
                            currentNode, comeFrom = e.value
                            self.currentNode = currentNode
                            self.comeFrom = comeFrom
                elif event.key == pygame.K_g:
                    mousePos = pygame.mouse.get_pos()
                    x, y = findMouseNode(mousePos, 20)
                    self.userInterface.updateGoal((x, y))
                elif event.key == pygame.K_s:
                    mousePos = pygame.mouse.get_pos()
                    x, y = findMouseNode(mousePos, 20)
                    self.userInterface.updateStart((x, y))
                    self.gameState = GameState.START
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 3:
                    # process mouse input
                    mousePos = pygame.mouse.get_pos()
                    x, y = findMouseNode(mousePos, 20)
                    vertex = self.graphManager.getVertexByInd(y, x)
                    self.userInterface.updateBarrier((x, y))
                    self.algo.feasible.addBarrier(vertex)

                elif event.button == 1:
                    mousePos = pygame.mouse.get_pos()
                    x, y = findMouseNode(mousePos, 20)
                    self.userInterface.updatePathTarget((x, y))

    def update(self):
        self.gameSurface.update(self.userInterface)
        self.helpSurface.updateGameMessage(self.userInterface)

    def render(self):
        self._screen.blit(self.gameSurface.surface, (0, 0))
        self._screen.blit(self.helpSurface.surface, (0, 600))
        pygame.display.update()
        self._clock.tick(FPS)

    def run(self):
        while self._running:
            self.processInput()
            self.update()
            self.render()

def main():
    feasible = GameFeasible()
    graph = GridGraphCreator()
    graph.createGraph(GraphType.AdjList)
    algorithm = SearchAlgorithm(graph.graph, feasible)
    game = Visual(graph, algorithm)
    game.run()
    pygame.quit()

if __name__ == "__main__":
    main()


from PyQt5 import QtWidgets
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QSize, Qt
from .widgets import ControlButton, ScrollableButton, Seekbar, PlaybackModeControlButton
from .uilib.util import mask_image_circ, shadowify, setElide

class PlayerInfoFrame(QtWidgets.QFrame):
    def __init__(self, p):
        super(PlayerInfoFrame, self).__init__(p)
        self.hlay = QtWidgets.QHBoxLayout(self)
        self.hlay.setContentsMargins(4, 0, 16, 0)
        self.vlay = QtWidgets.QVBoxLayout()
        self.setFixedSize(208, 64)
        self.setObjectName("player-info-frame")

        self.coverArt = QtWidgets.QLabel(self)
        self.coverArt.setObjectName("player-info-cover-art")
        self.coverArt.setFixedSize(58, 58)
        self.coverArt.setPixmap(mask_image_circ("res/icons/cd.png", imgtype="png", size=58))

        self.trackTitle = QtWidgets.QLabel("----", self)
        self.trackTitle.setObjectName("player-info-track-title")

        self.trackArtist = QtWidgets.QLabel("--", self)
        self.trackArtist.setObjectName("player-info-track-artist")

        self.vlay.addWidget(self.trackTitle)
        self.vlay.addSpacing(-35)
        self.vlay.addWidget(self.trackArtist)

        self.hlay.addWidget(self.coverArt)
        self.hlay.addLayout(self.vlay)

        shadowify(self)

    def setCoverArt(self, coverPath):
        if coverPath is None:
            self.coverArt.setPixmap(mask_image_circ("res/icons/cd.png", imgtype="png", size=58))
        else:
            self.coverArt.setPixmap(mask_image_circ(coverPath, imgtype="jpg", size=58))

    def setTitle(self, trackTitle):
        setElide(self.trackTitle, trackTitle)

    def setArtist(self, trackArtist):
        setElide(self.trackArtist, trackArtist)

class PlayerControllerFrame(QtWidgets.QFrame):
    def __init__(self, p):
        super(PlayerControllerFrame, self).__init__(p)
        self.hlay = QtWidgets.QHBoxLayout(self)
        self.hlay.setContentsMargins(8, 0, 8, 0)
        self.hlay.setSpacing(0)
        self.hlay.setAlignment(Qt.AlignCenter)
        self.setFixedSize(168, 34)
        self.setObjectName("player-controller-frame")

        self.playPause = ControlButton(24, QIcon("res/icons/play.svg"), "", self)
        self.previousButton = ControlButton(16, QIcon("res/icons/skipback.svg"), "", self)
        self.nextButton = ControlButton(16, QIcon("res/icons/skipforward.svg"), "", self)
        self.rewind = ControlButton(16, QIcon("res/icons/rewind.svg"), "", self)
        self.fastForward = ControlButton(16, QIcon("res/icons/forward.svg"), "", self)

        self.rewind.setAutoRepeat(True)
        self.rewind.setAutoRepeatDelay(500)
        self.rewind.setAutoRepeatInterval(100)

        self.fastForward.setAutoRepeat(True)
        self.fastForward.setAutoRepeatDelay(500)
        self.fastForward.setAutoRepeatInterval(100)

        for button in self.findChildren(QtWidgets.QPushButton):
            button.setFixedSize(30, 30)

        self.hlay.addWidget(self.rewind)
        self.hlay.addWidget(self.previousButton)
        self.hlay.addWidget(self.playPause)
        self.hlay.addWidget(self.nextButton)
        self.hlay.addWidget(self.fastForward)

        shadowify(self)

class PlaybackControllerFrame(QtWidgets.QFrame):
    def __init__(self, p):
        super(PlaybackControllerFrame, self).__init__(p)
        self.hlay = QtWidgets.QHBoxLayout(self)
        self.hlay.setContentsMargins(0, 0, 0, 0)
        self.hlay.setSpacing(0)
        self.hlay.setAlignment(Qt.AlignCenter)
        self.setFixedSize(97, 34)
        self.setObjectName("playback-controller-frame")

        self.volumeButton = ScrollableButton(16, QIcon("res/icons/volume.svg"), "", self)
        self.playbackModeButton = PlaybackModeControlButton(16, QIcon("res/icons/repeatoff.svg"), "", self)
        self.playbackModeButton.set_state(0)
        self.equalizerButton = ControlButton(16, QIcon("res/icons/equalizer.svg"), "", self)

        for button in self.findChildren(QtWidgets.QPushButton):
            button.setIconSize(QSize(16, 16))
            button.setFixedSize(30, 30)
            self.hlay.addWidget(button)

        shadowify(self)

class SeekbarFrame(QtWidgets.QFrame):
    def __init__(self, p):
        super(SeekbarFrame, self).__init__(p)
        self.setObjectName("seekbar-frame")
        self.setFixedHeight(25)
        self.hlay = QtWidgets.QHBoxLayout(self)
        self.hlay.setContentsMargins(8, 0, 8, 0)
        self.seekbar = Seekbar(self)
        self.seekbar.setOrientation(Qt.Horizontal)
        self.hlay.addWidget(self.seekbar)

        shadowify(self)

class FavouriteFrame(QtWidgets.QFrame):
    def __init__(self, p):
        super(FavouriteFrame, self).__init__(p)
        self.setObjectName("favourite-frame")
        self.setFixedSize(34, 34)
        self.hlay = QtWidgets.QHBoxLayout(self)
        self.hlay.setContentsMargins(0, 0, 0, 0)
        self.favouriteButton = QtWidgets.QPushButton(QIcon("res/icons/fav-untoggled.svg"), "", self)
        self.favouriteButton.setIconSize(QSize(16, 16))
        self.favouriteButton.setFixedSize(30, 30)
        self.hlay.addWidget(self.favouriteButton)

        shadowify(self)

class TimeFrame(QtWidgets.QFrame):
    def __init__(self, p):
        super(TimeFrame, self).__init__(p)
        self.setObjectName("time-frame")
        self.setFixedHeight(34)
        self.hlay = QtWidgets.QHBoxLayout(self)
        self.time = QtWidgets.QLabel("--:--/--:--", self)
        self.hlay.addWidget(self.time, alignment=Qt.AlignCenter)

        shadowify(self)

class PlayerPanelLayout(QtWidgets.QHBoxLayout):
    def __init__(self):
        super(PlayerPanelLayout, self).__init__()
        self.setSpacing(10)
        self.bottom_hlay = QtWidgets.QHBoxLayout()
        self.vlay = QtWidgets.QVBoxLayout()

        self.playerInfoFrame = PlayerInfoFrame(self.parent())
        self.playerControllerFrame = PlayerControllerFrame(self.parent())
        self.playbackControllerFrame = PlaybackControllerFrame(self.parent())
        self.seekbarFrame = SeekbarFrame(self.parent())
        self.favouriteFrame = FavouriteFrame(self.parent())
        self.timeFrame = TimeFrame(self.parent())

        self.bottom_hlay.addWidget(self.playerControllerFrame)
        self.bottom_hlay.addWidget(self.playbackControllerFrame)
        self.bottom_hlay.addStretch()
        self.bottom_hlay.addWidget(self.favouriteFrame)
        self.bottom_hlay.addWidget(self.timeFrame)
        self.vlay.addWidget(self.seekbarFrame)
        self.vlay.addLayout(self.bottom_hlay)
        self.addWidget(self.playerInfoFrame)
        self.addLayout(self.vlay)


from pydantic import BaseModel
from typing import List
from config import db
from models.enums.tableType import TableType

class Constraint(BaseModel):
    table_name: TableType
    attribute_name: str
    name: str = "is_unique"
    condition: bool

    def to_dict(self):
        return {
            "table_name": self.table_name.value,
            "attribute_name": self.attribute_name,
            "is_unique": self.condition,
        }

class Constraints(BaseModel):
    constraints: List[Constraint] = []

    def __new__(cls):
        if not hasattr(cls, "_instance"):
            cls._instance = super(Constraints, cls).__new__(cls)
            cls.constraints: List[Constraint] = []
            cls.set_constraints()
        return cls.constraints

    @classmethod
    def set_constraints(cls):
        connection = db.get_connection()
        cursor = connection.cursor()

        select_query = "SELECT * FROM attributes_details;"

        cursor.execute(select_query)

        rows = cursor.fetchall()
        cls.constraints = [
            Constraint(
                table_name=TableType.EMPLOYEE
                if row[0].upper() == "EMPLOYEE"
                else TableType.PROJECT,
                attribute_name=row[1],
                condition=row[2],
            )
            for row in rows
        ]

        cursor.close()
        connection.close()

    @classmethod
    def get_columns(cls, table_name: TableType) -> List[Constraint]:
        Constraints()
        return [
            constraint
            for constraint in cls.constraints
            if constraint.table_name == table_name
        ]


import cv2

def car_lic_split(img_path):
    binary_threshold = 100
    segmentation_spacing = 0.9

    # 前處理：灰階、二值化
    img = cv2.imread(img_path)
    img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # cv2.imshow('gray', img_gray)
    # cv2.waitKey(0)
    img_thre = img_gray
    cv2.threshold(img_gray, binary_threshold, 255, cv2.THRESH_BINARY_INV, img_thre)
    # cv2.imshow('threshold', img_thre)
    # cv2.waitKey(0)

    # 分割字符
    white = []      # 記錄每一列的白色像素總和
    black = []      # 記錄每一列的黑色像素總和
    height, width = img_thre.shape[:2]
    white_max = 0   # 僅保存每列，取列中白色最多的像素總數
    black_max = 0   # 僅保存每列，取列中黑色最多的像素總數

    # 循環計算每一列的黑白色像素總和
    for i in range(width):
        w_count = 0     # 這一列白色總數
        b_count = 0     # 這一列黑色總數
        for j in range(height):
            if img_thre[j][i] == 255:
                w_count += 1
            else:
                b_count += 1
        white_max = max(white_max, w_count)
        black_max = max(black_max, b_count)
        white.append(w_count)
        black.append(b_count)

    # False表示白底黑字；True表示黑底白字
    arg = black_max > white_max

    # 分割圖像，給定參數爲要分割字符的開始位
    def find_end(start_):
        end_ = start_ + 1
        for m in range(start_+1, width - 1):
            if(black[m] if arg else white[m]) > (segmentation_spacing * black_max if arg else segmentation_spacing * white_max):
                end_ = m
                break
        return end_

    n = 1
    count = 0
    start = 0
    end = 0
    while n < width - 1:
        n += 1
        if(white[n] if arg else black[n]) > ((1 - segmentation_spacing) * white_max if arg else (1 - segmentation_spacing) * black_max):
            # 上面這些判斷用來辨別是白底黑字還是黑底白字
            start = n
            end = find_end(start)
            n = end
            if end - start > 5:
                cj = img_thre[1:height, start:end]

                cv2.imwrite('./car_lic_char_img/{0}_{1}.png'.format(d, count), cj)
                # cv2.imshow('cutChar', cj)
                # cv2.waitKey(0)
                count += 1

if __name__ == '__main__':
    data = ["Train01.jpg", "Train05.jpg", "Train09.jpg", "Train13.jpg", "Train17.jpg",
            "Train02.jpg", "Train06.jpg", "Train10.jpg", "Train14.jpg",
            "Train03.jpg", "Train07.jpg", "Train11.jpg", "Train15.jpg",
            "Train04.jpg", "Train08.jpg", "Train12.jpg", "Train16.jpg"]
    for d in data:
        img_path = './car_license_img/'+d
        car_lic_split(img_path)

    # img_path = './car_license_img/Train02.jpg'
    # car_lic_split(img_path)


import argparse
import sys
import os

def create_arg_parser():
    """"Creates and returns the ArgumentParser object."""

    parser = argparse.ArgumentParser(description='Description of your app.')
    parser.add_argument('inputDirectory',
                    help='Path to the input directory.')
    parser.add_argument('--outputDirectory',
                    help='Path to the output that contains the resumes.')
    return parser

if __name__ == "__main__":
    arg_parser = create_arg_parser()
    parsed_args = arg_parser.parse_args(sys.argv[1:])
    if os.path.exists(parsed_args.inputDirectory):
       print("File exist")


from django import template

register = template.Library()

@register.simple_tag
def bizz_or_fuzz(number):
    if number % 3 == 0 and number % 5 == 0:
        return 'BizzFuzz'
    elif number % 3 == 0:
        return 'Bizz'
    elif number % 5 == 0:
        return 'Fuzz'
    else:
        return number


"""Support for myUplink sensors."""
from __future__ import annotations

import logging

from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.const import Platform

from .api import Parameter
from .const import DOMAIN
from .entity import MyUplinkParameterEntity

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Set up the sensors."""

    coordinator = hass.data[DOMAIN][entry.entry_id]
    entities: list[SwitchEntity] = []

    for system in coordinator.data:
        for device in system.devices:
            for parameter in device.parameters:
                if parameter.find_fitting_entity() == Platform.SWITCH:
                    entities.append(
                        MyUplinkParameterSwitchEntityEntity(
                            coordinator, device, parameter
                        )
                    )

    async_add_entities(entities)

class MyUplinkParameterSwitchEntityEntity(MyUplinkParameterEntity, SwitchEntity):
    """Representation of a myUplink paramater binary sensor."""

    def _update_from_parameter(self, parameter: Parameter) -> None:
        """Update attrs from parameter."""
        super()._update_from_parameter(parameter)
        self._attr_is_on = bool(int(self._parameter.value))

    async def async_turn_on(self, **kwargs):
        """Turn the entity on."""
        await self._parameter.update_parameter(1)
        await self.async_update()

    async def async_turn_off(self, **kwargs):
        """Turn the entity off."""
        await self._parameter.update_parameter(0)
        await self.async_update()


#Michael Holloway SID: 001215316

import csv
import datetime

# entry point for the entire program to begin running
def start():
    #import distance and location data
    global distances, locations
    distances, locations = importDistanceList();

    #intial load of trucks
    #manually set to balance loads and group matching addresses together
    truck1Load = ['1', '2', '4', '5', '10', '13', '15', '19', '20', '23', '26', '27', '30', '35', '39'];
    truck2Load = ['3', '7', '8', '11', '12', '17', '18', '21', '22', '24', '29', '31', '33', '34', '36', '37', '38', '40'];
    #create our truck objects - pass in the load information
    truck1 = Truck(truck1Load);
    truck2 = Truck(truck2Load);

    #variable to allow for easy modification of maximum allowed packages
    global numberOfPackages
    numberOfPackages = 40;

    #load hub with packages that can't leave at 8am(constraints/special notes)
    hub =['6', '9', '14', '16', '25', '28', '32'];

    #import our list of packages
    global packages
    packages = importPackageList(hub, numberOfPackages);

    #iterate through truck #1 to successfully deliver every package on board
    while truck1.packageList:
        #every run find the next closest destination
        packNum, dist, packObj =  findClosestNext(truck1);
        #add the distance traveled to this truck
        truck1.travel(dist);
        #deliver primary package found
        deliverPackage(packObj, packNum, truck1);
        #set the delivery time within this particular package object
        packages.search(packNum).setDelivery(truck1.currentTime);

        #find any package with matching location and deliver to save mileage
        for package in truck1.packageList:
            packageObj = packages.search(package);
            if(packageObj.address == packObj.address):
                if packageObj.id in truck1.packageList:
                    deliverPackage(packageObj, packageObj.id, truck1);
                    packages.search(package).setDelivery(truck1.currentTime);

    #iterate through truck #2 to deliver all packages on board
    #truck 2 manually told to return to pickup additional packages at the hub at a particular time
    while truck2.packageList:
        now = datetime.datetime.now();
        #schedule a time to return to the hub to pickup remaining/delayed packages
        returnToHubTime = now.replace(hour=9, minute=6, second=0, microsecond=0);
        truckTime = truck2.currentTime;
        if(truckTime > returnToHubTime and hub):
            #pickup remaining packages a hub and deliver
            hubIndex = destinationIndex = findLocationIndex('HUB');
            distanceBetween = findDistance(truck2.currentLocation, hubIndex);
            truck2.travel(distanceBetween);
            truck2.currentLocation = hubIndex;

            #grab the package list we already have and combine it with the packages from the hub
            tempArray = hub + truck2.packageList;
            truck2.packageList = tempArray;

            #set the package pickupTime for hub elements
            for p in hub:
                packages.search(p).departureTime = truckTime;
            hub.clear(); #remove all elements from the hub so we don't repeat this

        else:
            #every run find the next closest destination
            packNum, dist, packObj =  findClosestNext(truck2);
            #add the distance traveled to this truck
            truck2.travel(dist);
            #deliver primary package found
            deliverPackage(packObj, packNum, truck2);
            #set the delivery time within this particular package object
            packages.search(packNum).setDelivery(truck2.currentTime);

            #find any package with matching location and deliver to save mileage
            for package in truck2.packageList:
                packageObj = packages.search(package);
                if(packageObj.address == packObj.address):
                    id = str(packageObj.id);
                    if id in truck2.packageList:
                        deliverPackage(packageObj, packageObj.id, truck2);
                        packages.search(package).setDelivery(truck2.currentTime);

    #being interface
    print("Select an option:");
    print("1. package information by id");
    print("2. package information by id and at a specific time");
    print("3. all package information at a specific time");
    print("4. mileage information");
    print("5. exit");

    x = input('option: '); #varibale to use as a switch for interface options
    while x != "5":
        if(x=="1"):
            packID = input('package id: ');
            packObject = packages.search(packID);
            print("-----------------------------------");
            print("Package #", packObject.id);
            print("Address:", packObject.address);
            print("City:", packObject.city);
            print("Zip Code:", packObject.zip);
            print("Package Weight:", packObject.weight);
            print("Deadline:", packObject.deadline);
            print("Delivery Time:", packObject.deliveryTime);
            print("-----------------------------------");
        if(x=="2"):
            packID = input('package id: ');
            timeQuery = input('Enter a time in HH:MM AM/PM format(i.e. 10:00 AM/2:30 PM): ');
            try: #if a single digit is provided for the hour catch the error and reformat
                hour = int(timeQuery[:2]);
                min = int(timeQuery[3:5]);
                period = timeQuery[6:].upper();#force to upper case
            except:
                hour = int(timeQuery[:1]);
                min = int(timeQuery[2:4]);
                period = timeQuery[5:].upper();#force to upper case

            if(period == "PM"): #convert to 24 hour time scale
                if(hour != 12):
                    hour = hour + 12;

            now = datetime.datetime.now();
            queryTime =  now.replace(hour=hour, minute=min, second=0, microsecond=0);
            packObject = packages.search(packID);
            timePickedUp = packObject.departureTime;
            timeDelivered = packObject.deliveryTime;
            #set a default status and test values against queryTime
            status = "At the hub";
            if(queryTime > timePickedUp and queryTime < timeDelivered):
                timeOutput = timeDelivered.strftime('%H:%M %p');
                status = "Out for delivery, ETA: " + timeOutput;
            elif(queryTime >= timeDelivered):
                timeOutput = timeDelivered.strftime('%H:%M %p');
                status = "Delivered at: " + timeOutput;

            #print package information to user
            print("-----------------------------------");
            print("Package #", packObject.id);
            print("Address:", packObject.address);
            print("City:", packObject.city);
            print("Zip Code:", packObject.zip);
            print("Package Weight:", packObject.weight);
            print("Deadline:", packObject.deadline);
            print("Stauts:", status);
            print("-----------------------------------");
        if(x=="3"):
            #printout all package information at given time
            timeQuery = input('Enter a time in HH:MM AM/PM format(i.e. 10:00 AM/2:30 PM): ');
            try: #if a single digit is provided for the hour catch the error and reformat
                hour = int(timeQuery[:2]);
                min = int(timeQuery[3:5]);
                period = timeQuery[6:].upper();#force to upper case
            except:
                hour = int(timeQuery[:1]);
                min = int(timeQuery[2:4]);
                period = timeQuery[5:].upper();#force to upper case
            if(period == "PM"): #convert to 24 hour time scale
                if(hour != 12):
                    hour = hour + 12;
            now = datetime.datetime.now();
            queryTime =  now.replace(hour=hour, minute=min, second=0, microsecond=0);
            print("-----------------------------------");

            #iterate through entire package list and print info
            for packID in range(1, numberOfPackages + 1): #known number of packages
                status = "At the hub";
                packObject = packages.search(str(packID));
                timePickedUp = packObject.departureTime;
                timeDelivered = packObject.deliveryTime;
                status = "At the hub";
                if(queryTime > timePickedUp and queryTime < timeDelivered):
                    timeOutput = timeDelivered.strftime('%H:%M %p');
                    status = "Out for delivery, ETA: " + timeOutput;
                elif(queryTime >= timeDelivered):
                    timeOutput = timeDelivered.strftime('%H:%M %p');
                    status = "Delivered at: " + timeOutput;

                #print all package information - condensed to save visual space on screen
                print("Package #:" + packObject.id + " address: " + packObject.address + ", " + packObject.city + ", " + packObject.zip + " weight: " + packObject.weight + " deadline: " + packObject.deadline + " status: " + status );

        if(x=="4"): #printout truck mileages
            truck1Miles = truck1.distanceTraveled;
            truck2Miles = truck2.distanceTraveled;
            print("-----------------------------------");
            print('Truck #1 mileage: ' + str(round(truck1Miles,2)));
            print('Truck #2 mileage: ' + str(round(truck2Miles,2)));
            print('total miles: ',round(truck1Miles + truck2Miles, 2));
            print("-----------------------------------");

        #loop through UI to menu
        print("Select an option:");
        print("1. package information by id");
        print("2. package information by id and at a specific time");
        print("3. all package information at a specific time");
        print("4. mileage information");
        print("5. exit");
        x = input('option: ');

#end main process

#start functions

#function to process our csv file and setup the data for distances/locations
#returns distanceList - a 2d array designed to allow for quick reference
#returns locationList - a list to help convert addresses to indexes
def importDistanceList():
    filename = "distanceData.csv"
    distanceList = []
    locationList = []
    with open(filename,'r') as data:
        for line in csv.reader(data, delimiter=","):
            individualDistanceList = [];
            for d in line:
                if(d.replace('.','',1).isdigit()):
                    individualDistanceList.append(d);
            address = line[1].strip().split('\n')[0];
            locationList.append(address);
            distanceList.append(individualDistanceList);
    return distanceList, locationList;

#function to process csv file for packages
#creates individual package objects that are added the the hash table for lookup speed
#returns the HashTable object packageTable
def importPackageList(hub, size):
    filename = "packageData.csv"
    packageTable = HashTable(size);
    with open(filename,'r') as data:
        for line in csv.reader(data, delimiter=","):

            id = line[0];
            address = line[1];
            pickupTime = datetime.datetime.now().replace(hour=8, minute=0, second=0, microsecond=0);
            for p in hub:
                if id == p:
                    pickupTime = datetime.datetime.now().replace(hour=9, minute=6, second=0, microsecond=0);
            deadline = line[5];
            city = line[2];
            zip = line[4];
            weight = line[6];
            #create package objects and add to packageList
            packObj = package(id, address, pickupTime, deadline, city, zip, weight);

            packageTable.insert(id, packObj);

    return packageTable;

#convert address name to the indexed location
#returns the index of the item so we can crossrefernce with our distance table
def findLocationIndex(addr):
    for items in locations:
        if addr in items:
            index = locations.index(items);
            return index;

#distance from indexed location to indexed location, each machtes index with location in distance array, use addres to find index
#returns the mileage between the 2 specified locations
def findDistance(curr, dest):
    return distances[curr][dest];

#remaining packages in truck, find closest next location
#returns the information for the closest package as well as the package object itself
def findClosestNext(truck):
    lowestDist = 999;
    lowestID = '000';
    for packageID in truck.packageList:
        packageObj = packages.search(packageID);
        packageLocIndex = findLocationIndex(packageObj.address);
        dist = findDistance(truck.currentLocation, packageLocIndex);
        f = float(dist);
        if (f < lowestDist):
            lowestDist = float(dist);
            lowestID = packageID;
    return lowestID, lowestDist, packageObj;

#interacts with the package to set delivery status
#sends a call to deliver the package in this truck object
def deliverPackage(package, packageID, truck):
    package.status = "delivered";
    truck.deliver(packageID);

#easy function to set the status of the package specified
def packageInTransit(package):
    package.status = "out for delivery";

#begin classes

#class to hold and interact with information on each truck
class Truck:
    def __init__(self, packageList):
        self.packageList = packageList;
        self.currentLocation = findLocationIndex('HUB');
        self.distanceTraveled = 0;
        self.speed = 18;
        self.currentTime = datetime.datetime.now().replace(hour=8, minute=0, second=0, microsecond=0); #set starting time

    def deliver(self, pack):
        self.packageList.remove(pack);

    def travel(self, distance):
        self.distanceTraveled = self.distanceTraveled + float(distance);
        timeTraveled = float(distance)/self.speed; #divide number of miles by miles per hour speed
        self.currentTime = self.currentTime + datetime.timedelta(hours = timeTraveled); #add appropriate partial of hour

    def addTimeByDistance(self, miles):
        timeTraveled = miles/self.speed; #divide number of miles by miles per hour speed
        self.currentTime = self.currentTime + datetime.timedelta(hours = timeTraveled); #add appropriate partial of hour

#class to hold and set delivery status for each package object
class package:
    def __init__(self, id, address, departureTime, deadline, city, zip, weight):
        self.id = id;
        self.address = address;
        self.deadline = deadline;
        self.city = city;
        self.zip = zip;
        self.weight = weight;
        self.status = "At the HUB";
        self.departureTime = departureTime;
        self.deliveryTime = datetime.datetime.now().replace(hour=8, minute=0, second=0, microsecond=0);

    def setDelivery(self, time):
        self.deliveryTime = time;

#an empty class to use as a placeholder in HashTable
class EmptyItem:
    pass

#class to hold our hash table
#brings in packages, indexes them based on a hash key
#using linearing probing to handle collisions
#function to insert an object with a given key
#function to retrieve the object based on mimicing the insert functions hash design
#function to remove the object and indexed information give the key
class HashTable:
    def __init__(self, size):
        self.EMPTY_AT_START = EmptyItem();
        self.EMPTY_AFTER_REMOVE = EmptyItem();

        self.table = [self.EMPTY_AT_START] * size;

        #take in the key(our package id) and the obj(package object)
    def insert (self, key, obj):
        #set our hashed index based on key and table size
        bucketKey = hash(key) % len(self.table);
        #utilize a key and package object so we can retrieve easily
        bucketList = [key, obj];

        #linear search of our table based on hashed key location
        bucketIndex = 0;
        while bucketIndex < len(self.table):
            #test expected bucket location
            #if filled with EmptyItem insert the paired key and package object
            if type(self.table[bucketKey]) is EmptyItem:
                self.table[bucketKey] = bucketList;
                return True;
            bucketKey = (bucketKey + 1) % len(self.table);
            bucketIndex = bucketIndex + 1;

        #return false if we can't find a location for this key within the table
        return False;

        #take in the package id as the key
    def search (self, key):
        #set our hashed index based on the key and table size
        bucketKey = hash(key) % len(self.table);

        bucketIndex = 0;

        #search starting at expected location and increase index until found
        while self.table[bucketKey] is not self.EMPTY_AT_START and bucketIndex < len(self.table):
            #get the id value from the expected bucket location
            value = (self.table[bucketKey]);
            if value[0] == key:
                retValue = self.table[bucketKey]
                #return the package object to the reqeust
                return retValue[1];

            # +1 the key if the bucket is already in use and doesn't match our id
            # collabs with our insert function to ensure the exact location is found
            bucketKey = (bucketKey + 1) % len(self.table);
            bucketIndex = bucketIndex + 1;

        #if key isn't found
        return None;

        #take in the package id as the key
    def remove (self, key):
        #set our hashed index based on the key and table size
        bucketKey = hash(key) % len(self.table);

        bucketIndex = 0;

        #search starting at expected location and increase index until found
        #ignore empty locations and fail if not found within the size of the table
        while self.table[bucketKey] is not self.EMPTY_AT_START and bucketIndex < len(self.table):
            #get the id value from the expected bucket location
            value = (self.table[bucketKey]);
            if value[0] == key:
                #if found, set to our EmptyItem class under the removed name
                self.table[bucketKey] = self.EMPTY_AFTER_REMOVE;

            # +1 the key if the bucket is already in use and doesn't match our id
            # collabs with our insert function to ensure the exact location is found
            bucketKey = (bucketKey + 1) % len(self.table);
            bucketIndex = bucketIndex + 1;

        #if key isn't found
        return None;

#begin the program
start();