# !/usr/bin/env python
import cv2
import os
import queue
import threading
import traceback
import time
from edgetpu_detection.edgetpu import TFliteDetection

# Import utilites
from object_detection.utils import label_map_util
from object_detection.utils import visualization_utils as vis_util

import olympe
from olympe.messages.ardrone3.Piloting import TakeOff, Landing
from olympe.messages.ardrone3.Piloting import moveBy
from olympe.messages.ardrone3.PilotingState import FlyingStateChanged
from olympe.messages.ardrone3.PilotingSettings import MaxTilt
from olympe.messages.ardrone3.GPSSettingsState import GPSFixStateChanged

olympe.log.update_config({"loggers": {"olympe": {"level": "WARNING"}}})

DRONE_IP = "192.168.42.1"
CWD_PATH = os.getcwd()

# Re-trained model
MODEL_DIR = "edgetpu_detection/models"
PATH_TO_MODEL = os.path.join(CWD_PATH, MODEL_DIR, 'ssd_mobilenet_v2_parrot_v1_quant_edgetpu.tflite')
# Path to label map file
PATH_TO_LABELS = os.path.join(CWD_PATH, MODEL_DIR, 'labels.txt')

class Detection(threading.Thread):

    def __init__(self, processed_queue, signal):
        # Create the olympe.Drone object from its IP address
        self.drone = olympe.Drone(DRONE_IP)
        self.frame_queue = queue.Queue()
        self.signal_queue = signal
        self.processed_frame_queue = processed_queue
        self.flush_queue_lock = threading.Lock()
        self.detector = TFliteDetection(PATH_TO_MODEL, PATH_TO_LABELS, count=1)
        super().__init__()
        super().start()

    def start(self):
        # Connect the the drone
        self.drone.connect()

        # Setup callback functions to do some live video processing
        # Yuv frames are passed into set_streaming_callbacks somehow and then
        # we collect the frames using the yuv_frame_cb function
        self.drone.set_streaming_callbacks(
            raw_cb=self.yuv_frame_cb,
            start_cb=self.start_cb,
            end_cb=self.end_cb,
            flush_raw_cb=self.flush_cb,
        )
        # Start video streaming
        self.drone.start_video_streaming()  # equivalent to pdraw.play

    def stop(self):
        # Properly stop the video stream and disconnect
        self.drone.stop_video_streaming()
        self.drone.disconnect()

    def yuv_frame_cb(self, yuv_frame):
        """
        This function will be called by Olympe for each decoded YUV frame.
            :type yuv_frame: olympe.VideoFrame

        Each video frame callback function takes an :py:func:`~olympe.VideoFrame` parameter whose
        lifetime ends after the callback execution. If this video frame is passed to another thread,
        its internal reference count need to be incremented first by calling
        :py:func:`~olympe.VideoFrame.ref`. In this case, once the frame is no longer needed, its
        reference count needs to be decremented so that this video frame can be returned to
        memory pool.
        """
        # Essentially accessing the yuv_frame from this call_back function and then
        # passes it into a queue to be picked up outside the callback function e.g. (object detection function)

        yuv_frame.ref()  # increments the reference counter of the underlying buffer(s) - need to understand

        self.frame_queue.put_nowait(yuv_frame)  # put the yuv_frames into a queue data structure

    def flush_cb(self):
        """
        Video flush callback functions are called when a video stream reclaim all its associated video buffer.
        Every frame that has been referenced
        """
        with self.flush_queue_lock:
            while not self.frame_queue.empty():
                self.frame_queue.get_nowait().unref()
        return True

    def start_cb(self):
        pass

    def end_cb(self):
        pass

    def convert_yuv_frame(self, yuv_frame):

        # the VideoFrame.info() dictionary contains some useful information
        # such as the video resolution
        info = yuv_frame.info()
        height, width = info["yuv"]["height"], info["yuv"]["width"]

        # yuv_frame.vmeta() returns a dictionary that contains additional
        # metadata from the drone (GPS coordinates, battery percentage, ...)

        # convert pdraw YUV flag to OpenCV YUV flag
        cv2_cvt_color_flag = {
            olympe.PDRAW_YUV_FORMAT_I420: cv2.COLOR_YUV2BGR_I420,
            olympe.PDRAW_YUV_FORMAT_NV12: cv2.COLOR_YUV2BGR_NV12,
        }[info["yuv"]["format"]]

        # yuv_frame.as_ndarray() is a 2D numpy array with the proper "shape"
        # i.e (3 * height / 2, width) because it's a YUV I420 or NV12 frame

        # Use OpenCV to convert the yuv frame to RGB
        cv2frame = cv2.cvtColor(yuv_frame.as_ndarray(), cv2_cvt_color_flag)
        return cv2frame

    def terminate(self):
        try:
            signal = self.signal_queue.get(timeout=0.01)
            return signal
        except queue.Empty:
            return None

    def object_detection(self):
        print('entered object detecting')
        while True:
            with self.flush_queue_lock:
                try:
                    yuv_frame = self.frame_queue.get(timeout=0.01)
                    rgb_frame = self.convert_yuv_frame(yuv_frame=yuv_frame)

                    # perform object detection
                    rgb_frame, state = self.detector.detect(rgb_frame)

                    cv2.putText(rgb_frame, "Object Detection frame", (100, 50),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 2)

                except queue.Empty:
                    if self.frame_queue.empty():
                        continue

                try:
                    self.processed_frame_queue.put_nowait((rgb_frame, state))
                    if self.terminate():
                        break

                except Exception:
                    # We have to continue popping frame from the queue even if
                    # we fail to show one frame
                    traceback.print_exc()

                finally:
                    # Don't forget to unref the yuv frame. We don't want to
                    # starve the video buffer pool
                    yuv_frame.unref()

    # TODO -
    #  Option 1: Change object tracking function to threading.run()
    #  such that object tracking will only be active for duration of flight
    #  will need destroy all windows within the fly function, if not destroyed by keyboard
    #  Option 2: Put this function as another thread

    def fly(self):
        time.sleep(20)

        pass

class VideoManager(object):
    """
    This class handles the displaying of videoframes from the drone post object detection
    """
    def __init__(self, processed_queue, signal):
        self.processed_queue = processed_queue
        thread = threading.Thread(target=self.show, args=(self.processed_queue,))
        self.lock = threading.Lock()
        self.signal_queue = signal
        thread.daemon = True      # when main program quits, any daemon threads are killed automatically)
        thread.start()            # Start the execution
        self._sentinel = False

    def show(self, q):
        """
         Method to show the frames from the drone camera
        """
        while True:
            with self.lock:
                if not q.empty():
                    try:
                        # get the processed frame queue from object detection function in Detection class
                        proc_img, state = q.get_nowait()
                        exit_condition = self.show_yuv_frame(proc_img, state)
                        if exit_condition:
                            self._sentinel = True  # send signal to break the object detection thread
                            print('breaking object detecting thread')
                            break
                    except Exception:
                        continue
                        # We have to continue popping frame from the queue even if
                        # we fail to show one frame
                        # traceback.print_exc()
                else:
                    # print('global queue is empty')
                    pass

        # indicate external completion signal from user (i.e keyboard termination)
        self.signal_queue.put_nowait(self._sentinel)

    def show_yuv_frame(self, rgb_frame, state):
        window_name = "Olympe Streaming Tracking"
        cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(window_name, 2500, 1500)
        # cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)

        # Display tracker type on frame
        if state:
            cv2.putText(rgb_frame,"Detection Successful",
                        (100, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50, 170, 50), 2)
        else:
            cv2.putText(rgb_frame, "Detection Failed",
                        (100, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50, 170, 50), 2)

        # Use OpenCV to show this frame
        cv2.imshow(window_name, rgb_frame)

        # Press 'q' to quit
        if cv2.waitKey(1) == ord('q'):
            cv2.destroyWindow(window_name)
            return True
        else:
            return False

if __name__ == "__main__":

    proc_queue = queue.Queue()

    shutdown_signal = queue.Queue(maxsize=5)

    t2 = VideoManager(proc_queue, shutdown_signal)

    # Entire thread is all the functions below so thread will be kept alive until the entire thread completes
    detection = Detection(proc_queue, shutdown_signal)

    # Start the video stream
    detection.start()

    # Perform some live video processing while the drone is flying
    # detection.fly()

    # comment this out if object_detection is threading.run() (inherited) and uncomment streaming_example.fly()
    detection.object_detection()

    # Stop the video stream
    detection.stop()

    # Recorded video stream postprocessing
    # detection.postprocessing()


""" Task
Write the processArray function, which takes an array and a callback function as parameters. The callback function can be, for example, a mathematical function that will be applied on each element of this array. Optionally, also write tests similar to the examples below.

Examples
1) Array [4, 8, 2, 7, 5] after processing with function

function(a): return a*2;
will be [ 8, 16, 4, 14, 10 ].

2) Array [7, 8, 9, 1, 2] after processing with function

function(a): return a+5;
will be [ 12, 13, 14, 6, 7 ]. """

def process_array(arr, callback):
    # initialize new array
    new_array = []
    # for every item in the passed in array, append that to the new array after it has been passed through the callback funcction
    for i in arr:
      new_array.append(callback(i))
    return new_array

my_array = [4, 8, 2, 7, 5]
func = lambda val: val * 2
my_array = process_array(my_array, func)
print(my_array)
# test.assert_equals(my_array, [ 8, 16, 4, 14, 10 ], 'The result array is wrong.');

my_array = [7, 8, 9, 1, 2];
func = lambda val: val + 5
my_array = process_array(my_array, func)
print(my_array)
# test.assert_equals(my_array, [ 12, 13, 14, 6, 7 ], 'The result array is wrong.');

my_array = [-1, 1, 2, 3, 4, 5];
func = lambda val: val ** 3
my_array = process_array(my_array, func)
print(my_array)
# test.assert_equals(my_array, [ -1, 1, 8, 27, 64, 125 ], 'The result array is wrong.');

# Best Practice Solutions:

# def process_array(arr, callback):
#     return map(callback, arr)

# def process_array(arr, callback):
#     new_arr = [callback(x) for x in arr]
#     return new_arr


import re

texto = "En esta cadena se encuentra una palabra mágica"
re.search('mágica', texto)

def validtNumber(numero):
    patron=r'^9\d{8}$'
    a=re.match(patron,numero)
    print(a,a.start()-a.end())

validtNumber('95145')


import cv2

img1 = cv2.imread("./Lenna.png")
img2 = cv2.imread("./peppers.png")

gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)

height,width = gray1.shape
minus = gray1.copy()

for i in range(width):
    for j in range(height):
        minus[i][j] = int(gray1[i][j]) - int(gray2[i][j])

        if minus[i][j] < 0:
            minus[i][j] = 0

subtract = cv2.subtract(gray1,gray2)#cv2내장 함수로
cv2.imshow('lenna',gray1)
cv2.imshow('peppers',gray2)
cv2.imshow('minus',minus)
cv2.imshow('substract_func',subtract)
cv2.waitKey(0)


from TripleTriad.player.ZeroNN import ZeroPolicy
from TripleTriad.run import MAX_POOL_SIZE

import numpy as np
import random

from keras.optimizers import Adam

# The size of batch for each training
TRAIN_BATCH_SIZE = 2048
# The number of epoch before it can be evaluated
TRAN_EPOCH = 1000
# Learning rate
LEARNING_RATE = 0.01

def run_training(queue, train_manager):

    player = ZeroPolicy(train_manager.out_dir)
    player.save_model()
    optimizer = Adam(lr=LEARNING_RATE)
    player.model.compile(loss=log_loss, optimizer=optimizer)

    game_pool = []

    while True:
        # Before training update the game pool
        if queue.qsize() + len(game_pool) > MAX_POOL_SIZE:
            pool_size = 0
            while pool_size < MAX_POOL_SIZE and not queue.empty():
                game_pool.append(queue.get())
                pool_size = pool_size + 1

        # fetch the batch from game pool
        game_batch = []
        for i in range(TRAIN_BATCH_SIZE):
            # pick a random position from a random game in the pool
            position = random.choice(random.choice(game_pool))
            game_batch.append(position)

        train_on_batch(player)
        train_manager.evaluate_policy(player, game_batch)

def log_loss(y_true, y_pred):
    '''Keras 'loss' function for the REINFORCE algorithm, where y_true is the action that was
    taken, and updates with the negative gradient will make that action more likely. We use the
    negative gradient because keras expects training data to minimize a loss function.
    '''
    return -y_true * K.log(K.clip(y_pred, K.epsilon(), 1.0 - K.epsilon()))

def pg_loss(y_true, y_pred):
    '''Keras 'loss' function for the REINFORCE algorithm, where y_true is the action that was
    taken, and updates with the negative gradient will make that action more likely. We use the
    negative gradient because keras expects training data to minimize a loss function.
    '''
    return K.sum(K.log(K.sum(y_true * K.clip(y_pred, K.epsilon(), 1.0 - K.epsilon()), axis=1)))

def train_on_batch(policy, states, card_actions, move_actions, rewards):
    for (state, card_action, move_action, result) in zip(states, card_actions, move_actions, rewards):
        policy.fit(np.concatenate(state, axis=0),
                   np.concatenate(card_action, axis=0),
                   np.concatenate(move_action, axis=0),
                   result[0] == 1)

def train_on_result(policy, states, card_actions, move_actions, rewards):
    won_tuples = [(state, card, move, won) for (game_state, game_card, game_move, game_won) in
                  zip(states, card_actions, move_actions, rewards) \
                  for (state, card, move, won) in zip(game_state, game_card, game_move, game_won) if won == 1]
    lost_tuples = [(state, card, move, won) for (game_state, game_card, game_move, game_won) in
                   zip(states, card_actions, move_actions, rewards) \
                   for (state, card, move, won) in zip(game_state, game_card, game_move, game_won) if won == -1]
    if len(won_tuples) > 0:
        won_tensors = np.transpose(won_tuples)
        policy.fit(np.concatenate(won_tensors[0], axis=0),
                   np.concatenate(won_tensors[1], axis=0),
                   np.concatenate(won_tensors[2], axis=0),
                   1)

    if len(lost_tuples) > 0:
        lost_tensors = np.transpose(lost_tuples)
        policy.fit(np.concatenate(lost_tensors[0], axis=0),
                   np.concatenate(lost_tensors[1], axis=0),
                   np.concatenate(lost_tensors[2], axis=0),
                   0)


# Kobee Raveendran
# University of Central Florida
# CAP5610 Assignment 1 - Fall 2019
# 9/13/2019

import csv
import numpy as np
import random
import pandas as pd
#import seaborn as sns
import matplotlib.pyplot as plt
import os

# loads dataset from 'iris.data' in root project directory
# as a 2-dimensional list of lists
# rows = training + testing examples
# cols = labels
#   > cols 0 - 3: features
#   > col 4: class label, encoded from string to integer
def load_dataset():
    with open('iris.data', 'r') as file:
        lines = csv.reader(file)

        dataset = list(lines)[:-1]

    for row in dataset:
        class_label = row[4][5:]

        row[:-1] = list(map(float, row[:-1]))

        if class_label == 'setosa':
            row[4] = 0

        elif class_label == 'versicolor':
            row[4] = 1

        else:
            row[4] = 2

    return dataset

# splits the dataset loaded by the function above
# into k-equally partitioned sections
# where one partition serves as the test set and
# all others become the training set
def generate_k_folds(dataset, k = 5):

    random.shuffle(dataset)

    avg, mod = divmod(len(dataset), k)

    folds = list(dataset[i * avg + min(i, mod):(i + 1) * avg + min(i + 1, mod)] for i in range(k))

    return folds

# generates text-based and plot-based confusion matrix
# inputs:
#   - actual: array of the actual class labels for the test set
#   - predicted: array of the predicted class labels for every element of the test set
#   - plot_title: string representing title of the confusion matrix (for matplotlib plot)
#   - num_classes: number of unique classes in dataset
#   - filename: if provided, saves the plot of the confusion matrix locally in folder 'plots'
def generate_confusion_matrix(actual, predicted, plot_title, num_classes = 3, filename = None):
    confusion_matrix = np.zeros((num_classes, num_classes), dtype = int)

    for i in range(len(actual)):
        confusion_matrix[actual[i]][predicted[i]] += 1

    print('\n\n' + plot_title)
    print(confusion_matrix)

    # using matplotlib only
    fig, ax = plt.subplots()
    im = ax.imshow(confusion_matrix, interpolation = 'nearest', cmap = plt.cm.Blues)
    ax.figure.colorbar(im, ax = ax)
    ax.set(xticks = np.arange(confusion_matrix.shape[0]),
           yticks = np.arange(confusion_matrix.shape[1]),
           xticklabels = ['setosa', 'versicolor', 'virginica'],
           yticklabels = ['setosa', 'versicolor', 'virginica'],
           xlabel = 'Predicted class',
           ylabel = 'True class')

    plt.title(plot_title, y = 1.12)

    ax.xaxis.set_label_position('top')
    ax.xaxis.tick_top()

    plt.setp(ax.get_xticklabels(), ha = 'center', rotation_mode = 'anchor')

    threshold = confusion_matrix.max() / 2

    for i in range(confusion_matrix.shape[0]):
        for j in range(confusion_matrix.shape[1]):
            ax.text(j, i, format(confusion_matrix[i, j], 'd'), ha = 'center', va = 'center',
            color = 'white' if confusion_matrix[i, j] > threshold else 'black')

    fig.tight_layout()

    if filename:
        if not os.path.isdir('plots'):
            os.makedirs('plots')

        plt.savefig('plots/' + filename)

    return ax

# TODO: maybe move the plotting of the confusion matrix to a standalone function
def average_confusion_matrices(confusion_matrices):

    agg_cm = np.matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])

    for matrix in confusion_matrices:
        agg_cm = agg_cm + matrix

    agg_cm = np.divide(agg_cm, len(confusion_matrices))

    filename = 'nb_average_cm.png'

    print(agg_cm)

    fig, ax = plt.subplots()
    im = ax.imshow(agg_cm, interpolation = 'nearest', cmap = plt.cm.Blues)
    ax.figure.colorbar(im, ax = ax)
    ax.set(xticks = np.arange(agg_cm.shape[0]),
           yticks = np.arange(agg_cm.shape[1]),
           xticklabels = ['setosa', 'versicolor', 'virginica'],
           yticklabels = ['setosa', 'versicolor', 'virginica'],
           xlabel = 'Predicted class',
           ylabel = 'True class')

    plt.title('Avg of Naive Bayes CMs', y = 1.12)

    ax.xaxis.set_label_position('top')
    ax.xaxis.tick_top()

    plt.setp(ax.get_xticklabels(), ha = 'center', rotation_mode = 'anchor')

    threshold = agg_cm.max() / 2

    for i in range(agg_cm.shape[0]):
        for j in range(agg_cm.shape[1]):
            ax.text(j, i, format(agg_cm[i, j], '.2f'), ha = 'center', va = 'center',
            color = 'white' if agg_cm[i, j] > threshold else 'black')

    fig.tight_layout()

    if filename:
        if not os.path.isdir('plots'):
            os.makedirs('plots')

        plt.savefig('plots/' + filename)

# ignore: I only used this to create the aggregate matrix for the best-performing knn condition
# and one for naive bayes
'''
knn_fold1 = np.matrix([[8, 0, 0], [0, 14, 0], [0, 0, 8]])
knn_fold2 = np.matrix([[9, 0, 0], [0, 10, 0], [0, 0, 11]])
knn_fold3 = np.matrix([[15, 0, 0], [0, 8, 1], [0, 0, 6]])
knn_fold4 = np.matrix([[10, 0, 0], [0, 10, 0], [0, 0, 10]])
knn_fold5 = np.matrix([[8, 0, 0], [0, 5, 2], [0, 1, 14]])
'''
'''
nb_fold1 = np.matrix([[11, 0, 0], [0, 8, 0], [0, 2, 9]])
nb_fold2 = np.matrix([[7, 0, 0], [0, 12, 0], [0, 1, 10]])
nb_fold3 = np.matrix([[12, 0, 0], [0, 8, 1], [0, 1, 8]])
nb_fold4 = np.matrix([[11, 0, 0], [0, 12, 0], [0, 0, 7]])
nb_fold5 = np.matrix([[9, 0, 0], [0, 8, 1], [0, 0, 12]])

average_confusion_matrices([nb_fold1, nb_fold2, nb_fold3, nb_fold4, nb_fold5])
'''


from urllib.parse import urlencode
from spotifyapp.db import insert_album
from spotifyapp.config import config
import requests
import csv

CLIENT_ID = config.get('client_id')  # enter client id here

CLIENT_SECRET = config.get('client_secret')  # enter client secret here

SCOPES = ['user-read-private', 'user-read-email']
BASE_AUTH_URL = 'https://accounts.spotify.com/authorize'
REDIR = 'http://localhost:5000/callback'

BASE_BROWSE_URL = 'https://api.spotify.com/v1/browse/'
BASE_NEW_RELASES_URL = BASE_BROWSE_URL + 'new-releases'

BASE_TOKEN_URL = 'https://accounts.spotify.com/api/token'
ALBUM_BASE_URL = 'https://api.spotify.com/v1/albums/'

def get_spotify_auth_url():
    params = urlencode({
        'scopes': ','.join(SCOPES),
        'response_type': 'code',
        'client_id': CLIENT_ID,
        'redirect_uri': REDIR
    })
    uri = BASE_AUTH_URL + '?' + params

    return uri

def get_new_releases(country, offset, limit, code):
    # hamtar vi auth token
    body = {
        'code': code,
        'grant_type': 'authorization_code',
        'redirect_uri': REDIR,
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET
    }
    token_response = requests.post(BASE_TOKEN_URL, data=body)
    token_data = token_response.json()
    token = token_data.get('access_token')

    # hamtar vi new releases, och skickar med token
    params = urlencode({
        'country': country,
        'offset': offset,
        'limit': limit
    })
    uri = BASE_NEW_RELASES_URL + '?' + params
    print(uri)

    # har skickar vi med token
    headers = {'Authorization': 'Bearer ' + token}

    response = requests.get(uri, headers=headers)

    data = response.json()
    albums = data['albums']['items']

    found_albums = []

    with open('albums.csv', 'w', newline='') as csvfile:
        # loopar igenom alla album fran new-releases APIet
        for album in albums:
            album_id = album.get('id')

            if album_id:
                # plocka ut mer info om albumet genom att anropa
                # album APIet och skicka med albumet i loopens ID.
                response = requests.get(
                    ALBUM_BASE_URL + album_id, headers=headers)
                album_data = response.json()

                # sakerhetsatgerd ifall album_data ar tom
                # da hoppar vi vidare i loopen och ignorerar resten
                if not album_data:
                    continue

                album = album_data
                album['actual_artists'] = []
                found_albums.append(album)

                artists = album.get('artists', [])

                # loopa igen om alla artist i albumet
                for artist in artists:
                    artist_id = artist.get('id')

                    # sakerhetsatgerd, finns inte artist_id
                    # sa struntar vi i den artisten.
                    if not artist_id:
                        continue

                    # hamta mer info om artisten
                    response = requests.get(
                        'https://api.spotify.com/v1/artists/' + artist_id,
                        headers=headers)

                    data = response.json()

                    # stoppa in info om artisten i album objektet
                    album['actual_artists'].append(data)

            name = album['name']
            release_date = album['release_date']
            popularity = album['popularity']

            writer = csv.writer(
                csvfile,
                delimiter=';',
            )

            writer.writerow([name, release_date, popularity])

            insert_album(name, release_date, popularity)

    return found_albums


from camd.loop import Loop

from camd.agent.base import RandomAgent
from camd.analysis import AnalyzeStability
from camd.experiment.base import ATFSampler
from camd.utils.data import load_default_atf_data

##########################################################
# Load dataset and filter by n_species of 2 or less
##########################################################
df = load_default_atf_data()

##########################################################
# Binary stable material discovery Random Agent example
##########################################################
n_seed = 5000  # Starting sample size
n_query = 200  # This many new candidates are "calculated with DFT" (i.e. requested from Oracle -- DFT)
agent = RandomAgent
agent_params = {'n_query': n_query}
analyzer = AnalyzeStability
analyzer_params = {'hull_distance': 0.05}
experiment = ATFSampler
experiment_params = {'dataframe': df}
candidate_data = df
##########################################################
new_loop = Loop(candidate_data, agent, experiment, analyzer,
                agent_params=agent_params, analyzer_params=analyzer_params, experiment_params=experiment_params,
                create_seed=n_seed)

new_loop.auto_loop(n_iterations=4, timeout=5, initialize=True)


"""
All available requests.
"""

import os, re
import traceback
from importlib import import_module
from muddery.server.settings import SETTINGS
from muddery.server.utils.logger import logger
from muddery.common.utils.utils import class_from_path
from muddery.common.utils.utils import get_module_path

class ElementSet(object):
    """
    All available classes.
    """
    def __init__(self):
        self.module_dict = {}
        self.class_dict = {}

        self.all_loaded = False
        self.match_class = re.compile(r'^class\s+(\w+)\s*.*$')
        self.match_key = re.compile(r""" {4}element_type\s*=\s*("|')(.+)("|')\s*$""")

    def load_files(self, component_path):
        """
        Get elements' file path.
        """
        if not component_path:
            return

        module = import_module(component_path)
        base_path = module.__path__
        if not base_path:
            return

        base_path = base_path[0]
        for root, dirs, files in os.walk(base_path):
            for filename in files:
                name, ext = os.path.splitext(filename)
                if ext != ".py":
                    continue

                with open(os.path.join(root, filename), "r", encoding="utf-8") as fp:
                    class_name = ""
                    for line in fp.readlines():
                        if not class_name:
                            match = self.match_class.match(line)
                            if match:
                                class_name = match.group(1)
                        else:
                            match = self.match_key.match(line)
                            if match:
                                key_name = match.group(2)

                                module_path = component_path
                                if base_path == root:
                                    module_path += "." + name + "." + class_name
                                else:
                                    relative_path = get_module_path(os.path.relpath(root, base_path))
                                    module_path += "." + relative_path + "." + name + "." + class_name

                                if key_name in self.module_dict:
                                    logger.log_debug("Element %s is replaced by %s." % (key_name, module_path))

                                self.module_dict[key_name] = module_path
                                class_name = ""

    def load_classes(self):
        """
        Add all elements from the element_type path.

        To prevent loop import, call this method later.
        """
        if self.all_loaded:
            return

        for key in self.module_dict:
            if key in self.class_dict:
                continue
            cls = class_from_path(self.module_dict[key])
            self.class_dict[key] = cls

        self.all_loaded = True

    def get(self, key):
        """
        Get a element_type recursively.
        """
        if key in self.class_dict:
            return self.class_dict[key]
        elif key in self.module_dict:
            try:
                cls = class_from_path(self.module_dict[key])
                if key in self.class_dict:
                    if self.class_dict[key] != cls:
                        logger.log_debug("Element %s is replaced by %s." % (key, cls))
                self.class_dict[key] = cls
                return cls
            except Exception as e:
                logger.log_trace("Load module error: %s" % e)

        # If can not find the element, return the base element.
        from muddery.server.elements.base_element import BaseElement
        return BaseElement

    def get_module(self, key):
        """
        Get a element_type's module path.
        """
        return self.module_dict.get(key, None)

    def get_group(self, group_key):
        """
        Get an element_type and all its children.
        """
        self.load_classes()

        group_class = self.class_dict[group_key]
        element_types = {group_key: group_class}

        for key in self.class_dict:
            if issubclass(self.class_dict[key], group_class):
                element_types[key] = self.class_dict[key]

        return element_types

    def get_class_models(self, key):
        """
        Get an element type's models.
        """
        cls = self.get(key)
        if cls:
            return cls.get_models()
        else:
            return []

    def get_all_info(self):
        """
        Get all element type's information.

        Return: (dict) all element type's information.
        """
        self.load_classes()

        info = {}
        for key, cls in self.class_dict.items():
            # Get the class's parent which has another element name.
            bases = cls.__bases__
            parent_type = ""
            while not parent_type:
                has_element_type = False
                for base in bases:
                    if hasattr(base, "element_type") and base.element_type:
                        has_element_type = True

                        if base.element_type != cls.element_type:
                            parent_type = base.element_type
                        else:
                            bases = base.__bases__
                        break

                if not has_element_type:
                    break

            info[key] = {
                "name": cls.element_name,
                "parent": parent_type
            }

        return info

ELEMENT_SET = ElementSet()
ELEMENT = ELEMENT_SET.get
ELEMENT_SET.load_files(SETTINGS.PATH_ELEMENTS_BASE)
ELEMENT_SET.load_files(SETTINGS.PATH_ELEMENTS_CUSTOM)


from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
from dotenv import dotenv_values
import uvicorn
import copy
from datetime import datetime
from re import search
from multiprocessing import set_start_method

from src.models.debug import UpdateItemsCountData

from .internal import TreeService, NodeType, treeToJSON, MetaData, MetaType, CollectionService, BarcodeService, HistoryService, HistoryAction
from .models.search import InsertPath, DeletePath, DeleteItemList
from .models.add import InsertData, InsertHoldingItems
from fastapi.responses import JSONResponse

config = dotenv_values('.env')

try:
    set_start_method('spawn')
except RuntimeError:
    pass

def compareLocation(a, operator, b):
        commands = {
            ">" : lambda x, y : x > y,
            "<" : lambda x, y : x < y,
            ">=": lambda x, y : x >= y,
            "<=": lambda x, y : x <= y,
            "==": lambda x, y : x == y,
        }
        print(a, b)

        aNums, aChars = a.split("-")
        bNums, bChars = b.split("-")
        aNums = int(aNums)
        bNums = int(bNums)
        aChars = sum([ord(c) for c in aChars])
        bChars = sum([ord(c) for c in bChars])

        if  aChars == bChars:
            return commands[operator](aNums, bNums)

        return commands[operator](aChars, bChars)

def mergeSortByLocation(arr):
    print(arr)
    if len(arr) > 1:
        mid = len(arr)//2

        L = arr[:mid]
        R = arr[mid:]

        mergeSortByLocation(L)
        mergeSortByLocation(R)

        i = j = k = 0
        while i < len(L) and j < len(R):
            if compareLocation(L[i]['location'], "<", R[j]['location']):
                arr[k] = L[i]
                i += 1
            else:
                arr[k] = R[j]
                j += 1
            k += 1

        while i < len(L):
            arr[k] = L[i]
            i += 1
            k += 1
        while j < len(R):
            arr[k] = R[j]
            j += 1
            k += 1

app = FastAPI()

origins = [config["ORIGIN"]]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

treeService = TreeService("tree", config['PATH'])
collectionService = CollectionService("warehouseDB", config['PATH'], CacheLength=1000000, threadSize=3)
historyService = HistoryService("logDB", config['PATH'], threadSize=3)
barcodeService = BarcodeService("barcode", config['PATH'])

# @app.on_event("shutdown")
# def shutdown_event():
#     treeService.saveObject()

@app.get("/", tags=["search"])
async def getHome():
    return Response(media_type="application/json", content=treeToJSON(treeService.navigationTree.root))

@app.get("/search", tags=["search"])
async def getSearch(path: str="", pattern: str=""):
    print(path, pattern)
    itemList = treeService.search(path, pattern)
    res = []
    for data in itemList:
        doc = collectionService.getDoc(data["id"])
        if not doc:
            return JSONResponse(status_code=404, content={"message": f"ID : {data['id']}, Item not found while searching items."})
        item = {
            "id": data["id"],
            "name": data["name"],
            "date": doc["date"],
            "path": doc["path"],
            "location": doc["location"]
        }
        res.append(item)
    return {
        "searchedResults":res
    }

@app.get("/warehouse", tags=["search"])
async def getWarehouse():
    return {
        "warehouses" :treeService.getWarehouses()
    }

@app.get("/warehouse/{warehouseName}/zone/{zoneName}", tags=["search"])
async def getShelfs(warehouseName: str, zoneName: str):
    return {
        "shelfs": treeService.getShelfs(f"warehouse{warehouseName}/zone{zoneName}")
    }

@app.get("/warehouse/{warehouseName}/zone/{zoneName}/shelf/{shelfName}", tags=["search"])
async def getItems(warehouseName: str, zoneName: str, shelfName: str):
    itemsId = treeService.getItems(f"warehouse{warehouseName}/zone{zoneName}/shelf{shelfName}")
    res = []
    for Id in itemsId:
        ## TODO process doc
        doc = copy.deepcopy(collectionService.getDoc(Id))
        if not doc:
            return JSONResponse(status_code=404, content={"message": f"ID : {Id}, Item not found while getting items."})
        doc["name"] = barcodeService.mapBarcode(doc["barcode"])["name"]
        del doc["tags"]
        del doc["barcode"]
        res.append(doc)

    mergeSortByLocation(res)
    return {
        "items" :res
    }

@app.post("/path", tags=["search"])
async def insertPath(insertPath: InsertPath):
    metaData = {
        "type": insertPath.type,
        "name": insertPath.name,
        "id": insertPath.name,
    }

    return {
        "isInserted": treeService.insert(NodeType.NON_ITEM, insertPath.path, metaData)
    }

@app.delete("/path", tags=["search"])
async def deletePath(deletePath: DeletePath):
    isDeleted, statusCode, message = treeService.deleteMultiple(NodeType.NON_ITEM, deletePath.path, deletePath.nameList)

    return JSONResponse(
        status_code=statusCode,
        content={
            "isDeleted": isDeleted,
            "message": message
        })

@app.get("/holding-items", tags=["add"])
async def getHoldingItems():
    holdingItems = collectionService.where("id","#",True, tags=["holding-items"])
    if type(holdingItems) == str:
        return {
            "holdingItems": []
        }
    itemsData = []
    for holdingItem in holdingItems:
        itemsData.append(barcodeService.mapBarcode(holdingItem['barcode'])['name'])

    processedHoldingItems = []
    for holdingItem, itemData in zip(holdingItems, itemsData):
        processedHoldingItems.append({
            "id": holdingItem['id'],
            "date": holdingItem['date'],
            "name": itemData
        })

    return {
        "holdingItems":processedHoldingItems
    }

@app.post("/holding-items", tags=["add"])
async def importItem(insertHoldingItems: InsertHoldingItems):
    barcodeList = insertHoldingItems.barcodeList
    try:
        products = []
        for barcode in barcodeList:
            products.append(barcodeService.mapBarcode(barcode))

        ids = []
        for barcode, product in zip(barcodeList, products):
            ids.append(collectionService.addDoc({
                "barcode": barcode,
                "date": datetime.now().isoformat()
            }, tags=[barcode, "holding-items"]))

        return {
            "isImported": True,
            "productIds": ids
        }
    except:

        return {
            "isImported": False
        }

@app.get("/item/{itemId}", tags=["search"])
async def getItemById(itemId: str):
    doc = collectionService.getDoc(itemId)
    if not doc:
        return JSONResponse(status_code=404, content={"message": f"ID : {itemId}, Item not found while getting item."})
    itemData = barcodeService.mapBarcode(doc["barcode"])
    res = {
        **itemData,
        **doc
    }

    #del res["barcode"]
    del res["tags"]
    del res["tag"]

    return res

@app.post("/item", tags=["add"])
async def insertItem(insertData: InsertData):
    # try:
        doc = collectionService.getDoc(insertData.id)
        if not doc:
            return JSONResponse(status_code=404, content={"message": f"ID : {insertData.id}, Item not found while adding item."})
        doc["path"] = insertData.path
        doc["location"] = insertData.location
        doc["date"] = datetime.now().isoformat()

        res = treeService.insert(NodeType.ITEM, insertData.path, {
            "name": insertData.name,
            "id": doc["id"],
            "type": MetaType.ITEM
        })

        if res:
            if "holding-items" in doc["tags"]:
                del doc["tags"]["holding-items"]
                collectionService.addTag(doc["id"], "InStorage")
            collectionService.updateDoc(insertData.id, doc)

            historyService.addLog({
                "action": str(HistoryAction.INSERT),
                "itemId": doc["id"],
                "name": barcodeService.mapBarcode(doc["barcode"])["name"],
                "date": doc["date"],
                "barcode": doc["barcode"]
            })

            return {
                "IsInserted":True
            }
        else:
            return {
               "IsInserted": False
            }
    # except:
        # return False

@app.delete("/item", tags=["search"])
async def deleteItem(deleteItemList: DeleteItemList):
    try:
        treeService.deleteMultiple(NodeType.ITEM, deleteItemList.path, deleteItemList.itemIdList)
        for id in deleteItemList.itemIdList:
            deletedDate = datetime.now().isoformat()

            collectionService.removeTag(id, "InStorage")
            collectionService.addTag(id, "OutStorage")

            doc = collectionService.getDoc(id)
            if not doc:
                return JSONResponse(status_code=404, content={"message": f"ID : {id}, Item not found while deleting."})
            collectionService.updateDoc(id, {
                **doc,
                "dateOut": deletedDate
            })

            historyService.addLog({
               "action": str(HistoryAction.EXPORT),
               "itemId": doc["id"],
                "name": barcodeService.mapBarcode(doc["barcode"])["name"],
                "date": deletedDate,
                "barcode": doc["barcode"]
            })

        return {
            "isDeleted":True
        }
    except:
        return {
            "isDeleted":False
        }

@app.get("/history", tags=["history"])
async def getHistory():
    hisDocList = copy.deepcopy(historyService.getHistory())
    for hisDoc in hisDocList:
        # hisDoc["id"] = hisDoc["itemId"]

        del hisDoc["tags"]
        # del hisDoc["itemId"]
        del hisDoc["barcode"]

    return {
        "histories":hisDocList
    }

@app.get("/history/search", tags=["history"])
async def searchHistory(pattern:str="", preDate:str="", postDate:str="", action:str=""):
    print("ENTERRR")

    res = []
    print(len(preDate), len(postDate))

    tags = {None} if len(action) == 0 else [action]

    if len(preDate) > 0 and len(postDate) > 0:
        docs = copy.deepcopy(historyService.searchByPeriod(preDate, postDate, tags=tags))

        if len(pattern) > 0:
            for doc in docs:
                if search(pattern=pattern, string=doc["name"]) is not None:
                    # doc["id"] = doc["itemId"]

                    del doc["tags"]
                    # del doc["itemId"]
                    del doc["barcode"]
                    res.append(doc)

        else:
            for doc in docs:
                # doc["id"] = doc["itemId"]

                del doc["tags"]
                # del doc["itemId"]
                del doc["barcode"]
                res.append(doc)

    elif len(pattern) > 0:
        docs = copy.deepcopy(historyService.getHistory(tags=tags))
        for doc in docs:
            if search(pattern=pattern, string=doc["name"]) is not None:
                # doc["id"] = doc["itemId"]

                del doc["tags"]
                # del doc["itemId"]
                del doc["barcode"]
                res.append(doc)

    elif len(preDate) == 0 and len(postDate) == 0:
        docs = copy.deepcopy(historyService.getHistory(tags=tags))
        for doc in docs:
            # doc["id"] = doc["itemId"]

            del doc["tags"]
            # del doc["itemId"]
            del doc["barcode"]
            res.append(doc)

    return {
        "searchedResults":res
    }

@app.post('/item-count', tags=["temporary"])
async def updateItemCount(updateItemsCountData: UpdateItemsCountData):
    path, count = updateItemsCountData.path, updateItemsCountData.count
    treeService.updateItemsCount(path, count)
    return True

if __name__ == "__main__":
    uvicorn.run("src.main:app", host=f'{config["HOST"]}', port=int(config["PORT"]), reload=False)


from collections import deque

d = deque()

for _ in range(int(input())):
    inp = input().split(' ')
    if inp[0] == "append":
        d.append(int(inp[1]))
    elif inp[0] == "appendleft":
        d.appendleft(int(inp[1]))
    elif inp[0] == "pop":
        d.pop()
    elif inp[0] == "popleft":
        d.popleft()

print(*d)

# A deque is a double-ended queue. It can be used to add or remove elements from both ends.
# Deques support thread safe, memory efficient appends and pops from either side of the deque with
# approximately the same O(1) performance in either direction.

"""d = deque()
d.append(1)
print(d)
d.appendleft(2)
print(d)
d.clear()
print(d)
d.extend('1')
print(d)
d.extendleft('234')
print(d)
print(d.count('1'))
d.pop()
print(d)
d.popleft()
print(d)
d.extend('7896')
print(d)
d.remove('2')
print(d)
d.reverse()
print(d)
d.rotate(3)
print(d)"""


## variable gamma fiber...

## model creation of gamma fiber (111) // ND
# (hkl) // ND, random vector // RD

import numpy as np
import random
from euler import euler
gauss = random.gauss
expov = random.expovariate
logno = random.lognormvariate
norma = random.normalvariate
from sym import __mmm__ as mmm

def main(ngrains=100,sigma=5.,iopt=1,ifig=1):
    """
    arguments
    =========
    ngrains = 100
    sigma   = 5.
    iopt    = 1 (1: gauss (recommended); 2: expov; 3: logno; 4: norma)
    ifig    = 1
    """
    import upf
    import matplotlib.pyplot as plt
    h = mmm()
    gr = []
    for i in range(ngrains):
        dth = random.uniform(-180., 180.)
        g = gen_gamma_gr(dth, sigma, iopt=iopt)
        for j in range(len(h)):
            temp = np.dot(g,h[j].T)
            phi1,phi,phi2 = euler(a=temp, echo=False)
            gr.append([phi1,phi,phi2,1./ngrains])

    fn = 'gam_fib_ngr%s_sigma%s.cmb'%(str(len(gr)).zfill(5),str(sigma).zfill(3))
    f = open(fn,'w')
    f.writelines('Artificial gamma fibered polycrystal aggregate\n')
    f.writelines('var_gam_fiber.py python script\n')
    f.writelines('distribution: %s')
    if iopt==1: f.writelines(' gauss')
    if iopt==2: f.writelines(' expov')
    if iopt==3: f.writelines(' logno')
    if iopt==4: f.writelines(' norma')
    f.writelines('\n')
    f.writelines('B %i\n'%ngrains)
    for i in range(len(gr)):
        f.writelines('%7.3f %7.3f %7.3f %13.4e\n'%(
            gr[i][0], gr[i][1], gr[i][2], 1./len(gr)))

    upf.cubgr(gr=gr,ifig=ifig)
    plt.figure(ifig).savefig('%s.pdf'%(fn.split('.cmb')[0]))

    return np.array(gr)

def gen_gamma_gr(th=0.,sigma=5.,iopt=1):
    """
    grain generator for gamma fiber

    =========
    Arguments

    th       = 0.0
    sigma    = 5.0
    iopt     = 1
    """
    from text import miller2mat

    ## gamma fiber: axisymmetry about ND
    hkl, uvw = hkl_gamma()
    g_casa = miller2mat(hkl, uvw)
    g_sasa = nd_rot(th)
    ##

    g = np.dot(g_casa,g_sasa)
    if iopt==1: dth=gauss(mu=0., sigma=sigma)
    if iopt==2: dth=expov(sigma)
    if iopt==3: dth=logno(mu=0., sigma=sigma)
    if iopt==4: dth=norma(mu=0., sigma=sigma)

    return rot_vectang(th=dth, r=g)

def rd_rot(thet):
    return vector_ang(u=[1,0,0],th=thet)
def td_rot(thet):
    return vector_ang(u=[0,1,0],th=thet)
def nd_rot(thet):
    return vector_ang(u=[0,0,1],th=thet)

def rot_vectang(th,r):
    """ Rotate the given rotation matrix r
    [ca<-sa] by a random axis with th degree.
    """
    #import cs
    delta, phi = rot_rand_axis()
    v = polar2vect(delta,phi)
    #rot = cs.vector_ang(u=v, th=th)
    rot = vector_ang(u=v, th=th)
    newr = np.dot(rot,r)
    return newr

def rot_rand_axis():
    delta = random.uniform(-1.,1.)
    delta = np.arccos(delta)
    phi   = random.uniform(-np.pi, np.pi)
    return delta, phi

def polar2vect(delta, phi):
    """ Given delta and phi returns a vector"""
    x = np.cos(phi)
    y = np.sin(phi)
    z = np.sin(delta)
    vector = np.array([x,y,z])
    vector = vector / np.linalg.norm(vector)
    return vector

def vector_ang(u,th):
    """
    implementation of subroutine vector_ang in cs.f

    arguments
    =========
    u[3] = vector axis about whic the rotation occurs
    th   = radian angle (degree of the rotation)
    """
    u = np.array(u)
    u = u/np.sqrt((u**2).sum()) # normalize

    idx = np.identity(3)

    ct = np.cos(th*np.pi/180.)
    st = np.sin(th*np.pi/180.)
    cm = crossop(u) # cross product operator

    # print th
    # print ct, st
    # print cm
    # raw_input()

    r = np.zeros((3,3))

    for i in range(3):
        for j in range(3):
            r[i,j] = idx[i,j] * ct + st * cm[i,j] + \
                     (1. - ct ) * u[i] * u[j]

    return r

def crossop(u):
    """
    Cross operator can be used to obtain the cross
    product as a matrix-vector product

    a x b  = [a_x]_ij [b]_j
    """
    m=np.zeros((3,3))
    m[0][1] = -u[2]
    m[0][2] =  u[1]
    m[1][0] =  u[2]
    m[1][2] = -u[0]
    m[2][0] = -u[1]
    m[2][1] =  u[0]

    return m

# hkl // ND
# uvw // RD
# xyz // TD

"""
Only major diection is relevant
Any auxilary vector that is perpendicular to the
major will work it out.
 """
def hkl_gamma():
    hkl = [ 1, 1, 1] # major // ND
    uvw = [-1, 1, 0] # minor // RD
    return hkl, uvw

def hkl_alpha():
    uvw = [ 1, 1, 0] # major // RD
    hkl = [ 0, 0, 1] # minor // ND

def hkl_eta():
    uvw = [0,0,1] # major // RD
    hkl = [1,0,0] # minor // ND

def hkl_epsilon():
    xyz = [1,1,0] # major // TD
    uvw = [0,0,1] # minor // RD


# 15486 : 퇴사 2
import sys, itertools

input = sys.stdin.readline
comb = []
N = int(input())

counseling = []
confirm = [0] * N

for _ in range(N):
    day, pay = map(sys.stdin.readline().split())
    counseling.append((_, day, pay))

for i in range(N, 0, -1):
    comb += list(itertools.combinations(list(range(N)), i))


#!/usr/bin/env python3
from Bio import Align
from Bio import SeqIO
import gzip
import pandas as pd
import csv
from Bio import pairwise2
from itertools import islice
import time
import multiprocessing

def align_DNA(query, target,
              alignment_method="pairwise2",
              mode='local',
              match_score=1,
              mismatch_score=-2,
              open_gap_score=-10,
              extend_gap_score=-1) -> dict:
    """
    Perform local alignment of two DNA sequences.

    Input: query -- Bio.Seq.Seq object, the sequencing read to map to target sequence
           target -- Bio.Seq.Seq object, the reference sequence.
           alignment_method -- the method used to perform sequence alignment, "pairwise2" or "PairwiseAligner", "pairwise2" is the default
           mode -- parameter to pass to the biopython Bio.Align.PairwiseAligner object.
           match_score, mismatch_score, open_gap_score, extend_gap_score -- parameters to pass to the biopython Bio.Align.PairwiseAligner object.

    Output: alignment_lib -- A dictionary object containing the information to describe the DNA level mutations.

    """

    # Standardize both sequences to upper cases.
    read_seq = query.upper()
    reference_seq = target.upper()

    if alignment_method=="PairwiseAligner":
    ### Alignment using PairwiseAligner
        # Create a PairwiseAligner object.
        aligner = Align.PairwiseAligner(mode='local',
                                        match_score=match_score,
                                        mismatch_score=mismatch_score,
                                        open_gap_score=open_gap_score,
                                        extend_gap_score=extend_gap_score)

        alignment = list(aligner.align(reference_seq, read_seq, strand='+'))[0]
        alignment_r = list(aligner.align(reference_seq, read_seq, strand='-'))[0]
        strand = 'forward'
        alignment_start=alignment.indices[1][alignment.indices[1] > 0].min()

        if alignment_r.score > alignment.score:
            strand = 'reverse'
            alignment = alignment_r
            read_seq = read_seq.reverse_complement()
            alignment_start = len(read_seq) - alignment.indices[1][alignment.indices[1] > 0].max() -1 ## the -1 is important. Otherwise the alignments on the reverse strands will be shifted by one digit to miss the one bp on the 5' end and gain one bp on the 3' end.

        reference = alignment[0] # str, with '-' for insertion
        query = alignment[1] # str, with '-' for deletion
        alignment_length = len(alignment[0]) # alignment[0] is the target sequence, with '-' for insertsion

    ### Alignment using PairwiseAligner done

    elif alignment_method=="pairwise2":
    ### Alignment using pairwise2 localms

        # perform alignment
        alignments = pairwise2.align.localms(read_seq,
                                             reference_seq,
                                             match_score,
                                             mismatch_score,
                                             open_gap_score,
                                             extend_gap_score,
                                             one_alignment_only=True)

        alignments_r = pairwise2.align.localms(read_seq.reverse_complement(),
                                               reference_seq,
                                               match_score,
                                               mismatch_score,
                                               open_gap_score,
                                               extend_gap_score,
                                               one_alignment_only=True)
        strand = 'forward'

        if alignments_r[0][2] > alignments[0][2]:
            alignments = alignments_r
            read_seq = read_seq.reverse_complement()
            strand = 'reverse'

        # Get the alignment with the highest score (assuming unique alignment)
        best_alignment = alignments[0]

        # Get the starting position
        alignment_start = best_alignment[3]
        alignment_end = best_alignment[4]

        # Calculate alignment statistics
        reference = best_alignment[1].strip('-')
        # alignment_length = len(re.findall(r'ATG.*', reference)[0])
        alignment_length = alignment_end - alignment_start
        query = best_alignment[0][alignment_start: alignment_start+alignment_length]

    ### Alignment using pairwise2 localms done

    insertions = []
    deletions = []
    substitutions = []

    for j in range(len(query)):
        if query[j] == '-':
            deletions.append(j)
        elif reference[j] == '-':
            insertions.append(j)
        elif (query[j] != reference[j]):
            substitutions.append(reference[j]+str(j)+query[j])

    alignment_lib={"sequence": read_seq[alignment_start:alignment_start+alignment_length-len(deletions)],
                   "read_length": len(read_seq),
                   "alignment_start": alignment_start,
                   "alignment_length": alignment_length,
                   "insertions": insertions,
                   "num_insertions": len(insertions),
                   "deletions":deletions,
                   "num_deletions":len(deletions),
                   "substitutions": substitutions,
                   "num_substitutions":len(substitutions),
                   "strand":strand}

    return alignment_lib

def align_DNA_write_to_csv(input_file,
                           output_file,
                           target,
                           alignment_method="pairwise2",
                           mode='local',
                           match_score=1,
                           mismatch_score=-2,
                           open_gap_score=-10,
                           extend_gap_score=-1,
                           set_num_iterations=True,
                           start_read=0,
                           num_iterations=500):
    """
    Function that reads in sequences from a .fastq.gz file, perform alignment and save each entry to .csv at a time.

    input: input_file -- str, input file path
           output_file -- str, output file path
           target -- Bio.Seq.Seq object, the reference sequence, to be passed into mutalign.align_DNA()
           alignment_method -- the method used to perform sequence alignment, "pairwise2" or "PairwiseAligner", "pairwise2" is the default
           mode -- parameter to pass to the biopython Bio.Align.PairwiseAligner object, , to be passed into mutalign.align_DNA()
           match_score, mismatch_score, open_gap_score, extend_gap_score -- parameters to pass to the biopython Bio.Align.PairwiseAligner object, to be passed into mutalign.align_DNA()
           set_num_iterations -- bool, whether or not the user intends to set an upper limit on the number of entries to process, default is True. When set to false, the function processes the entire fastq.gz set.
           start_iter -- int, the index of read to start reading.
           num_iterations -- int, to be set if set_num_iterations is True. Default is 500

    """
    with open(output_file, mode='w', newline='') as csv_file:

        # if user not intended to set the number of entries to align, the default is the iterate through the entire dataset
        if not set_num_iterations:
            with gzip.open(input_file, "rt") as handle:
                num_iterations = sum([1 for _ in SeqIO.parse(handle, 'fastq')])

        ct=0
        with gzip.open(input_file, "rt") as handle:
            seq_iter=SeqIO.parse(handle, 'fastq')
            if start_read !=0:
                seq_iter=islice(seq_iter, start_read, None)
            for record in seq_iter:

                try:
                    # prioritize the alignment method specified by user,
                    # using multithread to eliminate sequences causing dead kernal.
                    # Use multiprocessing to run PairwiseAligner with a timeout. The processing time increases 10% for each sequence. multithreading is more lightweight but really doesn't prevent the kernel from dying.
                    pool = multiprocessing.Pool(processes=1)
                    aligner_result = pool.apply_async(align_DNA, args=(record.seq, target),
                                                     kwds={'alignment_method':'PairwiseAligner',
                                                           'mode':mode,
                                                           'match_score':match_score,
                                                           'mismatch_score':mismatch_score,
                                                           'open_gap_score':open_gap_score,
                                                           'extend_gap_score':extend_gap_score})
                    alignment_result = aligner_result.get(timeout=5)  # Set an appropriate timeout value
                    pool.terminate()  # Terminate the process to free up resources
                except multiprocessing.TimeoutError:
                    # In case of timeout, use pairwise2 aligner instead
                    alignment_result = align_DNA(record.seq, target,
                                                    alignment_method='pairwise2',
                                                    mode=mode,
                                                    match_score=match_score,
                                                    mismatch_score=mismatch_score,
                                                    open_gap_score=open_gap_score,
                                                    extend_gap_score=extend_gap_score)

                except MemoryError:  # if pairwisealigner generates a memory error, use pairsise2
                    alignment_result = align_DNA(record.seq,
                                                 target=target,
                                                 alignment_method='pairwise2',
                                                 mode=mode,
                                                 match_score=match_score,
                                                 mismatch_score=mismatch_score,
                                                 open_gap_score=open_gap_score,
                                                 extend_gap_score=extend_gap_score)  # Assuming `target_sequence` is defined

                alignment_start=alignment_result["alignment_start"]
                alignment_length=alignment_result["alignment_length"]
                num_deletions=alignment_result["num_deletions"]
                # read_seq[alignment_start:alignment_start+alignment_length-num_deletions]

                alignment_result.update({'sequence_id':record.id})
                alignment_result.update({'quality':record.letter_annotations["phred_quality"][alignment_start:alignment_start+alignment_length-num_deletions]})

                if ct == 0: # first iteration writes the header
                    writer = csv.DictWriter(csv_file, fieldnames=alignment_result.keys())
                    writer.writeheader() # write the header for the output .csv file

                writer.writerow(alignment_result) # write one row per iteration

                ct+=1 # iterate until the set limit

                if ct%100 == 0:
                    print(f'{ct} iterations done')
                if ct >= num_iterations:
                    break

def align_all_DNA(file_path, target, exclude_short=False):
    """
    Extract every seq in a fastq.gz file, align them to a target seq and calculate
    indels and substitutions. Save results to a pandas DataFrame

    input: filepath -- str, path of the fastq.gz file
           target -- Bio.Seq.Seq object to be used as the target for alignment
           exclude_short -- int if not False, the minimum number of bps in the read to allow the read to be included to the output df
    """
    if file_path[-8:] != 'fastq.gz':
        raise TypeError('The input file should have a .fastq.gz format')

    with gzip.open(file_path, "rt") as handle:
        # Use SeqIO.parse() to read and parse the sequences
        record_list = []
        for record in SeqIO.parse(handle, "fastq"):
            record_lib={}
            record_lib['sequence_id'] = record.id
            record_lib['sequence'] = record.seq
            # when exclude_short is specified, short reads are excluded from the df.
            if (exclude_short) and (len(record.seq)<exclude_short):
                continue
            # when exclude_short is not specified, or if the read is long, info included in the output df
            else:
                record_lib['quality_scores'] = record.letter_annotations["phred_quality"]
                record_lib.update(align_DNA(record.seq, target))
                record_list.append(record_lib)

    return pd.DataFrame(record_list)


from gates.NotGate import NotGate
from gates.Mux16Gate import Mux16Gate
from gates.RegisterGate import RegisterGate
from gates.AluGate import AluGate
from gates.AndGate import AndGate
from gates.PcGate import PcGate
from gates.OrGate import OrGate

class CPU:
    def __init__(self):
        self.not_gate = NotGate()
        self.mux16_gate = Mux16Gate()
        self.alu_gate = AluGate()
        self.and_gate = AndGate()
        self.or_gate = OrGate()

        self.register_a = RegisterGate()
        self.register_d = RegisterGate()

        self.pc_gate = PcGate()

    def __call__(self,inM,instruction,reset):
        # instruction a
        if(instruction[0] == 0):
          a = self.register_a(instruction,1)
          addressM = a.copy()
          addressM[0] = 0

          out_pc = self.pc_gate(a,0,1,reset)
          out_pc[0] = 0

          out = [0]*16

          return out,0,addressM,out_pc
        # instruction c
        else:
          a = self.register_a(instruction,instruction[10])
          addressM = a.copy()
          addressM[0] = 0
          d = self.register_d(instruction,0)
          a_or_d = self.mux16_gate(a, inM, instruction[3])

          out, zr, ng = self.alu_gate(d, a_or_d, instruction[4], instruction[5], instruction[6], instruction[7], instruction[8], instruction[9])

          self.register_d(out,instruction[11])
          writeM = instruction[12]

          not_ng = self.not_gate(ng)
          not_zr = self.not_gate(zr)
          jgt = instruction[15]
          posnzr = self.and_gate(not_ng, not_zr)
          ld1 = self.and_gate(jgt, posnzr)

          jeq = instruction[14]
          ld2 = self.and_gate(jeq,not_zr)

          jlt = instruction[13]
          ld3 = self.and_gate(jlt,ng)

          ldt = self.or_gate(ld1,ld2)
          ld = self.or_gate(ld3,ldt)

          out_pc = self.pc_gate(a,ld,1,reset)
          out_pc[0] = 0

          return out,writeM,addressM,out_pc


from celery import Celery
from flask_mail import Message

from _15thnight.email import mailer
from _15thnight.twilio_client import send_sms

try:
    from config import CELERY_BROKER
except:
    from configdist import CELERY_BROKER

celery = Celery('15thnight', broker=CELERY_BROKER)

def init_app(app):
    TaskBase = celery.Task

    class ContextTask(TaskBase):
        abstract = True

        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)

    celery.Task = ContextTask

@celery.task
def queue_send_message(email, number, subject, body):
    """
    Celery task to send messages out in sms and email.
    """
    if number:
        send_sms(to_number=number, body=body)
    message = Message(body=body, subject=subject, recipients=[email])
    mailer.send(message)

@celery.task
def queue_send_email(message):
    mailer.send(message)


from __future__ import print_function, division

import datetime
import os

import matplotlib.pyplot as plt
import numpy as np
from data_loader import DataLoader
from tensorflow.keras.layers import Input, Concatenate
from tensorflow.keras.layers import LeakyReLU
from tensorflow.keras.layers import UpSampling2D, Conv2D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow_addons.layers.normalizations import InstanceNormalization

IMG_SIZE = 256

def build_generator(img: Input) -> Model:
    """
    U-Net Generator
    :param img: input image placeholder
    """

    def downsampling2d(layer_input, filters: int):
        """Layers used in the encoder"""
        d = Conv2D(filters=filters,
                   kernel_size=4,
                   strides=2,
                   padding='same')(layer_input)
        d = LeakyReLU(alpha=0.2)(d)
        d = InstanceNormalization()(d)
        return d

    def upsampling2d(layer_input, skip_input, filters: int):
        """
        Layers used in the decoder
        :param layer_input: input layer
        :param skip_input: another input from the corresponding encoder block
        :param filters: number of filter
        """
        u = UpSampling2D(size=2)(layer_input)
        u = Conv2D(filters=filters, kernel_size=4, strides=1, padding='same',
                   activation='relu')(u)
        u = InstanceNormalization()(u)
        u = Concatenate()([u, skip_input])
        return u

    # Encoder
    gf = 32
    d1 = downsampling2d(img, gf)
    d2 = downsampling2d(d1, gf * 2)
    d3 = downsampling2d(d2, gf * 4)
    d4 = downsampling2d(d3, gf * 8)

    # Decoder
    # Note that we concatenate each upsampling2d block with
    # its corresponding downsampling2d block, as per U-net
    u1 = upsampling2d(d4, d3, gf * 4)
    u2 = upsampling2d(u1, d2, gf * 2)
    u3 = upsampling2d(u2, d1, gf)

    u4 = UpSampling2D(size=2)(u3)
    output_img = Conv2D(3, kernel_size=4, strides=1, padding='same', activation='tanh')(u4)

    model = Model(img, output_img)

    model.summary()

    return model

def build_discriminator(img: Input) -> Model:
    """
    CNN discriminator
    :param img: input image placeholder
    """

    def d_layer(layer_input, filters, f_size=4, normalization=True):
        """Discriminator layer"""
        d = Conv2D(filters, kernel_size=f_size, strides=2, padding='same')(layer_input)
        d = LeakyReLU(alpha=0.2)(d)
        if normalization:
            d = InstanceNormalization()(d)
        return d

    df = 64

    d1 = d_layer(img, df, normalization=False)
    d2 = d_layer(d1, df * 2)
    d3 = d_layer(d2, df * 4)
    d4 = d_layer(d3, df * 8)

    validity = Conv2D(1, kernel_size=4, strides=1, padding='same')(d4)

    model = Model(img, validity)

    model.summary()

    return model

def train(epochs: int,
          data_loader: DataLoader,
          g_XY: Model,
          g_YX: Model,
          d_X: Model,
          d_Y: Model,
          combined: Model,
          batch_size=1,
          sample_interval=50):
    """
    :param epochs: number of steps to train
    :param data_loader: the data loader
    :param g_XY: generator G = X -> Y
    :param g_YX: generator F = Y -> X
    :param d_X: discriminator for X
    :param d_Y: discriminator for Y
    :param combined: the combined generator/discriminator model
    :param batch_size: mini batch size
    :param sample_interval: how often to sample images
    """
    start_time = datetime.datetime.now()

    # Calculate output shape of D (PatchGAN)
    patch = int(IMG_SIZE / 2 ** 4)
    disc_patch = (patch, patch, 1)

    # GAN loss ground truths
    valid = np.ones((batch_size,) + disc_patch)
    fake = np.zeros((batch_size,) + disc_patch)

    for epoch in range(epochs):
        for batch_i, (imgs_X, imgs_Y) in enumerate(data_loader.load_batch(batch_size)):
            # Train the discriminators

            # Translate images to opposite domain
            fake_Y = g_XY.predict(imgs_X)
            fake_X = g_YX.predict(imgs_Y)

            # Train the discriminators (original images = real / translated = Fake)
            dX_loss_real = d_X.train_on_batch(imgs_X, valid)
            dX_loss_fake = d_X.train_on_batch(fake_X, fake)
            dX_loss = 0.5 * np.add(dX_loss_real, dX_loss_fake)

            dY_loss_real = d_Y.train_on_batch(imgs_Y, valid)
            dY_loss_fake = d_Y.train_on_batch(fake_Y, fake)
            dY_loss = 0.5 * np.add(dY_loss_real, dY_loss_fake)

            # Total disciminator loss
            d_loss = 0.5 * np.add(dX_loss, dY_loss)

            # Train the generators
            g_loss = combined.train_on_batch([imgs_X, imgs_Y],
                                             [valid, valid,
                                              imgs_X, imgs_Y,
                                              imgs_X, imgs_Y])

            elapsed_time = datetime.datetime.now() - start_time

            # Plot the progress
            print("[Epoch %d/%d] [Batch %d/%d] [D loss: %f, acc: %3d%%] [G loss: %05f, adv: %05f, recon: %05f, id: %05f] time: %s " \
                  % (epoch, epochs, batch_i, data_loader.n_batches, d_loss[0], 100 * d_loss[1], g_loss[0], np.mean(g_loss[1:3]), np.mean(g_loss[3:5]), np.mean(g_loss[5:6]), elapsed_time))

            # If at save interval => save generated image samples
            if batch_i % sample_interval == 0:
                sample_images(epoch, batch_i, g_XY, g_YX, data_loader)

def sample_images(epoch: int,
                  current_batch: int,
                  g_XY: Model,
                  g_YX: Model,
                  data_loader: DataLoader):
    """
    Sample new generated images
    :param epoch: current epoch
    :param current_batch: current batch
    :param g_XY: generator G: X->Y
    :param g_YX: generator F: Y->X
    :param data_loader: data loader
    """
    os.makedirs('images/%s' % data_loader.dataset_name, exist_ok=True)
    r, c = 2, 3

    imgs_X = data_loader.load_data(domain="A", batch_size=1, is_testing=True)
    imgs_Y = data_loader.load_data(domain="B", batch_size=1, is_testing=True)

    # Translate images to the other domain
    fake_Y = g_XY.predict(imgs_X)
    fake_X = g_YX.predict(imgs_Y)

    # Translate back to original domain
    reconstr_X = g_YX.predict(fake_Y)
    reconstr_Y = g_XY.predict(fake_X)

    gen_imgs = np.concatenate([imgs_X, fake_Y, reconstr_X, imgs_Y, fake_X, reconstr_Y])

    # Rescale images 0 - 1
    gen_imgs = 0.5 * gen_imgs + 0.5

    titles = ['Original', 'Translated', 'Reconstructed']
    fig, axs = plt.subplots(r, c)
    cnt = 0
    for i in range(r):
        for j in range(c):
            axs[i, j].imshow(gen_imgs[cnt])
            axs[i, j].set_title(titles[j])
            axs[i, j].axis('off')
            cnt += 1
    fig.savefig("images/%s/%d_%d.png" % (data_loader.dataset_name, epoch, current_batch))
    plt.close()

if __name__ == '__main__':
    # Input shape
    img_shape = (IMG_SIZE, IMG_SIZE, 3)

    # Configure data loader
    data_loader = DataLoader(dataset_name='facades',
                             img_res=(IMG_SIZE, IMG_SIZE))

    # Loss weights
    lambda_cycle = 10.0  # Cycle-consistency loss
    lambda_id = 0.1 * lambda_cycle  # Identity loss

    optimizer = Adam(0.0002, 0.5)

    # Build and compile the discriminators
    d_X = build_discriminator(Input(shape=img_shape))
    d_Y = build_discriminator(Input(shape=img_shape))
    d_X.compile(loss='mse',
                optimizer=optimizer,
                metrics=['accuracy'])
    d_Y.compile(loss='mse',
                optimizer=optimizer,
                metrics=['accuracy'])

    # Build the generators
    img_X = Input(shape=img_shape)
    g_XY = build_generator(img_X)

    img_Y = Input(shape=img_shape)
    g_YX = build_generator(img_Y)

    # Translate images to the other domain
    fake_Y = g_XY(img_X)
    fake_X = g_YX(img_Y)

    # Translate images back to original domain
    reconstr_X = g_YX(fake_Y)
    reconstr_Y = g_XY(fake_X)

    # Identity mapping of images
    img_X_id = g_YX(img_X)
    img_Y_id = g_XY(img_Y)

    # For the combined model we will only train the generators
    d_X.trainable = False
    d_Y.trainable = False

    # Discriminators determines validity of translated images
    valid_X = d_X(fake_X)
    valid_Y = d_Y(fake_Y)

    # Combined model trains both generators to fool the two discriminators
    combined = Model(inputs=[img_X, img_Y],
                     outputs=[valid_X, valid_Y,
                              reconstr_X, reconstr_Y,
                              img_X_id, img_Y_id])
    combined.compile(loss=['mse', 'mse',
                           'mae', 'mae',
                           'mae', 'mae'],
                     loss_weights=[1, 1,
                                   lambda_cycle, lambda_cycle,
                                   lambda_id, lambda_id],
                     optimizer=optimizer)

    train(epochs=75,
          batch_size=1,
          data_loader=data_loader,
          g_XY=g_XY,
          g_YX=g_YX,
          d_X=d_X,
          d_Y=d_Y,
          combined=combined,
          sample_interval=200)


from applications.kinopoisk_api import KP

kinopoisk = KP(token='Your token')

def get_film(user_input):
    global kinopoisk
    name, count = user_input.split('-')
    count = int(count)
    search = kinopoisk.search(name)

    kino = [[None]*4 for _ in range(50)]
    i = 0

    for item in search:
        kino[i][0] = item.ru_name
        kino[i][1] = item.year
        kino[i][2] = item.kp_rate
        kino[i][3] = item.poster_preview
        i += 1

    kino = kino[::-1]

    j = 0
    while kino[j][0] == None:
        del(kino[j])

    # sorting by movie rating
    kino.sort(key=lambda x: x[2], reverse=True)

    if count > len(kino):
        kino.append(len(kino))
        kino.append(
            'https://i.mycdn.me/i?r=AzEPZsRbOZEKgBhR0XGMT1Rktb5tvjJBmafby7jp-PTXfKaKTM5SRkZCeTgDn6uOyic')

     # at the beginning the worst , then better
    kino = kino[::-1]

    return kino[:count]

def get_film_top20():
    global kinopoisk
    top500 = kinopoisk.top500()
    good_film = {item.ru_name: item.kp_rate for item in top500}

    return good_film


from logica import ator

def imprimir_ator(ator):
	print("Codigo do ator: ", ator[0])
	print("Nome: ", ator[1])
	print("Nacionalidade: ", ator[2])
	print("Idade: ", ator[3])
	print()

def menu_cadastrar():
	print("\nCadastrar Ator\n")
	cod_ator = int(input("Codigo do ator: "))
	nome = input("Nome: ")
	nacionalidade = input("Nacionalidade: ")
	idade = int(input("Idade: "))
	ator.cadastrar_ator(cod_ator, nome, nacionalidade, idade)

def menu_listar():
	print("\nListar Atores\n")
	atores = ator.listar_atores()
	for a in atores:
		imprimir_ator(a)

def menu_buscar():
	print("\nBuscar Ator\n")
	cod_ator = int(input("Codigo do ator: "))
	print()
	a = ator.buscar_ator(cod_ator)
	if (a == None):
		print("Ator nao encontrado")
	else:
		imprimir_ator(a)

def menu_remover():
	print("\nRemover Ator\n")
	cod_ator = int(input("Codigo do ator: "))
	print()
	a = ator.remover_ator(cod_ator)
	if (a == False):
		print("Ator nao encontrado")
	else:
		print("Ator removido")

def mostrar_menu():
	menu = ("\n----------------\n"+
            "(1) Cadastrar novo Ator \n" +
            "(2) Listar Atores \n" +
            "(3) Buscar Ator por codigo \n" +
            "(4) Remover Ator \n" +
			"(0) Voltar\n"+
            "----------------")

	while True:
		print(menu)

		option = int(input("Digite sua escolha: "))

		if(option == 1):
			menu_cadastrar()
		elif(option == 2):
			menu_listar()
		elif(option == 3):
			menu_buscar()
		elif(option == 4):
			menu_remover()
		elif(option == 0):
			print("Retornando ao menu principal...")
			break
		else:
			print("Opcao invalida...")


from __future__ import print_function
from contra import BloomFilter, CountFilter, BigCountFilter
import random
import sys
from tempfile import NamedTemporaryFile

def test_filter_basic():
    """
    Test basic essentials: contruct, add, get
    """
    bf = BloomFilter(90, 2)
    bf.insert(101)
    assert bf.query(101) is True
    assert bf.query(100) is False

    cf = CountFilter(120, 2)
    cf.insert(101)
    assert cf.query(101) == 1
    assert cf.query(100) == 0
    for i in range(100):
        cf.insert(101)
    assert cf.query(101) == 101

    bcf = BigCountFilter(200, 2)
    bcf.insert(101)
    assert bcf.query(101) == 1
    assert bcf.query(100) == 0
    for i in range(1024):
        bcf.insert(101)
    assert bcf.query(101) == 1025

def test_filter_size():
    bf = BloomFilter(90, 2)
    # 90 bytes / 2 hash functions * 8 buckets per byte = 360 buckets per table
    # The closest prime numbers to 360 are 359 and 353
    assert len(bf) == 359 + 353
    bf.insert(101)
    assert len(bf) == 359 + 353

    # 2000 bytes / 2 hash functions * 1 bucket per byte = 1000 buckets per table
    cf = CountFilter(2000, 2)
    assert len(cf) == 997 + 991
    cf.insert(101)
    assert len(cf) == 997 + 991

    # 16000 bytes / 4 hash functions / 4 bytes per bucket per byte = 1000 buckets per table
    bcf = BigCountFilter(16000)
    assert len(bcf) == 997 + 991 + 983 + 977
    bcf.insert(101)
    assert len(bcf) == 997 + 991 + 983 + 977

def test_filter_save_load():
    # Populate a count filter with 100 random elements between 1 and 100,000,
    # each element with a random abundance between 1 and 10.
    counts = CountFilter(5e4, 4)
    testcounts = dict()
    for _ in range(100):
        element = random.randint(0, 1e5)
        abund = random.randint(1, 10)
        testcounts[element] = abund
        for _ in range(abund):
            counts.insert(element)

    # Save the filter to disk, load it again, and check to make sure everything
    # matches.
    loadcounts = CountFilter(1, 1)
    with NamedTemporaryFile(suffix='.kct') as diskfile:
        counts.save(diskfile.name)
        loadcounts.load(diskfile.name)
    for e in range(int(1e5)):
        if e in testcounts:
            assert loadcounts.query(e) == testcounts[e]
        else:
            assert loadcounts.query(e) == 0


import sys
from collections import deque

input = lambda: sys.stdin.readline().rstrip()

def print(*args, sep=' ', end='\n'):
    sys.stdout.write(sep.join(map(str, args)))
    sys.stdout.write(end)

def solution():
    n = int(input())
    q = deque([1])
    seen = {1}
    ans = [-1 for i in range(n)]
    while q:
        num = q.popleft()
        for factor in [2, 3, 5]:
            x = num * factor % n
            if x not in seen:
                q.append(x)
                seen.add(x)
                ans[x] = num
    print(*ans)

def main():
    t = 1
    while t:
        solution()
        t -= 1

if __name__ == '__main__':
    main()