# G-01: API-ify the Reservation System
#
# Group Name: 404 Team Not Found
#
# File Name: reserve.py
#
# Date: April 30, 2022

from datetime import datetime, timedelta
from fastapi import HTTPException

class Reservation:
    """
    A class representing a single reservation within the system

    All dates stored are in the form of a mm-dd-yyyy string
    Attributes:
        reservation_id (int): A unique interger for each reservation
        customer_id (str): The unique string id representing a customer
        reservation_type (str): The type of reservation made (for workshop,
            or for a special machine)
        start_date (str): The starting date of the reservation
        end_date (str): The ending date of the reservation
        date_of_reservation (str): The date on which the reservation is made
        total (float): A float representing the total cost of this reservation
        down_payment (float): A float representing the amount required for a down payment
        reservation_string (str): A string representation of the reservation object
    """
    def __init__(self, _reserve):
        self.reservation_id = int(_reserve[0])
        self.customer_id = _reserve[1]
        self.reservation_type = _reserve[2]
        self.start_date = _reserve[3]
        self.end_date = _reserve[4]
        self.start_time = _reserve[5]
        self.end_time = _reserve[6]
        self.date_of_reservation = _reserve[7]
        self.discount = 0
        self.total_cost = float(_reserve[8]) if len(_reserve) > 8 else self.calculate_total_cost()
        self.down_payment = float(_reserve[9]) if len(_reserve) > 9 else self.calculate_down_payment()
        self.reservation_string = ' '.join(_reserve)
        if len(_reserve) <= 8:
            self.reservation_string += f' {self.total_cost} {self.down_payment}'

    def calculate_total_cost(self):
        """
        Calculate the total cost based on the current Reservation object

        Returns:
            A float amount in dollars
        """

        start_date = datetime.strptime(self.start_date, "%m-%d-%Y")
        end_date = datetime.strptime(self.end_date, "%m-%d-%Y")
        date_of_reservation = datetime.strptime(self.date_of_reservation, "%m-%d-%Y")

        # Note that if the reservation start date and end date are different days
        # It is considered to be multiple appointments from start_time to end_time
        # for each of those days, not from start_day start_time to end_day end_time
        days = (end_date - start_date).days + 1
        start_hour, start_minute = map(int, self.start_time.split(':'))
        end_hour, end_minute = map(int, self.end_time.split(':'))

        # Number of half hour blocks for this reservation
        half_hours = (end_hour - start_hour) * 2
        half_hours += -start_minute // 30 + end_minute // 30
        half_hours *= days

        # Base price
        total_cost = 0
        if self.reservation_type == 'workshop':
            total_cost = half_hours * 99 / 2
        elif self.reservation_type == 'microvac':
            total_cost = half_hours * 1000 / 2
        elif self.reservation_type == 'irradiator':
            total_cost = half_hours * 2220 / 2
        elif self.reservation_type == 'extruder':
            total_cost = half_hours * 600 / 2
        elif self.reservation_type == 'hvc':
            total_cost = half_hours * 10000
        elif self.reservation_type == 'harvester':
            total_cost = half_hours * 8800 / 2
        else:
            print(f"Unsupported resource: {self.reservation_type}.")

        # Discount by 75% if reservation is made 14 days in advance
        if (start_date - date_of_reservation).days >= 14:
            total_cost *= 0.75
            self.discount = 25

        return total_cost

    def calculate_down_payment(self):
        """
        Calculate the amount of down payment that needs to be made
        based on the total cost of the reservation

        Returns:
            A float amount in dollars
        """
        if self.reservation_type == 'workshop':
            return 0
        return self.total_cost * 0.5

    def tolist(self):
        """
        Convert all information in the Reservation object to a list object

        Returns:
            A string that includes all information about the Reservation object
        """
        return [str(self.reservation_id), self.customer_id, self.reservation_type,
                    self.start_date, self.end_date, self.start_time, self.end_time,
                    self.date_of_reservation, str(self.total_cost), str(self.down_payment)]

class ReservationManager:
    """
    A class to manage all the reservations within the system

    Attributes:
        reservations (Reservation): A list that tracks all existing
            Reservation objects in the system
    """

    def __init__(self):
        self.reservations = []

    def add_reservation(self, reservation: Reservation):
        """
        Add a new reservation to the list of reservations kept by the Reservation Manager
        """
        # If the reservation is a list, convert it to a Reservation object
        # Otherwise append it straight to the list of reservations
        if type(reservation) == type([]):
            self.reservations.append(Reservation(reservation))
        else:
            self.reservations.append(reservation)

    def new_id(self):
        """
        Generate a new id based on the number of registrations already in the system

        Returns:
            A unique integer ID for a new reservation
        """
        if len(self.reservations) == 0:
            return 1
        return self.reservations[-1].reservation_id + 1

    def save_to_file(self, file):
        """
        Write all current reservations to the designated file descriptor
        """
        for reservation in self.reservations:
            file.write(reservation.reservation_string)
            file.write('\n')

    def generate_reservations_report(self, start_date, end_date, customer_id):
        """
        Generate a JSON report of all reservations in the system based
        Formatted according to the API design dcoument

        Args:
            start_date (str): The starting date of reservations to report on
            end_date (str): The ending date of reservations to report on
            customer_id (str): OPTIONAL, the customer ID to generate report on

        Returns:
            A JSON formatted report in accordance with API design document for
            the 'GET reservations' API endpoint
        """
        list_reservation_data = []
        for reservation in self.reservations:
            # If customer id matches or not specified
            if (customer_id == "" or reservation.customer_id == customer_id):
                # Print all reservations between this date
                if between(reservation.start_date, start_date, end_date):
                    list_reservation_data.append({
                        "reservation_id":reservation.reservation_id,
                        "customer_id": reservation.customer_id,
                        "resource": reservation.reservation_type,
                        "start_date": reservation.start_date,
                        "end_date": reservation.end_date,
                        "start_time": reservation.start_time,
                        "end_time": reservation.end_time,
                        "total_cost": reservation.total_cost,
                        "down_payment": reservation.down_payment
                    })
        return {"reservations": list_reservation_data}

class Transaction:
    """
    A class representing transactions that have taken place in the system

    Attributes:
        transaction_id (int): A unique interger for each transaction
        type (str): The type of transaction (CANCELLATION or RESERVATION)
        transaction_date (str): Date of the transaction in mm-dd-yyyy format
        detail (Reservation): The Reservation object related to this transaction
        reservation_string (str): A string representation of the reservation
            related to this transaction
    """
    def __init__(self, transaction):
        self.transaction_id = int(transaction[0])
        self.type = transaction[1]
        self.transaction_date = transaction[2]
        self.detail = Reservation(transaction[3:])
        self.reservation_string = f'{self.transaction_id} {self.type} {self.transaction_date} {self.detail.reservation_string}'

class Transaction_Manager:
    """
    A class to manage all the transaction within the system

    Attributes:
        transactions ([Transaction]): A list that tracks all existing
            Transaction objects in the system
    """
    def __init__(self):
        self.transactions = []

    def add_transaction(self, transaction):
        """
        Add a new transaction to the list kept by the Transaction Manager
        """
        self.transactions.append(Transaction(transaction))

    def new_id(self):
        """
        Generate a new id based on the number of registrations already in the system

        Returns:
            A unique integer ID for a new reservation
        """
        return len(self.transactions) + 1

    def save_to_file(self, file):
        """
        Write all current transactions to the designated file descriptor
        """
        for transaction in self.transactions:
            file.write(transaction.reservation_string)
            file.write('\n')

    def create_refund(self, cancelled_reservation, cancel_date):
        """
        Given a cancelled reservation and the date on which the cancellation ismade
        Calculate the amount that should be refunded and make the refund by
        recording it as a cancellation transaction

        Args:
            cancelled_reservation (Reservation): The reservation to refund
            cancel_date (str): the date on which the cancellation is requested
        """
        # print refund
        start_date = datetime.strptime(cancelled_reservation.start_date, "%m-%d-%Y")
        cancel_datetime = datetime.strptime(cancel_date, "%m-%d-%Y")
        refund = 0
        days_before_reservation = (start_date - cancel_datetime).days

        percent_returned = 0
        # calculate the amount of refund based on how many days ahead
        if days_before_reservation >= 7:
            percent_returned = 75
            refund = 0.75 * cancelled_reservation.down_payment
        elif days_before_reservation >= 2:
            percent_returned = 50
            refund = 0.5 * cancelled_reservation.down_payment
        print(f"Cancellation succeeded! Refund: ${refund}")

        # add a cancellation transaction
        transaction_info = [self.new_id(), f'CANCELLATION${refund}', cancel_date] + cancelled_reservation.tolist()
        self.add_transaction(transaction_info)
        return percent_returned, refund

    def generate_transactions_report(self, start_date, end_date):
        """
        Generate a JSON report of all transactions in the system based
        Formatted according to the API design given in web.py

        Args:
            start_date (str): The starting date of transaction to report on
            end_date (str): The ending date of transactions to report on

        Returns:
            A JSON formatted report in accordance with API design document for
            the 'GET transactions' API endpoint
        """
        list_transaction_data = []
        for transaction in self.transactions:
            if between(transaction.transaction_date, start_date, end_date):
                reservation = transaction.detail
                transaction_type = transaction.type.split("$")
                transaction_amount = reservation.down_payment
                if len(transaction_type) == 2:
                    transaction_amount = transaction_type[1]
                transaction_type = transaction_type[0]

                list_transaction_data.append({
                    "transaction_id": transaction.transaction_id,
                    "transaction_type": transaction_type,
                    "transaction_date": transaction.transaction_date,
                    "reservation_id": reservation.reservation_id,
                    "customer_id": reservation.customer_id,
                    "resource": reservation.reservation_type,
                    "total_cost": reservation.total_cost,
                    "transaction_amount": transaction_amount
                })
        return {"transactions": list_transaction_data}

def workshop_is_closed(start_time, end_time, date):
    """
    Given the date, start and end time of a reservation, determine if the
    workshop is going to be open for the duration of the reservation

    Returns:
        (bool) True if workshop is open, False otherwise
    """
    if date.weekday() == 6:
        return True
    if date.weekday() == 5 and (start_time < 100 or end_time > 160):
        return True
    return start_time < 90 or end_time > 180

def split_time(start, end):
    """
    Split the start and end time into an integer representation
    E.g. 150 represents 15:00, 105 represents 10:30

    Args:
        Arguments are of format HH:MM
        start (str): The start time
        end (str): The end time

    Returns:
        Integer representation of the start time and end time
    """
    start_hour, start_minute = map(int, start.split(':'))
    end_hour, end_minute = map(int, end.split(':'))
    start_time = start_hour * 10 + start_minute // 30 * 5
    end_time = end_hour * 10 + end_minute // 30 * 5
    return start_time, end_time

def is_available(reservation_type, count):
    """
    Given the type of reservation and the number of bookings already made for
    that type of workshop/equipment, determine if it is still available for booking

    Args:
        reservation_type (str): The type of resource to reserve
        count (int): The number of reservations already made for that resource

    Returns:
        (bool) True if the workshop/equipment is still available, False otherwise
    """
    if reservation_type == 'workshop':
        return count <= 15
    elif reservation_type == 'microvac':
        return count <= 2
    elif reservation_type == 'irradiator':
        return count <= 2
    elif reservation_type == 'extruder':
        return count <= 3
    elif reservation_type == 'hvc':
        return count <= 1
    elif reservation_type == 'harvester':
        return count <= 1
    print(f"Unsupported resource: {reservation_type}.")
    return False

def between(date, start, end):
    """
    Given three date strings, determine if the first date is between the
    start (second) and the end (third) date

    Args:
        All arguments are strings of the format MM-DD-YYYY
        date (str): The date to check for
        start (str): The start date
        end (str): The end date

    Returns:
        True if it is between the start and end date, False otherwise
    """
    start_date = datetime.strptime(start, "%m-%d-%Y")
    end_date = datetime.strptime(end, "%m-%d-%Y")
    ddate = datetime.strptime(date, "%m-%d-%Y")
    return (ddate - start_date).days >= 0 and (end_date - ddate).days >=0

def reservation_is_not_in_date_range(reservation_datetime, start_datetime, end_datetime):
    """
    Given all dates of a reservation to be made, check if it is within the
    allowed date range (0 to 30 days in advance)

    Args:
        Contraint: All inputs are datetime objects
        reservation_datetime (datetime): The date on which the reservation is made
        start_datetime (datetime): The first day of the reservation
        end_datetime (datetime): The last day day of the reservation

    Returns:
        (bool) False if it is between the allowed date range, True otherwise
    """
    # Check if reservation is in the future
    if (reservation_datetime - start_datetime).days > 0:
        print('Reservation Failed: cannot reserve time already passed.')
        handle_error(400, "Reservation", 'Cannot reserve time already passed.')

    # Check if reservation is within 30 days
    if (end_datetime - reservation_datetime).days > 30:
        # "Clients expect to be able to make reservations up to 30 days in advance"
        print('Reservation Failed: cannot reserve time more than 30 days away.')
        handle_error(400, "Reservation", 'Cannot reserve time more than 30 days away.')

    return False

def reservation_type_is_not_known(reservation_type):
    """
    Check if the reservation type is valid

    Args:
        reservation_type (str):The workshop/resouce type to reserve

    Returns:
        (bool) True if it is owned by the workshop, False otherwise
    """
    if reservation_type not in ['workshop', 'microvac', 'irradiator', 'extruder', 'hvc', 'harvester']:
        print(f"Unsupported resource: {reservation_type}.")
        handle_error(400, "Reservation", f"Unsupported resource: {reservation_type}")
    return False

def reservation_is_not_on_half_hour(minute):
    """
    Check if a reservation is made on the half hour

    Args:
        reservation_type (int): The minute mark of a reservation

    Returns:
        True if it is on the half hour, False otherwise
    """
    if minute != 0 and minute != 30:
        print('Reservation Failed: reservations for all resources are made in 30 minute blocks and always start on the hour or half hour.')
        handle_error(400, "Reservation", f"Reservations for all resources are made in 30 minute blocks and always start on the hour or half hour")
    return False

def check_only_one_special_machine(reservation_manager, days_to_reserve, reservation):
    """
    Check that there is only one special machine reserved by a single client
    at any given time

    Args:
        reservation_manager (ResevationManager): the reservation manager of the system
        days_to_reserve (int): All the days this reservation is trying to make
        reservation (Reservation): Reservation Object for this reservation

    Returns:
        (bool) True if only no special machine has been reserved, False otherwise
    """
    customer_id = reservation.customer_id
    reservation_type = reservation.reservation_type
    start_time, end_time = split_time(reservation.start_time, reservation.end_time)

    for day in days_to_reserve:
        for reservation in reservation_manager.reservations:
            if reservation.customer_id != customer_id:
                continue
            if reservation_type == 'workshop':
                continue
            if not between(f'{day.month}-{day.day}-{day.year}', reservation.start_date, reservation.end_date):
                continue
            reservation_start, reservation_end = split_time(reservation.start_time, reservation.end_time)
            if not (reservation_end <= start_time or end_time <= reservation_start):
                # "They can only reserve one special machine at a time"
                print('Reservation Failed: a client can only reserve one special machine at a time')
                handle_error(400, "Reservation", "A client can only reserve one special machine at a time")
    return True

def over_three_reservations(reservation_manager, days_to_reserve, customer_id):
    """
    Check if a customer is going to go over the limit of three reservations for
    a single week, given that they are trying to make reservations given by
    days_to_reserve

    Args:
        reservation_manager (ResevationManager): the reservation manager of the system
        days_to_reserve (int): All the days this reservation is trying to make
        customer_id (str): The customer that is trying to make the reservation

    Returns:
        (bool) False if the customer is not going to go over the three days
        restriction, True if they are going to go over the restriction
    """
    weekr = {}
    # Count up the number of reservations that this customer has already made
    for reservation in reservation_manager.reservations:
        if reservation.customer_id != customer_id:
            continue
        reservation_start_datetime = datetime.strptime(reservation.start_date, "%m-%d-%Y")
        reservation_end_datetime = datetime.strptime(reservation.end_date, "%m-%d-%Y")
        rdays = []
        rcur = reservation_start_datetime
        while (reservation_end_datetime - rcur).days >= 0:
            rdays.append(rcur)
            rcur += timedelta(days=1)
        for day in rdays:
            key = f'{day.year}-{day.isocalendar()[1]}'
            if key not in weekr:
                weekr[key] = 1
            else:
                weekr[key] += 1
    # Add the days that are going to be reserved now
    for day in days_to_reserve:
        key = f'{day.year}-{day.isocalendar()[1]}'
        if key not in weekr:
            weekr[key] = 1
        else:
            weekr[key] += 1
    # Check if it is going to go over three
    for k in weekr:
        if weekr[k] > 3:
            print(f'Reservation Failed: A client can only make reservations for 3 different days in a given week.')
            handle_error(400, "Reservation", "A client can only make reservations for 3 different days in a given week")

    return False

def check_non_cooldown_requirements(reservation_manager, day, reservation_type, start_time, end_time):
    """
    Given the day of reservation, type of reservation and the start and end time, check all
    the non-cooldown related rules that must be applied to the reservation, if the rules
    correctly applies without error, return True, return False if there exists some error
    and the reservation cannot be made

    Args:
        reservation_manager (ReservationManager): The reservation manager of the system
        day (datetime): the datetime object of the day that is being checked
        reservation_type (str): the machine/workshop to make reservation for
        start_time (str): the start time of this reservation
        end_time (str): the finish time of this reservation

    Returns:
        (bool) False if the reservation violates some requirement, True otherwise
    """
    for t in range(start_time, end_time, 5):
        count = 0
        s_cnt = 0
        h_run = False
        for reservation in reservation_manager.reservations:
            reservation_start, reservation_end = split_time(reservation.start_time, reservation.end_time)
            if not (t >= reservation_start and t < reservation_end):
                continue
            if not between(f'{day.month}-{day.day}-{day.year}', reservation.start_date, reservation.end_date):
                continue
            if reservation.reservation_type == 'harvester':
                h_run = True
            if reservation.reservation_type != 'workshop':
                # increament special_count by 1
                s_cnt += 1
            if reservation.reservation_type == reservation_type:
                # increament count by 1
                count += 1
        if not is_available(reservation_type, count+1):
            print(f'Reservation Failed: not enough available {reservation_type}, {count} already reserved.')
            handle_error(400, "Reservation", f'Not enough available {reservation_type}, {count} already reserved')

        if reservation_type == 'irradiator' and count == 1:
            print('Reservation Failed: only 1 irradiator can be used at a time.')
            handle_error(400, "Reservation", 'Only 1 irradiator can be used at a time')

        if reservation_type != 'workshop':
            s_cnt += 1
        if h_run and s_cnt > 4:
            print('Reservation Failed: only 3 other machines can run while the 1.21 gigawatt lightning harvester is operating.')
            handle_error(400, "Reservation", 'Only 3 other machines can run while the 1.21 gigawatt lightning harvester is operating')

    return True

def check_hvc_requirements(reservation_manager, day, start_time, end_time):
    """
    Given a start time and an end time, check that on a given day, the hvc machine
    is used in accordance with the cooldown rules

    Args:
        reservation_manager (ReservationManager): The reservation manager of the system
        day (datetime): the datetime object of the day that is being checked
        start_time (str): the start time of this reservation
        end_time (str): the finish time of this reservation

    Returns:
        (bool) True if the hvc is being operated within requirements, False otherwise
    """
    hvc_start = start_time - 60
    hvc_end = end_time + 60
    for reservation in reservation_manager.reservations:
        if reservation.reservation_type == 'hvc':
            if not between(f'{day.month}-{day.day}-{day.year}', reservation.start_date, reservation.end_date):
                continue
            reservation_start, reservation_end = split_time(reservation.start_time, reservation.end_time)
            if not (hvc_end <= reservation_start or reservation_end <= hvc_start):
                print(f'Reservation Failed: high velocity crusher needs to cool down for 6 hours between uses, hvc currently reserved for {reservation.start_time}-{reservation.end_time}.')
                handle_error(400, "Reservation", f'High velocity crusher needs to cool down for 6 hours between uses, hvc currently reserved for {reservation.start_time}-{reservation.end_time}.')

    return True

def check_irradiator_requirements(reservation_manager, day, start_time, end_time):
    """
    Given a start time and an end time, check that on a given day, the irradiator
    is used in accordance with the cooldown rules

    Args:
        reservation_manager (ReservationManager): The reservation manager of the system
        day (datetime): the datetime object of the day that is being checked
        start_time (str): the start time of this reservation
        end_time (str): the finish time of this reservation

    Returns:
        (bool) True if the irradiator is being operated within requirements, False otherwise
    """
    irradiator_start = start_time - 10
    irradiator_end = end_time + 10
    count = 0
    for reservation in reservation_manager.reservations:
        if reservation.reservation_type == 'irradiator':
            if not between(f'{day.month}-{day.day}-{day.year}', reservation.start_date, reservation.end_date):
                continue
            reservation_start, reservation_end = split_time(reservation.start_time, reservation.end_time)
            if not (irradiator_end <= reservation_start or reservation_end <= irradiator_start):
                count += 1
    if count == 2:
        print(f'Reservation Failed: irradiators need to cool down for 1 hour between uses.')
        handle_error(400, "Reservation","Irradiators need to cool down for 1 hour between uses")

    return True

def handle_reservation(reservation_manager, reservation):
    """
    Given a reservation, check all conditions to see if it is a valid reservation
    that does not break any of the reservation rules

    Args:
        reservation_manager (ReservationManager): The reservation manager of the system
        reservation (Reservation): The reservation to be made

    Returns:
        (bool) True if it is the reservation can be made in accordance to all
        the rules, False otherwise
    """
    # Unpack all required data from the Reservation object
    customer_id = reservation.customer_id
    reservation_type = reservation.reservation_type
    start_datetime = datetime.strptime(reservation.start_date, "%m-%d-%Y")
    end_datetime = datetime.strptime(reservation.end_date, "%m-%d-%Y")
    start_time = reservation.start_time
    end_time = reservation.end_time
    reservation_datetime = datetime.strptime(reservation.date_of_reservation, "%m-%d-%Y")

    # Check if the type of machine is known
    if reservation_type_is_not_known(reservation_type):
        return False

    if reservation_is_not_in_date_range(reservation_datetime, start_datetime, end_datetime):
        return False

    # Convert hour and minue to form 105 for 10:30, 160 for 16:00
    original_start_time = start_time
    original_end_time = end_time
    start_hour, start_minute = map(int, start_time.split(':'))
    end_hour, end_minute = map(int, end_time.split(':'))
    start_time = start_hour * 10 + start_minute // 30 * 5
    end_time = end_hour * 10 + end_minute // 30 * 5

    if reservation_is_not_on_half_hour(start_minute) or reservation_is_not_on_half_hour(end_minute):
        return False

    # A list of days to make reservations for, based on start date and end date
    days_to_reserve = []
    cur = start_datetime
    while (end_datetime - cur).days >= 0:
        days_to_reserve.append(cur)
        cur += timedelta(days=1)

    # Check if the workshop is open for each of the reservation days
    for day in days_to_reserve:
        if workshop_is_closed(start_time, end_time, day):
            print(f'Reservation Failed: cannot reserve time interval from {original_start_time} to {original_end_time} on {str(day).split()[0]}')
            handle_error(400, "Reservation", f'Cannot reserve time interval from {original_start_time} to {original_end_time} on {str(day).split()[0]}')

    # Make sure that one client only makes one special machine reservation at any time
    if not check_only_one_special_machine(reservation_manager, days_to_reserve, reservation):
        return False

    # For each day in the attempted reservation, check that it does not violate some
    # requirement for booking to be successful
    for day in days_to_reserve:
        # Check that all non-cooldown rules for a reservation
        if not check_non_cooldown_requirements(reservation_manager, day, reservation_type, start_time, end_time):
            return False

        # check that the high velocity crusher has 6 hours cooldown between uses
        if reservation_type == 'hvc':
            if not check_hvc_requirements(reservation_manager, day, start_time, end_time):
                return False

        # check that irradiators have a 60 minutes cooldown period after use
        if reservation_type == 'irradiator':
            if not check_irradiator_requirements(reservation_manager, day, start_time, end_time):
                return False

    # Check if A customer is going to go over 3 reservations in a given week
    if over_three_reservations(reservation_manager, days_to_reserve, customer_id):
        return False

    return True

def load_data_from_file(reservation_manager, transactions_manager):
    """
    Given empty reservation manager and transaction manager, load from data.txt
    file all of the reservations and transactions saved in the system

    Args:
        reservation_manager (ReservationManager): the reservation manager of
            the system to load reservations into
        transactions_manager (TransactionManager): the transactions manager of
            the system to load transactions into
    """
    file = open("data/data.txt", 'r')
    lines = file.readlines()
    # Read every line from data file, with a hash # seperating the reservations
    # from the transactions
    convert_to_reservation = True
    for line in lines:
        line = line.split()
        if line[0] == '#':
            convert_to_reservation = False
            continue
        if convert_to_reservation:
            reservation_manager.add_reservation(line)
        else:
            transactions_manager.add_transaction(line)
    file.close()

def save_date_to_file(reservation_manager, transactions_manager):
    """
    Given the current reservation manager and transaction manager, save all
    data in the system to data.txt

    Args:
        reservation_manager (ReservationManager): the reservation manager of
            the system to load reservations into
        transactions_manager (TransactionManager): the transactions manager of
            the system to load transactions into
    """
    # save_to_file reservation and transaction data, seperated by a hash #
    file = open('data/data.txt', 'w')
    reservation_manager.save_to_file(file)
    file.write('#\n')
    transactions_manager.save_to_file(file)
    file.close()

def handle_request(request):
    """
    Main function of this reservation program, the format of commands are as follows:
    reserve.py reserve <customer_id> <resource> <start_date> <end_date> <start_time> <end_time> <reserve_date>
    reserve.py cancel <reservation_id> <cancel_date>
    reserve.py reservations <start_date> <end_date>
    reserve.py financial <start_date> <end_date>
    reserve.py reservations <start_date> <end_date> <customer_id>

    Any date is of the form mm-dd-yyyy
    Any time is of the form hh:mm in 24 hour format

    Handle the above requests by loading the data from data.txt, performing request
    and then saving updated data back into data.txt

    Args:
        request (list): A list of comand and arugments

    Returns:
        response (JSON): A JSON formatted API response
    """

    # Initialize the Reservation and transaction managers by loading all
    # existing reservations and transactions from the data file
    reservation_manager = ReservationManager()
    transactions_manager = Transaction_Manager()

    load_data_from_file(reservation_manager, transactions_manager)

    response = None

    # handle request
    command = request[0]
    if command == 'reserve':
        # Get all the required arguments from the command line
        date_of_reservation = request[7]
        reservation_info = [str(reservation_manager.new_id())] + request[1:]
        new_reservation = Reservation(reservation_info)
        # check if the reservation is possible
        if handle_reservation(reservation_manager, new_reservation):
            # make the reservation
            reservation_manager.add_reservation(new_reservation)
            # add a transaction for this reservation
            transaction_info = [transactions_manager.new_id(), 'RESERVATION', date_of_reservation] + reservation_info
            transactions_manager.add_transaction(transaction_info)
            # print reservation successful message (including total cost and down payment)
            print(f"Reservation succeeded! Reservation id: {new_reservation.reservation_id}, Total cost: ${new_reservation.total_cost}, down payment: ${new_reservation.down_payment}.")
            response = reservation_detail(new_reservation)
        else:
            return

    elif command == 'cancel':
        reservation_id = int(request[1])
        cancel_date = request[2]
        found = False
        # cancel the reservation by removing it from the reservation manager
        for i in range(len(reservation_manager.reservations)):
            if reservation_manager.reservations[i].reservation_id == reservation_id:
                found = True
                break

        if not found:
            handle_error(400, "Cancellation", f"Invalid reservation id: {reservation_id}")

        cancelled_reservation = reservation_manager.reservations[i]
        del reservation_manager.reservations[i]

        # Ask the transaction manager to manage refund and record refund
        percent_returned, refund = transactions_manager.create_refund(cancelled_reservation, cancel_date)

        response = cancellation_detail(percent_returned, refund)

    elif command == 'reservations':
        customer_id = ""
        # If a specific customer is indicated
        if len(request) == 4:
            customer_id = request[3]
        response = reservation_manager.generate_reservations_report(request[1], request[2], customer_id)

    elif command == 'financial':
        # list transactions between the two dates
        response = transactions_manager.generate_transactions_report(request[1], request[2])

    else:
        print(f"Unsupported command: {command}")
        handle_error(400, "Cancellation", f"Invalid request: {command}")

    save_date_to_file(reservation_manager, transactions_manager)
    return response

def handle_error(code, operation_name, detail):
    """
    Raise a HTTPException

    Args:
        code (int): status code
        operation_name (str): name of the operation that triggered this error
        detail (str): error message indicating reason of the error
    """
    raise HTTPException(status_code=code, detail=f"{operation_name} failed: {detail}")

def reservation_detail(new_reservation: Reservation):
    """
    Construct detail of a reservation

    Args:
        new_reservation (Reservation): a Reservation object

    Returns:
        A dict object containing detail information of the reservation
    """
    return {
        'reservation_id': str(new_reservation.reservation_id),
        'discount': str(new_reservation.discount),
        'total_cost': str(new_reservation.total_cost),
        'down_payment': str(new_reservation.down_payment)
    }

def cancellation_detail(percent_returned, refund):
    """
    Construct detail of a cancellation

    Args:
        percent_returned (int): percentage of the refund to total cost
        refund (float): money refunded to the customer

    Returns:
        A dict object containing detail information of the cancellation
    """
    return {
        'percent_returned': str(percent_returned),
        'refund': str(refund)
    }


__metaclass__ = type

__all__ = [
    'BinaryPublishingRecordView',
    'SourcePublicationURL',
    'SourcePublishingRecordSelectableView',
    'SourcePublishingRecordView',
    ]

from operator import attrgetter

from lazr.delegates import delegate_to
from zope.component import getUtility
from zope.interface import implementer

from lp.archiveuploader.utils import re_isadeb
from lp.services.librarian.browser import (
    FileNavigationMixin,
    ProxiedLibraryFileAlias,
    )
from lp.services.propertycache import cachedproperty
from lp.services.webapp import Navigation
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.escaping import structured
from lp.services.webapp.interfaces import ICanonicalUrlData
from lp.services.webapp.publisher import (
    canonical_url,
    LaunchpadView,
    )
from lp.soyuz.adapters.proxiedsourcefiles import ProxiedSourceLibraryFileAlias
from lp.soyuz.enums import PackagePublishingStatus
from lp.soyuz.interfaces.binarypackagebuild import BuildSetStatus
from lp.soyuz.interfaces.packagediff import IPackageDiff
from lp.soyuz.interfaces.publishing import (
    IBinaryPackagePublishingHistory,
    IPublishingSet,
    ISourcePackagePublishingHistory,
    )

@implementer(ICanonicalUrlData)
class PublicationURLBase:
    """Dynamic URL declaration for `I*PackagePublishingHistory`"""
    rootsite = None

    def __init__(self, context):
        self.context = context

    @property
    def inside(self):
        return self.context.archive

class SourcePublicationURL(PublicationURLBase):
    """Dynamic URL declaration for `ISourcePackagePublishingHistory`"""
    @property
    def path(self):
        return u"+sourcepub/%s" % self.context.id

class BinaryPublicationURL(PublicationURLBase):
    """Dynamic URL declaration for `IBinaryPackagePublishingHistory`"""
    @property
    def path(self):
        return u"+binarypub/%s" % self.context.id

class SourcePackagePublishingHistoryNavigation(Navigation,
                                               FileNavigationMixin):
    usedfor = ISourcePackagePublishingHistory

@delegate_to(IPackageDiff)
class ProxiedPackageDiff:
    """A `PackageDiff` extension.

    Instead of `LibraryFileAlias` returns `ProxiedLibraryFileAlias`, so
    their 'http_url' attribute can be used in the template.
    """

    def __init__(self, context, parent):
        self.context = context
        self.parent = parent

    @property
    def diff_content(self):
        library_file = self.context.diff_content
        if library_file is None:
            return None
        return ProxiedLibraryFileAlias(library_file, self.parent)

class BasePublishingRecordView(LaunchpadView):
    """Base Publishing view class."""

    @property
    def is_source(self):
        return ISourcePackagePublishingHistory.providedBy(self.context)

    @property
    def is_binary(self):
        return IBinaryPackagePublishingHistory.providedBy(self.context)

    # The reason we define the map below outside the only function that uses
    # it (date_last_changed()) is that this allows us to test whether the map
    # covers all PackagePublishingStatus enumeration values.
    # The pertinent tests in doc/publishing-pages.txt will fail if we add a
    # new value to the PackagePublishingStatus enumeration but do not update
    # this map.
    timestamp_map = {
        PackagePublishingStatus.DELETED: 'dateremoved',
        PackagePublishingStatus.OBSOLETE: 'scheduleddeletiondate',
        PackagePublishingStatus.PENDING: 'datecreated',
        PackagePublishingStatus.PUBLISHED: 'datepublished',
        PackagePublishingStatus.SUPERSEDED: 'datesuperseded'
    }

    @property
    def date_last_changed(self):
        """Return the date of last change considering the publishing status.

        The date returned is as follows:
            * pending        -> datecreated
            * published      -> datepublished
            * superseded     -> datesuperseded
            * deleted        -> dateremoved
            * obsolete       -> scheduleddeletiondate
        """
        accessor = attrgetter(self.timestamp_map[self.context.status])
        return accessor(self.context)

    def wasDeleted(self):
        """Whether or not a publishing record deletion was requested.

        A publishing record deletion represents the explicit request from a
        archive-administrator (self.remove_by) to purge the published contents
        of this record from the archive for an arbitrary reason
        (self.removal_comment).
        """
        return self.context.status == PackagePublishingStatus.DELETED

    def wasSuperseded(self):
        """Whether or not a publishing record was superseded.

        'Superseded' means that a new and higher version of this package was
        uploaded/built after it was published or the publishing attributes
        (section, component, priority/urgency) was modified.
        """
        return self.context.supersededby is not None

    def isPendingRemoval(self):
        """Whether or not a publishing record is marked for removal.

        This package will be removed from the archive respecting the Soyuz
        'death row' quarantine period and the absence of file references in
        the target archive.
        """
        return self.context.scheduleddeletiondate is not None

    def isRemoved(self):
        """Whether or not a publishing records was removed from the archive.

        A publishing record (all files related to it) is removed from the
        archive disk once it pass through its quarantine period and it's not
        referred by any other archive publishing record.
        Archive removal represents the act of having its content purged from
        archive disk, such situation can be triggered for different
        status, each one representing a distinct step in the Soyuz
        publishing workflow:

         * SUPERSEDED -> the publication is not necessary since there is
           already a newer/higher/modified version available

         * DELETED -> the publishing was explicitly marked for removal by a
           archive-administrator, it's not wanted in the archive.

         * OBSOLETE -> the publication has become obsolete because its
           targeted distroseries has become obsolete (not supported by its
           developers).
        """
        return self.context.dateremoved is not None

    @property
    def removal_comment(self):
        """Return the removal comment or 'None provided'."""
        removal_comment = self.context.removal_comment
        if removal_comment is None or not removal_comment.strip():
            removal_comment = u'None provided.'

        return removal_comment

    @property
    def phased_update_percentage(self):
        """Return the formatted phased update percentage, or empty."""
        if (self.is_binary and
            self.context.phased_update_percentage is not None):
            return u"%d%% of users" % self.context.phased_update_percentage
        return u""

class SourcePublishingRecordView(BasePublishingRecordView):
    """View class for `ISourcePackagePublishingHistory`."""

    @cachedproperty
    def build_status_summary(self):
        """Returns a dict with a summary of the build status."""
        return self.context.getStatusSummaryForBuilds()

    @property
    def builds_successful_and_published(self):
        """Return whether all builds were successful and published."""
        status = self.build_status_summary['status']
        return status == BuildSetStatus.FULLYBUILT

    @property
    def builds_successful_and_pending(self):
        """Return whether builds were successful but not all published."""
        status = self.build_status_summary['status']
        return status == BuildSetStatus.FULLYBUILT_PENDING

    @property
    def pending_builds(self):
        """Return a list of successful builds pending publication."""
        if self.builds_successful_and_pending:
            return self.build_status_summary['builds']
        else:
            return []

    @property
    def build_status_img_src(self):
        """Return the image path for the current build status summary."""
        image_map = {
            BuildSetStatus.BUILDING: '/@@/processing',
            BuildSetStatus.NEEDSBUILD: '/@@/build-needed',
            BuildSetStatus.FAILEDTOBUILD: '/@@/no',
            BuildSetStatus.FULLYBUILT_PENDING: '/@@/build-success-publishing'
            }

        return image_map.get(self.build_status_summary['status'], '/@@/yes')

    def wasCopied(self):
        """Whether or not a source is published in its original location.

        A source is not in its original location when:

         * The publishing `Archive` is not the same than where the source
            was uploaded. (SSPPH -> SPR -> Archive != SSPPH -> Archive).
        Or

          * The publishing `DistroSeries` is not the same than where the
            source was uploaded (SSPPH -> SPR -> DS != SSPPH -> DS).
        """
        source = self.context.sourcepackagerelease

        if self.context.archive != source.upload_archive:
            return True

        if self.context.distroseries != source.upload_distroseries:
            return True

        return False

    @property
    def allow_selection(self):
        """Do not render the checkbox corresponding to this record."""
        return False

    @property
    def published_source_and_binary_files(self):
        """Return list of dictionaries representing published files."""
        files = sorted(
            self.context.getSourceAndBinaryLibraryFiles(),
            key=attrgetter('filename'))
        result = []
        urls = set()
        for library_file in files:
            url = library_file.http_url
            if url in urls:
                # Don't print out the same file multiple times. This
                # actually happens for arch-all builds, and is
                # particularly irritating for PPAs.
                continue
            urls.add(url)

            custom_dict = {}
            custom_dict["filename"] = library_file.filename
            custom_dict["filesize"] = library_file.content.filesize
            if re_isadeb.match(library_file.filename):
                custom_dict['class'] = 'binary'
                custom_dict["url"] = ProxiedLibraryFileAlias(
                    library_file, self.context.archive).http_url
            else:
                custom_dict['class'] = 'source'
                custom_dict["url"] = ProxiedSourceLibraryFileAlias(
                    library_file, self.context).http_url

            result.append(custom_dict)

        return result

    @property
    def available_diffs(self):
        package_diffs = self.context.sourcepackagerelease.package_diffs
        return [
            ProxiedPackageDiff(package_diff, self.context.archive)
            for package_diff in package_diffs]

    @property
    def built_packages(self):
        """Return a list of dictionaries with package names and their summary.

        For each built package from this published source, return a
        dictionary with keys "binarypackagename" and "summary", where
        the binarypackagename is unique (i.e. it ignores the same package
        published in more than one place/architecture.)
        """
        publishing_set = getUtility(IPublishingSet)
        return publishing_set.getBuiltPackagesSummaryForSourcePublication(
            self.context)

    @cachedproperty
    def builds(self):
        """Return a list of Builds for the context published source."""
        return list(self.context.getBuilds())

    @property
    def linkify_source_archive(self):
        """Return True if the source's upload_archive should be linkified.

        The source archive is the upload_archive for any source that was
        copied.  It should be linkified only if it's a PPA and the user
        has permission to view that PPA.
        """
        archive = self.context.sourcepackagerelease.upload_archive

        if not archive.is_ppa:
            return False

        return check_permission('launchpad.View', archive)

    @property
    def recipe_build_details(self):
        """Return a linkified string containing details about a
        SourcePackageRecipeBuild.
        """
        sprb = self.context.sourcepackagerelease.source_package_recipe_build
        if sprb is not None:
            if sprb.recipe is None:
                recipe = 'deleted recipe'
            else:
                recipe = structured(
                    'recipe %s',
                    canonical_url(sprb.recipe), sprb.recipe.name)
            return structured(
                'Built by %s for %s',
                    canonical_url(sprb), recipe,
                    canonical_url(sprb.requester),
                    sprb.requester.displayname).escapedtext
        return None

class SourcePublishingRecordSelectableView(SourcePublishingRecordView):
    """View class for a selectable `ISourcePackagePublishingHistory`."""

    @property
    def allow_selection(self):
        """Allow the checkbox corresponding to this record to be rendered."""
        return True

class BinaryPublishingRecordView(BasePublishingRecordView):
    """View class for `IBinaryPackagePublishingHistory`."""

    def wasCopied(self):
        """Whether or not a binary is published in its original location.

        A binary is not in its original location when:

         * The publishing `Archive` is not the same than where the binary
           was built. (SBPPH -> BPR -> Build -> Archive != SBPPH -> Archive).
        Or

          * The publishing `DistroArchSeries` is not the same than where
            the binary was built (SBPPH -> BPR -> B -> DAS != SBPPH -> DAS).

        Or

          * The publishing pocket is not the same than where the binary was
            built (SBPPH -> BPR -> B -> Pocket != SBPPH -> Pocket).

        """
        build = self.context.binarypackagerelease.build

        if self.context.archive != build.archive:
            return True

        if self.context.distroarchseries != build.distro_arch_series:
            return True

        if self.context.pocket != build.pocket:
            return True

        return False


#%% lib
import sqlite3
import pandas as pd
#%% create

# #Connecting to sqlite
# conn = sqlite3.connect('testbooking.db')

# #Creating a cursor object using the cursor() method
# cursor = conn.cursor()

# #Doping EMPLOYEE table if already exists.
# cursor.execute("DROP TABLE IF EXISTS TESTBOOKING")

# #Creating table as per requirement
# sql ='''CREATE TABLE TESTBOOKING(
#    PNR NVARCHAR(10) NOT NULL,
#    NAME CHAR(30),
#    FLIGHT_DATE DATETIME,
#    PID INT(9),
#    PAYMENT FLOAT
# )'''
# cursor.execute(sql)
# print("Table created successfully........")

# # Commit your changes in the database
# conn.commit()

# #Closing the connection
# conn.close()

# # %% insert
# conn = sqlite3.connect('testbooking.db')
# print("Opened database successfully")

# conn.execute("INSERT INTO TESTBOOKING (PNR,NAME,FLIGHT_DATE,PID,PAYMENT) \
#       VALUES ('1A2B3C', 'Ng Thi A', '2021-06-06', 123456789, 200000.00 )")

# conn.execute("INSERT INTO TESTBOOKING (PNR,NAME,FLIGHT_DATE,PID,PAYMENT) \
#       VALUES ('2A2B2C', 'Tr Van A', '2021-06-07', 111222333, 300000.00 )")

# conn.execute("INSERT INTO TESTBOOKING (PNR,NAME,FLIGHT_DATE,PID,PAYMENT) \
#       VALUES ('3A3B3C', 'Tr Van B', '2021-06-08', 333666888, 350000.00 )")

# conn.execute("INSERT INTO TESTBOOKING (PNR,NAME,FLIGHT_DATE,PID,PAYMENT) \
#       VALUES ('3A3B3C', 'Ng Thi C', '2021-06-09', 111333555, 350000.00 )")

# conn.execute("INSERT INTO TESTBOOKING (PNR,NAME,FLIGHT_DATE,PID,PAYMENT) \
#       VALUES ('3A3B3C', 'Ng Thi D', '2021-06-09', 999888777, 400000.00 )")

# conn.commit()
# print( "Records created successfully")
# conn.close()
# %% query
# conn = sqlite3.connect('testbooking.db')

# # con = sqlite3.connect("data/portal_mammals.sqlite")
# df = pd.read_sql_query("SELECT * from TESTBOOKING", conn)

# # Verify that result of SQL query is stored in the dataframe
# print(df.head())

# conn.close()
#%%
DB_PATH = 'actions/testbooking.db'
#%%
def query_pid(pid='111222333'):
    conn = sqlite3.connect(DB_PATH)
    query = "SELECT * from TESTBOOKING WHERE PID={}".format(pid)
    df = pd.read_sql_query(query, conn)
    conn.close()
    if df.shape[0] > 0:
        _result = [str(df.columns[idx])+': '+str(df.iloc[0,:][idx])
                    for idx in range(4) ]
        _result = ', '.join(_result)
    else:
        _result = 'PID không tìm thấy'
    return _result

# query_pid(pid='111222333')
# query_pid(pid='111222336')

#%%
def query_pnr(pnr='ABC123'):
    conn = sqlite3.connect(DB_PATH)
    query = "SELECT * from TESTBOOKING WHERE PNR='{}'".format(str(pnr))
    df = pd.read_sql_query(query, conn)
    conn.close()
    if df.shape[0] > 0:
        _result = [str(df.columns[idx])+': '+str(df.iloc[0,:][idx])
                    for idx in range(4) ]
        _result = ', '.join(_result)
    else:
        _result = 'PNR không tìm thấy'
    return _result

# query_pnr(pnr='ABC123')

# query_pnr(pnr='1A2B3C')


import math
from collections import namedtuple
from typing import Optional, Tuple, Union

import torch

from .external.flowpy import make_colorwheel

DEFAULT_TRANSITIONS = (15, 6, 4, 11, 13, 6)

def flow_to_rgb(
    flow: torch.Tensor,
    flow_max_radius: Optional[Union[float, torch.Tensor]] = None,
    background: str = 'bright',
    custom_colorwheel: Optional[torch.Tensor] = None
) -> torch.Tensor:
    """Create a RGB representation of an optical flow.

    Parameters
    ----------
    flow : torch.Tensor
        Flow with at least 3 dimensions in the ...CHW (..., Channels, Height, Width) layout, where ... represents any number
        of dimensions.
        flow[..., 0, h, w] should be the x-displacement
        flow[..., 1, h, w] should be the y-displacement
    flow_max_radius : float or torch.Tensor, optional
        Set the radius that gives the maximum color intensity, useful for comparing different flows.
        Default: The normalization is based on the input flow maximum radius per batch element.
    background : str, default 'bright'
        States if zero-valued flow should look 'bright' or 'dark'.
    custom_colorwheel : torch.Tensor
        Use a custom colorwheel for specific hue transition lengths. By default, the default transition lengths are used.

    Returns
    -------
    torch.Tensor
        The RGB representation of the flow. RGB values are float in the [0, 1] interval. The output shape is (..., 3, H, W).

    Raises
    ------
    ValueError
        If the background choice is invalid.

    See Also
    --------
    ptlflow.utils.external.flowpy.make_colorwheel : How the colorwheel can be generated.
    """
    valid_backgrounds = ('bright', 'dark')
    if background not in valid_backgrounds:
        raise ValueError(f'background should be one the following: {valid_backgrounds}, not {background}')

    wheel = make_colorwheel() if custom_colorwheel is None else custom_colorwheel
    wheel = torch.from_numpy(wheel).to(dtype=flow.dtype, device=flow.device) / 255

    orig_shape = flow.shape
    if len(orig_shape) == 3:
        flow = flow[None]
    elif len(orig_shape) > 4:
        flow = flow.view(-1, 2, flow.shape[-2], flow.shape[-1])

    complex_flow = flow[:, 0] + 1j * flow[:, 1]
    complex_flow, nan_mask = _replace_nans(complex_flow)

    radius, angle = torch.abs(complex_flow), torch.angle(complex_flow)

    if flow_max_radius is None:
        flow_max_radius = radius.view(radius.shape[0], -1).max(dim=1)[0]
    else:
        flow_max_radius = torch.zeros(radius.shape[0]).to(dtype=flow.dtype, device=flow.device) + flow_max_radius

    flow_max_radius = torch.clamp(flow_max_radius[:, None, None], 1)
    radius /= flow_max_radius

    ncols = len(wheel)

    # Map the angles from (-pi, pi] to [0, 2pi) to [0, ncols - 1)
    angle[angle < 0] += 2 * math.pi
    angle = angle * ((ncols - 1) / (2 * math.pi))

    # Make the wheel cyclic for interpolation
    wheel = torch.cat((wheel, wheel[:1]), dim=0)

    # Interpolate the hues
    angle_fractional = torch.frac(angle)
    angle_floor = torch.floor(angle)
    angle_ceil = torch.ceil(angle)
    angle_fractional = angle_fractional.view((angle_fractional.shape)+(1,))
    float_hue = (wheel[angle_floor.long()] * (1 - angle_fractional)
                 + wheel[angle_ceil.long()] * angle_fractional)

    ColorizationArgs = namedtuple('ColorizationArgs', [
        'move_hue_valid_radius',
        'move_hue_oversized_radius',
        'invalid_color'])

    def _move_hue_on_v_axis(hues: torch.Tensor, factors: torch.Tensor) -> torch.Tensor:
        return hues * torch.unsqueeze(factors, -1)

    def _move_hue_on_s_axis(hues: torch.Tensor, factors: torch.Tensor) -> torch.Tensor:
        return 1. - torch.unsqueeze(factors, -1) * (1. - hues)

    if background == 'dark':
        parameters = ColorizationArgs(_move_hue_on_v_axis, _move_hue_on_s_axis,
                                      torch.zeros(3).to(dtype=flow.dtype, device=flow.device)+1)
    else:
        parameters = ColorizationArgs(_move_hue_on_s_axis, _move_hue_on_v_axis,
                                      torch.zeros(3).to(dtype=flow.dtype, device=flow.device))

    colors = parameters.move_hue_valid_radius(float_hue, radius)

    oversized_radius_mask = radius > 1
    colors[oversized_radius_mask] = parameters.move_hue_oversized_radius(
        float_hue[oversized_radius_mask],
        1 / radius[oversized_radius_mask]
    )
    colors[nan_mask] = parameters.invalid_color

    output_shape = tuple(3 if i == len(orig_shape)-3 else orig_shape[i] for i in range(len(orig_shape)))
    colors = colors.permute(0, 3, 1, 2).contiguous()
    colors = colors.view(output_shape)

    return colors

def _replace_nans(
    array: torch.Tensor,
    value: int = 0
) -> Tuple[torch.Tensor, torch.Tensor]:
    nan_mask = torch.isnan(array)
    array[nan_mask] = value

    return array, nan_mask


"""
Author: Alex Hedges
"""

import argparse
import os
from os.path import join
from os.path import split
import plistlib
import re
import shutil
from urllib.parse import unquote

LIBRARY_FILE_NAME = r'D:\Alex\Music\iTunes\iTunes Library.xml'

def get_playlist(library: dict, name: str) -> list:
    """Gets sorted list of a playlist's song IDs from library ."""
    playlists = [playlist for playlist in library['Playlists'] if playlist['Name'] == name]
    if not playlists:
        return None
    playlist = playlists[0]
    tracks = playlist['Playlist Items']
    track_ids = [track['Track ID'] for track in tracks]
    return sorted(track_ids)

def get_song(library: dict, track_id: int) -> (str, str, str, str):
    """Gets song information from library and transform it to proper format."""
    song = library['Tracks'][str(track_id)]
    name = song['Name']
    if 'Compilation' in song and song['Compilation']:
        artist = 'Compilations'
    else:
        artist = song['Artist']
    if 'Album' not in song:
        album = 'Unknown Album'
    else:
        album = song['Album']
    location = unquote(song['Location'].replace('file://localhost/', ''))

    return name, artist, album, location

def normalize_name(text: str) -> str:
    """Converts string into a version safer for file paths by:
        - Replacing characters not permitted in Windows file paths with underscores.
        - Truncating the string to 40 characters (to stay within the Windows file path limit).
        - Removing trailing and leading whitespace for convenience.
    """
    return re.sub(r'[<>:"/\|?*]+', '_', text)[:40].strip()

def write_m3u(playlist_songs: list, playlist_name: str, destination_dir: str):
    """Writes songs to .m3u file ordered by album, then song"""
    playlist_file_name = join(destination_dir, f'{normalize_name(playlist_name)}.m3u')
    songs = [(album, split(location_source)[1], artist)
        for name, artist, album, location_source in playlist_songs]
    songs.sort()
    with open(playlist_file_name, 'w', encoding='utf8') as playlist_file:
        for album, file_name, artist in songs:
            playlist_file.write('/storage/emulated/0/Music/'
                               f'{normalize_name(artist)}/{normalize_name(album)}/{file_name}\n')

def main():
    """Exports songs from a playlist (and the playlist itself) to another directory."""
    parser = argparse.ArgumentParser(
        description='Exports songs from a playlist (and the playlist itself) to another directory.')
    parser.add_argument('playlist', help='Name of playlist to export')
    parser.add_argument('destination_dir', help='Local directory to export to.')
    args = vars(parser.parse_args())
    playlist_name = args['playlist']
    destination_dir = args['destination_dir']
    if not os.path.isfile(LIBRARY_FILE_NAME):
        raise RuntimeError(f'The iTunes library file "{LIBRARY_FILE_NAME}" does not exist!')
    if not os.path.isdir(destination_dir):
        raise RuntimeError(f'The destination directory "{destination_dir}" does not exist!')

    with open(LIBRARY_FILE_NAME, mode='rb') as library_file:
        library_text = library_file.read()
    library = plistlib.loads(library_text)

    print(f'Reading playlist "{playlist_name}"')
    playlist = get_playlist(library, playlist_name)
    playlist_songs = []

    for song in playlist:
        name, artist, album, location_source = get_song(library, song)
        playlist_songs.append((name, artist, album, location_source))
        location_destination = join(destination_dir, normalize_name(artist), normalize_name(album),
            split(location_source)[1])
        if os.path.isfile(location_destination):
            continue
        if not os.path.isdir(split(location_destination)[0]):
            os.makedirs(split(location_destination)[0])
        print(f'Copying "{name}" from {album}')
        shutil.copyfile(location_source, location_destination)

    print(f'Writing playlist "{playlist_name}"')
    write_m3u(playlist_songs, playlist_name, destination_dir)

if __name__ == '__main__':
    main()


import execjs
import requests
from time import sleep
from random import randint

def m():
    path = '一.js'
    with open(path,'r',encoding='utf-8') as f:
        js=f.read()
    js = execjs.compile(js)
    m=js.call('m')
    print(m)
    return m

def spiders():
    sum_list=[]
    headers={
        'Accept': 'application/json, text/javascript, */*; q = 0.01',
        'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'zh-CN, zh;q=0.9',
        'Connection': 'keep-alive',
        'Cookie': 'qpfccr=true; no-alert=true',
        'Host': 'match.yuanrenxue.com',
        'Referer': 'http://match.yuanrenxue.com/match/1',
        'Pragma': 'no-cache',
        'User-Agent': 'yuanrenxue.project',
        'X-Requested-With': 'XMLHttpRequest',
    }

    for page in range(1,6):
        params={'m':m().replace('|','%E4%B8%A8')}
        print(params)
        print('正在爬取第{}页'.format(page))
        url = 'http://match.yuanrenxue.com/api/match/1?page={}'.format(page)
        response=requests.get(url=url,headers=headers,params=params)
        list=response.json()["data"]
        list=[float(i['value']) for i in list]
        sum_list.extend(list)
        print(sum_list)
        sleep(randint(2,4))
    n=sum(sum_list)/len(sum_list)
    print(n)

spiders()


from flask import Flask, request, abort
from linebot import (
    LineBotApi, WebhookHandler
    )
from linebot.exceptions import (
    InvalidSignatureError, LineBotApiError
    )
from linebot.models import (
    StickerSendMessage, FollowEvent, PostbackEvent, MessageEvent,TextMessage, TextSendMessage, TemplateSendMessage, PostbackAction, ButtonsTemplate, MessageAction, URIAction, CarouselColumn, ImageComponent, FlexSendMessage, CarouselTemplate, BubbleContainer, QuickReply, QuickReplyButton, RichMenu, RichMenuSize, RichMenuArea, RichMenuBounds, MessageAction, URIAction, ImageCarouselTemplate, ImageCarouselColumn,
    ConfirmTemplate, BubbleContainer, ImageComponent, BoxComponent, TextComponent, SpacerComponent, IconComponent, ButtonComponent, SeparatorComponent
    )
from argparse import ArgumentParser
import os, sys, logging, re
import config
from machines import kamen, singaro
from module import is_int, is_float, regular_int, regular_float
from model import User

app = Flask(__name__)

# app.logger.addHandler(logging.StreamHandler(sys.stdout))
# app.logger.setLevel(logging.INFO)

line_bot_api = LineBotApi(config.CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(config.CHANNEL_SECRET)

users = {}

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'

@handler.add(FollowEvent)
def on_follow(event):
    message = TextSendMessage(text='フォローありがとうございます。\
            こちらは「遊タイム期待値」を計算するボットになります。\
            「遊タイム期待値」とは、現在の回転数から「大当たりを引く」あるいは「遊タイム終了」まで打った場合の期待値です。\
                以降、打ち続ける場合は期待値が変わるので注意してください。\
            それではまず、「おは」と入力してみましょう。')

    line_bot_api.reply_message(
        event.reply_token,
        message)

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    if event.message.text == 'おは':
        message = TemplateSendMessage(
            alt_text='Buttons template',
            template=ButtonsTemplate(
                title='必須項目',
                text='タップして選択してください',
                actions=[
                    PostbackAction(
                        label='機種を選ぶ',
                        data = 'machine'
                    ),
                    PostbackAction(
                        label='現在回転数/回転率',
                        data = 'count_rate'
                    )
                ]
            )
        )
    elif regular_int(event.message.text) is not None:
        count = regular_int(event.message.text).group()
        message = TemplateSendMessage(
            alt_text='Confirm template',
            template=ConfirmTemplate(
                text=f'現在の回転数は{count}ですね?',
                actions=[
                    PostbackAction(
                        label='はい',
                        data=count
                    ),
                    PostbackAction(
                        label='やり直す',
                        data='count_rate'
                    )
                ]
            )
        )
    elif regular_float(event.message.text) is not None:
        rate = regular_float(event.message.text).group()
        message = TemplateSendMessage(
            alt_text='Confirm template',
            template=ConfirmTemplate(
                text=f'回転率は{rate}ですね?',
                actions=[
                    PostbackAction(
                        label='はい',
                        data=rate
                    ),
                    PostbackAction(
                        label='やり直す',
                        data='count_rate'
                    )
                ]
            )
        )
    else:
        message = TextSendMessage(text='入力が正しくありません。')

    line_bot_api.reply_message(
        event.reply_token,
        message
    )

@handler.add(PostbackEvent)
def postback(event):
    userId = event.source.user_id
    if not userId in users:
        users[userId] = User(id=userId)

    if event.postback.data == 'machine':
        message = TemplateSendMessage(
            alt_text='Buttons template',
            template=ButtonsTemplate(
                title='機種一覧',
                text='タップして選択してください',
                actions=[
                    PostbackAction(
                        label='Pぱちんこ仮面ライダー轟音M2',
                        data='Pぱちんこ仮面ライダー轟音M2'
                    ),
                    PostbackAction(
                        label='P真・牙狼',
                        data ='P真・牙狼'
                    ),
                ]
            )
        )
    elif event.postback.data == 'count_rate':
        if users[userId].machine == []:
            message = TextSendMessage(text='機種を設定してください。')
        else:
            message = TextSendMessage(
                text='「確変を除いた現在回転数」または「回転率」を入力しています。\
                       整数値を入力すると回転数、小数値を入力すると回転率を設定します。\
                       入力例）\
                       現在回転数が450の場合「450」\
                       回転率が18の場合「18.0」'
                    )
    elif 'P' in event.postback.data:
        users[userId].push_machine(event.postback.data)
        message = TemplateSendMessage(
            alt_text='Confirm template',
            template=ConfirmTemplate(
                text=f'機種を設定しました。他の項目も設定しますか?',
                actions=[
                    PostbackAction(
                        label='はい',
                        data='count_rate'
                    ),
                    PostbackAction(
                        label='やり直す',
                        data='machine'
                    )
                ]
            )
        )
    elif is_int(event.postback.data):
        users[userId].push_count(event.postback.data)
        if users[userId].rate == []:
            message = TextSendMessage(text='続いて回転率を入力してください。')
        else:
            message = TemplateSendMessage(
            alt_text='Confirm template',
            template=ConfirmTemplate(
                text=f"{users[userId].machine[-1]}、現在の回転数{users[userId].count[-1]}、回転率{users[userId].rate[-1]}。この条件で期待値を計算しますか？",
                actions=[
                    PostbackAction(
                        label='はい',
                        data='calculation'
                    ),
                    PostbackAction(
                        label='やり直す',
                        data='machine'
                    )
                ]
            )
        )
    elif is_float(event.postback.data):
        users[userId].push_rate(event.postback.data)
        if users[userId].count == []:
            message = TextSendMessage(text='続いて現在の回転数を入力してください。')
        else:
            message = TemplateSendMessage(
            alt_text='Confirm template',
            template=ConfirmTemplate(
                text=f"{users[userId].machine[-1]}、現在の回転数{users[userId].count[-1]}、回転率{users[userId].rate[-1]}。この条件で期待値を計算しますか？",
                actions=[
                    PostbackAction(
                        label='はい',
                        data='calculation'
                    ),
                    PostbackAction(
                        label='やり直す',
                        data='machine'
                    )
                ]
            )
        )
    elif event.postback.data == 'calculation':
        if users[userId].machine[-1] == 'Pぱちんこ仮面ライダー轟音M2':
            func = kamen
        elif users[userId].machine[-1] == 'P真・牙狼':
            func = singaro
        message = TextSendMessage(text=f'期待値は{users[userId].calculate(func)}円です。')

    line_bot_api.reply_message(
            event.reply_token,
            message
        )

if __name__ == '__main__':
    app.run()


import sqlite3
from sqlite3 import Cursor

from pyrogram.types import Message

from bot.db.database import database

def data_base_connection() -> Cursor:
    try:
        with sqlite3.connect(database) as connection:
            cursor = connection.cursor()
    except sqlite3.Error as error:
        print('База не работает')

    return cursor

def check_user_is_registered(message: Message) -> bool:
    """
    Проверка для /start, чтобы не просить имя и телефон повторно
    """
    cursor = data_base_connection()

    select_query = f"""
    SELECT is_registered from user WHERE tg_id={message.from_user.id}
    """
    cursor.execute(select_query)
    records = cursor.fetchone()
    try:
        is_registered = records[0]
    except:
        is_registered = False  # Если пользователь не найден, будет false

    cursor.close()
    return is_registered

def check_user_condition(message: Message) -> str:
    """
    Проверка состояния пользователя
    """
    cursor = data_base_connection()

    select_query = f"""
        SELECT bot_condition from user WHERE tg_id={message.from_user.id}
        """
    cursor.execute(select_query)
    records = cursor.fetchone()
    try:
        bot_condition = records[0]
    except:
        bot_condition = "change_name"  # Если пользователь не найден, будет стартовое состояние (изменение имени)

    cursor.close()
    return bot_condition

def check_user_info(user_id) -> dict:
    cursor = data_base_connection()

    select_query = f"""
        SELECT tg_id, tg_username, full_name, phone
         from user WHERE tg_id={user_id}
        """
    cursor.execute(select_query)
    r = cursor.fetchone()
    user_info = {"tg_id": r[0], "tg_username": r[1], "full_name": r[2], "phone": r[3]}
    cursor.close()
    return user_info

def check_user_situation(message: Message) -> dict:
    cursor = data_base_connection()

    select_query = f"""
        SELECT tg_id, tg_username, full_name, phone, situation_address, situation_media, situation_description
         from user WHERE tg_id={message.from_user.id}
        """
    cursor.execute(select_query)
    r = cursor.fetchone()
    user_situation_info = {"tg_id": r[0], "tg_username": r[1], "full_name": r[2], "phone": r[3],
                           "situation_address": r[4], "situation_media": r[5],
                           "situation_description": r[6]}
    cursor.close()
    return user_situation_info

def check_user_phone(user_id) -> str:
    cursor = data_base_connection()

    select_query = f"""
        SELECT phone from user WHERE tg_id={user_id}
        """
    cursor.execute(select_query)
    r = cursor.fetchone()
    phone = r[0]
    cursor.close()
    return phone

def check_user_is_admin(user_id) -> bool:
    cursor = data_base_connection()

    select_query = f"""
        SELECT is_admin from user WHERE tg_id={user_id}
        """
    cursor.execute(select_query)
    r = cursor.fetchone()
    if r:
        is_admin = r[0]
    else:
        is_admin = False
    cursor.close()
    return is_admin

def check_user_is_banned(user_id) -> bool:
    cursor = data_base_connection()

    select_query = f"""
        SELECT is_banned from user WHERE tg_id={user_id}
        """
    cursor.execute(select_query)
    r = cursor.fetchone()
    if r:
        is_banned = r[0]
    else:
        is_banned = False
    cursor.close()
    return is_banned

def select_all_users_id() -> list:
    cursor = data_base_connection()

    select_query = f"""
    SELECT tg_id from user"""
    cursor.execute(select_query)
    all_user_id = cursor.fetchall()
    cursor.close()
    return all_user_id

def select_all_users_info() -> list:
    cursor = data_base_connection()

    select_query = f"""
    SELECT tg_id, tg_username, full_name, phone from user"""
    cursor.execute(select_query)
    all_user_id = cursor.fetchall()
    cursor.close()
    return all_user_id


from PyPDF2 import PdfFileWriter, PdfFileReader
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A5
from reportlab.lib.utils import ImageReader

# Barcode
from reportlab.graphics.barcode import qr
from reportlab.graphics.shapes import Drawing
from reportlab.graphics import renderPDF
import datetime, sys, os, glob

import math

participants = 0
pages = 1

# Algorithm to put A5 pages in an A4 booklet
# taken from https://bitbucket.org/spookylukey/booklet-maker/src

class Sheet(object):
    def __init__(self):
        self.front = PrintPage()
        self.back = PrintPage()

class PrintPage(object):
    def __init__(self):
        self.left = PageContainer()
        self.right = PageContainer()

class PageContainer(object):
    def __init__(self):
        self.page = None

def build_booklet(pages):
    # Double sized page, with double-sided printing, fits 4 of the original.
    sheet_count = int(math.ceil(len(pages) / 4.0))

    booklet = [Sheet() for i in range(0, sheet_count)]

    # Assign input pages to sheets

    # This is the core algo. To understand it:
    # * pick up 3 A4 sheets, landscape
    # * number the sheets from 1 to 3, starting with bottom one
    # * fold the stack in the middle to form an A5 booklet
    # * work out what order you need to use the front left,
    #   front right, back left and back right sides.

    def containers():
        # Yields parts of the booklet in the order they should be used.
        for sheet in booklet:
            yield sheet.back.right
            yield sheet.front.left

        for sheet in reversed(booklet):
            yield sheet.front.right
            yield sheet.back.left

    for c, p in zip(containers(), pages):
        c.page = p

    return booklet

def add_double_page(writer, page_size, print_page):
    width, height = page_size
    p = writer.insertBlankPage(width=width, height=height, index=writer.getNumPages())

    # Merge the left page
    l_page = print_page.left.page
    if l_page is not None:
        p.mergePage(l_page)

    # Merge the right page with translation
    r_page = print_page.right.page
    if r_page is not None:
        p.mergeTranslatedPage(r_page, width / 2, 0)

def make_booklet(input_name, output_name, blanks=0):
    reader = PdfFileReader(open(input_name, "rb"))
    pages = [reader.getPage(p) for p in range(0, reader.getNumPages())]
    for i in range(0, blanks):
        pages.insert(0, None)

    sheets = build_booklet(pages)

    writer = PdfFileWriter()
    p0 = reader.getPage(0)
    input_width = p0.mediaBox.getWidth()
    output_width = input_width * 2
    input_height = p0.mediaBox.getHeight()
    output_height = input_height

    page_size = (output_width, output_height)
    # We want to group fronts and backs together.
    for sheet in sheets:
        add_double_page(writer, page_size, sheet.back)
        add_double_page(writer, page_size, sheet.front)

    #    for sheet in sheets:

    writer.write(open(output_name, "wb"))

### Algorithm to take a template and mark it with date, participant ID and a QR code.

def main(argv):

    full_path = argv[1]
    pages = int(argv[2])
    email_address = argv[3]
    #pdfmetrics.registerFont(TTFont('Calibri', 'Calibri.ttf'))

    for subdir, dirs, files in os.walk(full_path):
        for directory in dirs:
            for template in glob.glob(os.path.join(full_path,directory,"diaryTemplate.pdf")):

                date = datetime.datetime(2017,5,29,0,0,0)
                output = PdfFileWriter()
                input_path = template
                input1 = PdfFileReader(open(input_path, "rb"))

                print("Participant " + str(directory) )

                #Cover
                packet = BytesIO()
                can = canvas.Canvas(packet, pagesize=A5)
                w, h = A5
                #can.setFont("Calibri", 20)
                # You can add a text legend
                #can.drawCentredString(w/2, 100, "The University of Manchester")

                # Centering the logo
                logo = ImageReader(os.path.join(full_path, "logo.png"))
                iw, ih = logo.getSize()
                image_scale_width = 250
                aspect = ih / float(iw)
                image_scale_height=(image_scale_width * aspect)
                can.drawImage(logo, x=(w/2) - (image_scale_width/2), y=(h/3)-(image_scale_height/2), width=image_scale_width,preserveAspectRatio=True,mask='auto')

                can.save()
                packet.seek(0)
                new_pdf = PdfFileReader(packet)
                output.addPage(new_pdf.getPage(0))
                output.addBlankPage()

                for page in range(1, pages+1):
                    input1 = PdfFileReader(open(input_path, "rb"))
                    # create the date and page marks with Reportlab
                    packet = BytesIO()
                    can = canvas.Canvas(packet, pagesize=A5)

                    date += datetime.timedelta(days=1)

                    # Barcode
                    barcode_value = date.strftime('%y%m%d') + directory

                    qr_code = qr.QrCodeWidget(barcode_value)
                    bounds = qr_code.getBounds()
                    width = bounds[2] - bounds[0]
                    height = bounds[3] - bounds[1]
                    d = Drawing(30, 30, transform=[30./width,0,0,30./height,0,0])
                    d.add(qr_code)
                    renderPDF.draw(d, can, 355, 554)

                    # Header
                    #can.setFont("Calibri", 11)
                    #can.drawString(340, 565, "P" + str(directory).zfill(2) )
                    can.drawString(320, 565, directory )
                    #can.setFont("Calibri", 11)
                    can.drawString(36.5, 565, date.strftime('%A, %d %b %Y') )

                    # Footer
                    #can.setFont("Calibri", 8)
                    can.drawString(36.5, 24, str(page))
                    can.save()

                    #move to the beginning of the StringIO buffer
                    packet.seek(0)
                    new_pdf = PdfFileReader(packet)

                    newPage = input1.getPage(0)
                    newPage.mergePage(new_pdf.getPage(0))
                    output.addPage(newPage)

                # Backcover
                output.addBlankPage()
                packet = BytesIO()
                can = canvas.Canvas(packet, pagesize=A5)
                #can.setFont("Calibri", 10)
                can.drawString(75, 50, "If you find this diary, please email " + email_address)
                can.save()
                packet.seek(0)
                new_pdf = PdfFileReader(packet)
                output.addPage(new_pdf.getPage(0))

                # finally, write "output" to document-output.pdf
                #outputPath = "C://Users//julio//Documents//phd//study//collection//visits//diary//sk0" + str(participant) + "//diary" + str(participant) + ".pdf"
                outputPath = os.path.join(full_path, directory, directory + "-a5-single-sided.pdf")
                #outputBookletPath = "C://Users//julio//Documents//phd//study//collection//visits//diary//diariesBooklets//diaryBooklet" + str(participant) + ".pdf"
                outputBookletPath = os.path.join(full_path, directory, directory + "-a4-double-sided.pdf")
                outputStream = open(outputPath, "wb")
                output.write(outputStream)

                outputStream.close()

                make_booklet(outputPath, outputBookletPath, 0)

if __name__ == "__main__":
    main(sys.argv)


import new

from paver import setuputils, tasks

class FakeModule(object):
    def __init__(self, **kw):
        for name, value in kw.items():
            setattr(self, name, value)

def patched_print(self, output):
    self.patch_captured.append(output)

class FakeExitException(Exception):
    """ Fake out tasks.Environment._exit to avoid interupting tests """

def patched_exit(self, code):
    self.exit_code = 1
    raise FakeExitException(code)

def _set_environment(patch_print=False, **kw):
    pavement = FakeModule(**kw)
    env = tasks.Environment(pavement)
    tasks.environment = env
    env._exit = new.instancemethod(patched_exit, env, tasks.Environment)
    if patch_print:
        env._print = new.instancemethod(patched_print, env, tasks.Environment)
        env.patch_captured = []
    return env


#!/usr/bin/env python3

from collections import Counter

def part1(fishes):
    for day in range(80):
        for i, fish in enumerate(fishes):
            if fish == 0:
                fishes[i] = 7
                fishes.append(9)
        fishes = [fish - 1 for fish in fishes]
    print(len(fishes))

def part2(fishes):
    timers = Counter({timer: 0 for timer in range(10)})
    fishes = Counter(fishes)
    fishes.update(timers)

    for day in range(256):
        fishes[7] += fishes.get(0,0)
        fishes[9] += fishes.get(0,0)
        fishes = {fish: fishes.get(fish + 1, 0) for fish in fishes}

    print(sum(fishes.values()))

def main():
    with open("input.txt", "r") as f:
        fishes = f.read().strip().split(',')
        fishes = list(map(int, fishes))
    part1(fishes)
    part2(fishes)

if __name__ == "__main__":
    main()


# Approach 4: Mark Visited Elements in the Input Array itself

# Intuition:
# All the above approaches have ignored a key piece of information in the problem statement:
# The integers in the input array arr satisfy 1 ≤ arr[i] ≤ n, where n is the size of array.
# This presents us with two key insights:
#   1. All the integers present in the array are positive. i.e. arr[i] > 0 for any valid index i.
#   2. The decrement of any integers present in the array must be an accessible index in the array. i.e. for any
#      integer x in the array, x-1 is a valid index, and thus, arr[x-1] is a valid reference to an element in the
#      array.

# Algorithm:
# 1. Iterate over the array and for every element x in the array, negate the value at index abs(x)-1.
#   -   The negation operation effectively marks the value abs(x) as seen / visited.
# 2. Iterate over the array again, for every element x in the array:
#   -   If the value at index abs(x)-1 is positive, it must have been negated twice. Thus abs(x) must have appeared
#       twice in the array. We add abs(x) to the result.
#   -   In the above case, when we reach the second occurrence of abs(x), we need to avoid fulfilling this
#       condition again. So, we'll additionally negate the value at index abs(x)-1.

from typing import List

class Solution:
    def findDuplicates(self, nums: List[int]) -> List[int]:
        ans = []

        for num in nums:
            nums[abs(num) - 1] *= -1

        for num in nums:
            if nums[abs(num) - 1] > 0:
                ans.append(abs(num))
                # to avoid duplicates
                nums[abs(num) - 1] *= -1

        return ans

# OR
# with just one for loop(avoiding first for loop to negate all elements)
# https://youtu.be/aMsSF1Il3IY

class Solution:
    def findDuplicates(self, nums: List[int]) -> List[int]:
        ans = []

        for num in nums:
            index = abs(num) - 1
            if nums[index] < 0:
                ans.append(abs(num))
            nums[index] *= -1

        return ans

nums = [4,3,2,7,8,2,3,1]
obj = Solution()
print(obj.findDuplicates(nums))

# Complexity Analysis:
# Time Complexity: O(n). We iterate over the array twice. Each negation operation occurs in constant time.
# Space Complexity: No extra space required, other than the space for the output list. We re-use the input array
# to store visited status.


import win32com.client
import os
from datetime import datetime, timedelta
import schedule
import time

# Создаем экземпляр приложения Outlook
outlook = win32com.client.Dispatch('outlook.application')

# Получаем доступ к объекту MAPI
mapi = outlook.GetNamespace("MAPI")

# Выводим названия учетных записей Outlook
for account in mapi.Accounts:
    print(account.DeliveryStore.DisplayName)  # Название учетной записи Outlook

# Получаем доступ к папке "Входящие" (Inbox)
inbox = mapi.GetDefaultFolder(6)  # Папка "Входящие"

# Получаем доступ к папке внутри "Входящие"
inbox = inbox.Folders['VN']  # Папка внутри "Входящие" (замените "your folder" на имя нужной папки)

# Функция для обработки писем
def process_emails():
    # Получаем все сообщения в выбранной папке
    messages = inbox.Items

    # Устанавливаем временной диапазон для ограничения поиска писем (последние 24 часа)
    received_dt = datetime.now() - timedelta(days=1)  # Сюда вводим значения дней по умолчанию 24 часа (1 день)
    received_dt = received_dt.strftime('%m/%d/%Y %H:%M %p')

    # Задаем отправителя и тему письма для фильтрации
    email_sender = 'Василий Наумов'  # Отправитель письма
    email_subject = 'FW: Отчет об активациях Nurtelecom'  # Тема письма

    # Применяем ограничения к набору сообщений
    messages = messages.Restrict("[ReceivedTime] >= '" + received_dt + "'")

    # Задаем путь для сохранения вложений в текущую директорию
    outputDir = r'D:\Aidar\Python\outlook_info'  # если хотим в текущую директорию, то оставляем outputDir = os.getcwd()

    try:
        for message in list(messages):
            # Проверяем соответствие отправителя, темы письма и даты получения
            if email_subject == message.subject and (message.SenderEmailAddress == email_sender or message.sender.Name == email_sender):
                try:
                    # Сохраняем все вложения письма
                    s = message.sender
                    for attachment in message.Attachments:
                        attachment.SaveASFile(os.path.join(outputDir, attachment.FileName))
                        print(f"Вложение {attachment.FileName} от {s} сохранено")
                except Exception as e:
                    print("Ошибка при сохранении вложения: " + str(e))
    except Exception as e:
        print("Ошибка при обработке писем: " + str(e))

# Запускаем обработку писем каждый день в определенное время
schedule.every().day.at("16:10").do(process_emails)  # Здесь установите желаемое время запуска

# Бесконечный цикл для выполнения заданий по расписанию
while True:
    schedule.run_pending()
    time.sleep(1)


from requests_oauthlib import OAuth1Session
import json
import os
import re
import urllib
import setting

twitter = OAuth1Session(setting.CONSUMER_KEY, setting.CONSUMER_SECRET, setting.ACCESS_TOKEN, setting.ACCESS_TOKEN_SECRET)
mkdir_name = "test"

# 集めた画像を格納するディレクトリの作成を行う
def dir_check():
    if not os.path.isdir(mkdir_name):
        os.mkdir(mkdir_name)
    check_count = 0
    while True:
        if not os.path.isdir(mkdir_name + "/dir" + str(check_count)):
            os.mkdir(mkdir_name + "/dir" + str(check_count))
            dir_name = "/dir" + str(check_count)
            return dir_name
        check_count += 1

def get_target_ward(ward):
    url = "https://api.twitter.com/1.1/search/tweets.json"
    params = {'q':ward,
              'count':100
          }
    req = twitter.get(url, params = params)
    timeline = json.loads(req.text)
    return timeline

# 取得したツイートに画像があれば、その画像を取得する
def get_illustration(timeline, dir_name):
    global image
    global image_number
    image_number = 0
    check_image = []
    for tweet in timeline['statuses']:
        try:
            media_list = tweet['extended_entities']['media']
            for media in media_list:
                image = media['media_url']
                if image in check_image:
                    continue
                with open(mkdir_name + dir_name +"/sense_"+str(image_number) +"_"+os.path.basename(image), 'wb') as f:
                    img = urllib.request.urlopen(image).read()
                    f.write(img)
                check_image.append(image)
                image_number += 1
            print("get tweet media")
        except KeyError:
            print("KeyError:画像を含んでいないツイートです")
        except:
            print("error")

if __name__ == '__main__':
    dir_name = dir_check()
    all_list = []
    # 検索対象の単語を設定
    # ORで条件を付けれる
    # 例:ward = "佐久間まゆ+OR+ままゆ"
    # ならば、ツイートに「佐久間まゆ」、もしくは「ままゆ」と含まれる物を取得
    ward = "#sense_connect"
    timeline = get_target_ward(ward)
    get_illustration(timeline, dir_name)


import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import os
import matplotlib.pyplot as plt
from tqdm import tqdm
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--train_root', type=str, default='/home/xywang/code/pruning/catdog_classification/train',
                    help='training dataset (default: train)')
parser.add_argument('--vaild_root', type=str, default='/home/xywang/code/pruning/catdog_classification/test',
                    help='training dataset (default: test)')
parser.add_argument('--sr',type=bool, default=True,
                    help='train with channel sparsity regularization')
parser.add_argument('--s', type=float, default=0.0001,
                    help='scale sparse rate (default: 0.0001)')
parser.add_argument('--refine', default='', type=str, metavar='PATH',
                    help='path to the pruned model to be fine tuned')
parser.add_argument('--batch_size', type=int, default=100, metavar='N',
                    help='input batch size for training (default: 64)')
parser.add_argument('--epochs', type=int, default=20, metavar='N',
                    help='number of epochs to train (default: 160)')
parser.add_argument('--start_epoch', default=0, type=int, metavar='N',
                    help='manual epoch number (useful on restarts)')
parser.add_argument('--lr', type=float, default=0.001, metavar='LR',
                    help='learning rate (default: 0.001)')
parser.add_argument('--resume', default='', type=str, metavar='PATH',
                    help='path to latest checkpoint (default: none)')
parser.add_argument('--save', default='./models', type=str, metavar='PATH',
                    help='path to save prune model (default: current directory)')
parser.add_argument('--arch', default='resnet', type=str,
                    help='architecture to use')
parser.add_argument('--scratch',default='', type=str,
                    help='the PATH to the pruned model')

args = parser.parse_args()
device = torch.device('cuda:0')

if not os.path.exists(args.save):
    os.makedirs(args.save)

#数据加载及处理
train_transform = transforms.Compose([
    transforms.Resize(224),
    transforms.RandomResizedCrop(224,scale=(0.6,1.0),ratio=(0.8,1.0)),
    transforms.RandomHorizontalFlip(),
    torchvision.transforms.ColorJitter(brightness=0.5, contrast=0, saturation=0, hue=0),
    torchvision.transforms.ColorJitter(brightness=0, contrast=0.5, saturation=0, hue=0),
    transforms.ToTensor(),
    transforms.Normalize(mean=[.5, .5, .5], std=[.5, .5, .5])
])

test_transform = transforms.Compose([
    transforms.Resize(224),
    transforms.RandomResizedCrop(224,scale=(1.0,1.0),ratio=(1.0,1.0)),
    # transforms.RandomResizedCrop(224,scale=(0.6,1.0),ratio=(0.8,1.0)),
    # transforms.RandomHorizontalFlip(),
    # torchvision.transforms.ColorJitter(brightness=0.5, contrast=0, saturation=0, hue=0),
    # torchvision.transforms.ColorJitter(brightness=0, contrast=0.5, saturation=0, hue=0),
    transforms.ToTensor(),
    transforms.Normalize(mean=[.5, .5, .5], std=[.5, .5, .5])
])

train_data =  torchvision.datasets.ImageFolder(
        root=args.train_root,
        transform=train_transform
    )

vaild_data = torchvision.datasets.ImageFolder(
        root=args.vaild_root,
        transform=test_transform
    )

train_set = torch.utils.data.DataLoader(
    train_data,
    batch_size=args.batch_size,
    shuffle=True
)

test_set = torch.utils.data.DataLoader(
    vaild_data,
    batch_size=args.batch_size,
    shuffle=False
)

def updateBN(model, s ,pruning_modules):
    for module in pruning_modules:
        module.weight.grad.data.add_(s * torch.sign(module.weight.data))
#训练和验证
criteration = nn.CrossEntropyLoss()
def train(model,device,dataset,optimizer,epoch,pruning_modules):
    model.train()
    correct = 0
    for i,(x,y) in tqdm(enumerate(dataset)):
        x , y = x.to(device), y.to(device)
        optimizer.zero_grad()
        output = model(x)
        pred = output.max(1,keepdim=True)[1]
        correct += pred.eq(y.view_as(pred)).sum().item()
        loss =  criteration(output,y)
        loss.backward()
        optimizer.step()

        if args.sr:
            updateBN(model,args.s,pruning_modules)

    print("Epoch {} Loss {:.4f} Accuracy {}/{} ({:.3f}%)".format(epoch,loss,correct,len(dataset)*args.batch_size,100*correct/(len(dataset)*args.batch_size)))

def vaild(model,device,dataset):
    model.eval()
    correct = 0
    with torch.no_grad():
        for i,(x,y) in tqdm(enumerate(dataset)):
            x,y = x.to(device) ,y.to(device)
            output = model(x)
            loss = criteration(output,y)
            pred = output.max(1,keepdim=True)[1]
            correct += pred.eq(y.view_as(pred)).sum().item()
    print("Test Loss {:.4f} Accuracy {}/{} ({:.3f}%)".format(loss,correct,len(dataset)*args.batch_size,100*correct/(len(dataset)*args.batch_size)))

def get_pruning_modules(model):
    module_list = []
    for module in model.modules():
        if isinstance(module,torchvision.models.resnet.Bottleneck):
            module_list.append(module.bn1)
            module_list.append(module.bn2)
    return module_list

model = torchvision.models.resnet50(pretrained=True)
model.fc = nn.Sequential(
        nn.Linear(2048,2)
    )
model.to(device)

pruning_modules = get_pruning_modules(model)

optimizer = optim.Adam(model.parameters(), lr=args.lr, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)

for epoch in range(1,args.epochs + 1):
    train(model,device,train_set,optimizer,epoch,pruning_modules)
    vaild(model,device,test_set)
    torch.save(model.state_dict(), 'models/model_pruning.pth')