import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Optional, Union

import tifffile

from imctools.data import Acquisition, Channel
from imctools.data.acquisitiondata import AcquisitionData

class OmeTiffParser:
    """Parser of MCD compatible .OME-TIFF files.

    Allows to get a single IMC acquisition from a single OME-TIFF file.
    """

    def __init__(self, filepath: Union[str, Path], slide_id: int = 0, channel_id_offset: int = 0):
        if isinstance(filepath, str):
            filepath = Path(filepath)
        self._filepath = filepath
        self._slide_id = slide_id
        self._channel_id_offset = channel_id_offset
        self._acquisition_data = self._parse_acquisition(filepath)

    @property
    def origin(self):
        return "ome.tiff"

    @property
    def filepath(self):
        return self._filepath

    def get_mcd_xml(self) -> Optional[str]:
        """Original (raw) metadata from MCD file in XML format."""
        return self._mcd_xml

    def get_acquisition_data(self):
        """Returns AcquisitionData object with binary image data"""
        return self._acquisition_data

    @staticmethod
    def extract_acquisition_id(filepath: Union[str, Path]):
        """Extract acquisition ID from source OME-TIFF filepath.

        Filename should end with a numeric symbol!

        Parameters
        ----------
        filepath
            Input OME-TIFF filepath
        """
        if isinstance(filepath, str):
            filepath = Path(filepath)
        return int(filepath.stem.rstrip("_ac.ome").split("_")[-1].lstrip("a"))

    def _parse_acquisition(self, filepath: Path):
        image_data, ome_xml = OmeTiffParser._read_file(filepath)
        image_name, channel_names, channel_labels, self._mcd_xml = OmeTiffParser._parse_ome_xml(ome_xml)

        max_x = image_data.shape[2]
        max_y = image_data.shape[1]

        # TODO: implement a proper signal type extraction
        signal_type = "Dual"

        # Extract acquisition id from OME-TIFF file name
        acquisition_id = OmeTiffParser.extract_acquisition_id(filepath)

        # Offset should be 0 as we already got rid of 'X', 'Y', 'Z' channels!
        acquisition = Acquisition(
            self._slide_id,
            acquisition_id,
            self.origin,
            str(filepath),
            max_x,
            max_y,
            signal_type=signal_type,
            description=image_name,
        )

        for i in range(len(channel_names)):
            channel = Channel(acquisition.id, self._channel_id_offset, i, channel_names[i], channel_labels[i])
            self._channel_id_offset += 1
            channel.acquisition = acquisition
            acquisition.channels[channel.id] = channel

        acquisition_data = AcquisitionData(acquisition, image_data)
        # Calculate channels intensity range
        for ch in acquisition.channels.values():
            img = acquisition_data.get_image_by_name(ch.name)
            ch.min_intensity = round(float(img.min()), 4)
            ch.max_intensity = round(float(img.max()), 4)

        return acquisition_data

    @staticmethod
    def _parse_ome_xml(xml: str):
        ome: ET.Element = ET.fromstring(xml)
        ns = "{" + ome.tag.split("}")[0].strip("{") + "}"

        img = ome.find(ns + "Image")
        if img is None:
            raise ValueError("OME-TIFF XML is corrupted")

        channels = img.findall(f"{ns}Pixels/{ns}Channel")
        chan_dict = {
            int(chan.attrib["ID"].split(":")[2]): (chan.attrib.get("Name", chan.attrib["Fluor"]), chan.attrib["Fluor"])
            for chan in channels
        }

        image_name = img.attrib["Name"]
        channel_names = [chan_dict[i][1] for i in range(len(channels))]
        channel_labels = [chan_dict[i][0] for i in range(len(channels))]

        original_metadata_value = ome.find(
            f"{ns}StructuredAnnotations/{ns}XMLAnnotation/{ns}Value/{ns}OriginalMetadata/{ns}Value"
        )
        mcd_xml = original_metadata_value.text if original_metadata_value else None

        return image_name, channel_names, channel_labels, mcd_xml

    @staticmethod
    def _read_file(filepath: Path):
        with tifffile.TiffFile(filepath) as tif:
            data = tif.asarray(out="memmap")
            try:
                ome_xml = tif.pages[0].tags["ImageDescription"].value
            except:
                ome_xml = tif.pages[0].tags["image_description"].value
            return data, ome_xml

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        pass

if __name__ == "__main__":
    import timeit

    tic = timeit.default_timer()

    with OmeTiffParser(
        "/home/anton/Downloads/imc_folder_v2/20170905_Fluidigmworkshopfinal_SEAJa/20170905_Fluidigmworkshopfinal_SEAJa_s0_a0_ac.ome.tiff"
    ) as parser:
        ac = parser.get_acquisition_data()
        pass

    print(timeit.default_timer() - tic)


from html.parser import HTMLParser
import os
import sys

class Parser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.in_line_number = False
        self.line_number = ""
        self.in_uncovered_line = False
        self.uncovered_line = ""
        self.uncovered_lines = []

    def handle_starttag(self, tag, attrs):
        if tag == "td":
            for k, v in attrs:
                if k == "class" and v == "line-number":
                    self.in_line_number = True
                    self.line_number = ""
                elif k == "class" and v == "uncovered-line":
                    self.in_uncovered_line = True
                    self.uncovered_line = ""

    def handle_endtag(self, tag):
        if self.in_line_number:
            self.in_line_number = False
        elif self.in_uncovered_line:
            self.in_uncovered_line = False
            if self.uncovered_line == "0":
                self.uncovered_lines.append(self.line_number)

    def handle_data(self, data):
        if self.in_line_number:
            self.line_number += data
        elif self.in_uncovered_line:
            self.uncovered_line += data

def show_missing_for_path(path) -> None:
    relpath = path
    relpath = relpath.replace(os.getcwd(), "")
    relpath = relpath.replace("target/llvm-cov/html/coverage/", "")
    relpath = relpath.replace(".rs.html", ".rs")
    with open(path) as stream:
        parser = Parser()
        parser.feed(stream.read())
        for uncovered_line in sorted(set(parser.uncovered_lines)):
            print("{}:{}".format(relpath, uncovered_line))

if __name__ == "__main__":
    root = "target/llvm-cov/html/coverage/"
    if len(sys.argv) > 1:
        paths = sys.argv[1:]
    else:
        paths = sorted(set([os.path.join(dp, f) for dp, dn, filenames in os.walk(root) for f in filenames if f.endswith(".rs.html")]))
    for path in paths:
        show_missing_for_path(path)

# vim:set shiftwidth=4 softtabstop=4 expandtab:


"""Contains the definition for the StorageHeartbeat class."""

import time
import cPickle as pickle
from socket import error as SocketError
from datetime import datetime
from threading import Thread, Event
from Queue import Empty

from shared import Message, configure_logging

class StorageHeartbeat(object):
    """The StorageHeartbeat runs on the parent process of the client instance.
       It acts as the master to the StorageConsumer and StorageMonitor
       instances.

       Communication of task status from all children is done
       via a multiprocessing.Queue.  Additionally, there is a multiprocessing.Pipe
       connected to each child process for sending HEARTBEAT and KILL requests.

       All status tasks received on the queue are pickled and sent to the server
       over a socket.  HEARTBEAT and KILL responses are aggregated, pickled,
       and sent to the server over the same socket.
    """

    #These constants could optionally be converted to configuration parameters

    # (seconds) HEARTBEAT requests to child process must be reponded to within
    # this amout of time.
    HEARTBEAT_RESPONSE_TIMEOUT = 3
    # (seconds) KILL requests to child process must be reponded to within
    # this amout of time.
    HEARTBEAT_KILL_TIMEOUT = 10

    def __init__(self, consumers, monitor, report_in, runtime,
                 poll_period, client_socket, log_level=None):
        """Initializes a StorageHeartbeat with:

            Args:
                consumers: An iterable of ProcessData objects for the
                    StorageConsumer processes.
                monitor: A ProcessData object for the StorageMonitor process.
                report_in: Queue containing all child processes status.
                runtime: The configured runtime of the client.
                poll_period: The interval (seconds) on which to send
                    HEARTBEAT requests to child processes and then forward
                    the results to the server.
                client_socket: A connected socket to the server.
                log_level: A string matching the logging level.
                    (e.g. DEBUG, INFO, WARNING)
        """
        self.consumers = consumers
        self.monitor = monitor
        self.report_in = report_in
        self.runtime = runtime
        self.poll_period = poll_period
        self.socket = client_socket
        self.log = configure_logging(log_level, 'Client') \
            if log_level is not None else None

    def _log_message_received(self, message):
        """A helper method to log a message received from a child process.

            Args:
                message: A Message object received from a child process.
        """
        self.log.info('Message received from {}_{}: {}'.format(message.name,
                                                               message.id,
                                                               repr(message)))

    def _log_message_sent(self, message, process):
        """A helper method to log a message sent to a child process.

            Args:
                message: A Message object sent to a child process.
                process: The Process to which the message was sent.
        """
        self.log.info('Message sent to {}_{}: {}'.format(process.name,
                                                         process.id,
                                                         repr(message)))

    def _send_message_to_server(self, message):
        """Pickle a message and send it to the server.

           Sets the kill Event should the socket no longer be open.

            Args:
                message: A Message object to be sent to the server.
        """
        # pickle the message into a string
        # Not secure but sufficient for our purposes
        ps_message = pickle.dumps(message, pickle.HIGHEST_PROTOCOL)

        # Add a simple prefix to our message indicating the length that the
        # server should expect.  This allows us to leave the socket open for
        # all messages on this connection.
        ps_message = ':::{}:::{}'.format(len(ps_message), ps_message)
        try:
            self.socket.sendall(ps_message)
        except SocketError:
            # Server socket seems to have gone away.  Abort!
            self.kill.set()

    def _poll_processes(self, message, timeout, response_type, wait_to_send=None):
        """This method sends a message to all children processes and collects
           the response messages from each.  Processes that fail to respond are
           aggregated as a list of tuple of (name, id). The process responses
           and failed response list are sent to the server.

           This method is used by _do_heartbeat and _kill_all to prevent code
           duplication.

            Args:
                message: A Message object to send to all children.
                timeout: Time (s) to wait for responses.
                response_type: The Message.type expected in the responses.
                wait_to_send: And event signal that we wait on before sending
                    the message to the server.
        """
        #Send the message to the monitor and all consumers
        self.monitor.pipe.send(message)
        self._log_message_sent(message, self.monitor.process)

        for consumer in self.consumers:
            consumer.pipe.send(message)
            self._log_message_sent(message, consumer.process)

        # Poll the monitor and consumers until we get all responses or until
        # we timeout.
        responses = []
        response_stop = time.time() + timeout

        while time.time() < response_stop and \
            len(responses) < len(self.consumers) + 1:

            if self.monitor.pipe.poll():
                response = self.monitor.pipe.recv()
                self._log_message_received(response)
                if response.type == response_type:
                    responses.append(response)

            for consumer in self.consumers:
                if consumer.pipe.poll():
                    response = consumer.pipe.recv()
                    self._log_message_received(response)
                    if response.type == response_type:
                        responses.append(response)

        missing_responses = set([])
        if len(responses) < len(self.consumers) + 1:
            # We must have timed out.  Check for missed responses
            responding_processes = set([(proc.name, proc.id) for proc in responses])
            processes = set([(consumer.process.name, consumer.process.id) for
                             consumer in self.consumers])
            processes.add((self.monitor.process.name, self.monitor.process.id))

            # Items in the processes set but not in the responding_processes set
            # (symmetric difference) are the missing responses
            missing_responses = processes - responding_processes

        if wait_to_send is not None:
            # Signal message queue to finish processing
            self.kill.set()
            # Wait until work is done
            wait_to_send.wait()

        message = Message(name='Heartbeat',
                          id=0,
                          date_time=datetime.now(),
                          type=response_type,
                          payload=(responses, missing_responses))
        self._send_message_to_server(message)
        self.log.info('Message sent to server: {}'.format(repr(message)))

    def _do_heartbeat(self):
        """Periodically send heartbeat requests to child processes and forward
           results to the server until:
            1. Runtime is elapsed
            2. Kill event gets set (socket was closed)
        """
        heartbeat_stop = time.time() + self.runtime

        # Stop running if the kill event is set or if we've been running
        # for specified runtime
        while time.time() < heartbeat_stop and not self.kill.is_set():
            poll_start_time = time.time()

            message = Message(name='Heartbeat',
                              id=0,
                              date_time=None,
                              type='HEARTBEAT',
                              payload=None)

            self._poll_processes(message=message,
                                 timeout=self.HEARTBEAT_RESPONSE_TIMEOUT,
                                 response_type='HEARTBEAT')

            # Subtract the elapsed time from the poll_period for
            # more accurate heartbeat intervals
            sleep_time = self.poll_period - (time.time() - poll_start_time)
            if sleep_time > 0:
                time.sleep(sleep_time)

    def _kill_all(self):
        """Send kill message to all child processes.

           This run after the client runtime has elapsed or the kill event
           has been set.
        """
        message = Message(name='Heartbeat',
                          id=0,
                          date_time=None,
                          type='KILL',
                          payload=None)

        # Wait to send STOP message to server until the message queue has been
        # fully processed.  This hopes to arrange this as the last message to
        # arrive at the server.
        self._poll_processes(message=message,
                             timeout=self.HEARTBEAT_KILL_TIMEOUT,
                             response_type='STOP',
                             wait_to_send=self.queue_empty)

    def _process_message_queue(self):
        """Process messages in the report_in queue.

           The self.kill Event signals this thread to process the
           queue until empty and then exit.

           This method runs as a separate background thread while
           _do_heartbeat and _kill_all run in the foreground.
        """
        while not self.kill.is_set():
            try:
                message = self.report_in.get(timeout=2)
            except Empty:
                continue

            self._log_message_received(message)
            self._send_message_to_server(message)

        # Kill event was set. Finish processing the remaining events in the queue
        while not self.report_in.empty():
            try:
                message = self.report_in.get(block=False)
            except Empty:
                break # We shouldn't ever get here

            self._log_message_received(message)
            self._send_message_to_server(message)

        # Signal that the queue has been fully processed
        self.queue_empty.set()

    def run(self):
        """This is the main entry point for StorageHeartbeat."""
        self.kill = Event() # This signals _process_message_queue to finish up

        # Event used to delay sending STOP message to server until all messages
        # in the queue have been handled
        self.queue_empty = Event()

        t = Thread(target=self._process_message_queue)
        t.start()

        # Let _do_heartbeat decide how long to run for
        self._do_heartbeat()
        # Then kill all child processes
        self._kill_all()

        # Finish processing remaining messages from child processes
        t.join()

        self.socket.close()


METER_TO_KM = 1e-3
ONE_TO_KILO = 1e3

KM_TO_METER = 1e3
KILO_TO_ONE = 1e3

# Average earth radius, see https://en.wikipedia.org/wiki/Earth_radius
EARTH_RADIUS_KM = 6371.0088

# in reality air density varies between 1.14 and 1.42 in kg/m^3
AIR_DENSITY_RHO = 1.225

# of course this introduces a small mistake due to leap years, but in average it's quite ok
# Warning: in most cases it might be better to use mean() instead of sum()/HOURS_PER_YEAR
HOURS_PER_YEAR = 8765.812536


import netCDF4

fp = 'insert your file path here'

def read_merra(fp):
    NETCDF = { }
    nc = netCDF4.Dataset(fp)
    Var = nc.variables
    Keys = list(Var)
    for k in Keys:
        array = numpy.array(nc[k])
        NETCDF[k] =   numpy.flip(array,axis=0)
        NETCDF['lat'] = ( nc.WesternmostLongitude  , nc.EasternmostLongitude )
        NETCDF['lon'] = ( nc.SouthernmostLatitude  , nc.NorthernmostLatitude )
        NETCDF['res'] =  { 'x'  : nc.LatitudeResolution  , 'y' : nc.LongitudeResolution }
    return NETCDF


"""
1. 아이디어
    -클경우 넣기
    -작을경우
        -안넣기
        -안작을때까지 빼고 넣기
    -max(len)
"""
import sys; input = sys.stdin.readline
n = int(input())
arr = list(map(int,input().split()))
dp = [1]*n
for i in range(1,n):
    for j in range(i):
        if arr[j]<arr[i]: dp[i] = max(dp[j]+1,dp[i])
print(max(dp))


import numpy as np
import cv2
import sys
from time import time

import kcftracker

selectingObject = False
initTracking = False
onTracking = False

ix, iy, cx, cy = -1, -1, -1, -1
w, h = 0, 0

inteval = 1
duration = 0.01

# mouse callback function
def draw_boundingbox(event, x, y, flags, param):
	global selectingObject, initTracking, onTracking, ix, iy, cx,cy, w, h

	if event == cv2.EVENT_LBUTTONDOWN:
		selectingObject = True
		onTracking = False
		ix, iy = x, y
		cx, cy = x, y

	elif event == cv2.EVENT_MOUSEMOVE:
		cx, cy = x, y

	elif event == cv2.EVENT_LBUTTONUP:
		selectingObject = False
		if(abs(x-ix)>10 and abs(y-iy)>10):
			w, h = abs(x - ix), abs(y - iy)
			ix, iy = min(x, ix), min(y, iy)
			initTracking = True
		else:
			onTracking = False

	elif event == cv2.EVENT_RBUTTONDOWN:
		onTracking = False
		if(w>0):
			ix, iy = x-w/2, y-h/2
			initTracking = True

if __name__ == '__main__':
	flag = 0

	if(len(sys.argv) == 1):
		cap = cv2.VideoCapture(0)
		flag = 1
	elif(len(sys.argv) == 3):
		## setting external camera
		if (sys.argv[1].isdigit() and sys.argv[2] == 'cam'):
			cap = cv2.VideoCapture(sys.argv[1])
			flag = 1
		elif (sys.argv[2] == 'vid'):
			cap = cv2.VideoCapture(sys.argv[1])
			flag = 2
		elif (sys.argv[2] == 'seq'):
			cap = cv2.VideoCapture(sys.argv[1] + '%04d.jpg', cv2.CAP_IMAGES)
			flag = 2
			inteval = 30
	else:
		assert(0), "Please check help for description"

	tracker = kcftracker.KCFTracker(True, True, True)
	# hog, fixed_window, multiscale

	cv2.namedWindow('Tracking')
	cv2.setMouseCallback('Tracking',draw_boundingbox)

	# Reading the first frame in case of stored video
	# This ensures we can mark the BB with stability.
	ret, frame = cap.read()
	if not ret:
		print('Nothing was returned.')
		exit(0)
	firstFrame = frame.copy() # Copy to restore each frame after BB creation
	while (not initTracking and flag == 2):
		if(selectingObject):
			cv2.rectangle(frame, (ix,iy), (cx,cy), (0,255,255), 1)
		cv2.imshow('Tracking', frame)
		cv2.waitKey(inteval)
		frame = firstFrame.copy() # Duplicate into frame the non BB overlay frame

	# Reading the subsequent frames in case of both camera & stored video
	while(cap.isOpened()):
		ret, frame = cap.read()
		if not ret:
			break
		if flag == 1 and selectingObject:
		 	cv2.rectangle(frame,(ix,iy), (cx,cy), (0,255,255), 1)
		elif(initTracking):
			initbb = [ix, iy, w, h]
			cv2.rectangle(frame,(ix,iy), (ix+w,iy+h), (0,255,255), 2)
			tracker.init([ix,iy,w,h], frame)
			initTracking = False
			onTracking = True
		elif(onTracking):
			t0 = time()
			bb = tracker.update(frame)
			t1 = time()
			bb = map(int, bb)
			amplitude = np.sqrt((initbb[0] + initbb[2]/2 - bb[0] - bb[2]/2)**2 + (initbb[1] + initbb[3]/2 - bb[1] - bb[3]/2)**2)
			cv2.rectangle(frame,(bb[0],bb[1]), (bb[0]+bb[2],bb[1]+bb[3]), (0,255,255), 1)

			duration = 0.8*duration + 0.2*(t1-t0)
			#duration = t1-t0
			cv2.putText(frame, 'FPS: '+str(1/duration)[:4].strip('.'), (8,20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2)
			cv2.putText(frame, 'Amplitude: '+ str(amplitude), (8,40), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2)
		cv2.imshow('Tracking', frame)
		# Exit on key 'esc' or 'q'
		c = cv2.waitKey(inteval) & 0xFF
		if c==27 or c==ord('q'):
			break

	cap.release()
	cv2.destroyAllWindows()


import pyblish.api

import openpype.hosts.flame.api as opfapi
from openpype.hosts.flame.otio import flame_export
from openpype.pipeline.create import get_subset_name

class CollecTimelineOTIO(pyblish.api.ContextPlugin):
    """Inject the current working context into publish context"""

    label = "Collect Timeline OTIO"
    order = pyblish.api.CollectorOrder - 0.099

    def process(self, context):
        # plugin defined
        family = "workfile"
        variant = "otioTimeline"

        # main
        asset_doc = context.data["assetEntity"]
        task_name = context.data["task"]
        project = opfapi.get_current_project()
        sequence = opfapi.get_current_sequence(opfapi.CTX.selection)

        # create subset name
        subset_name = get_subset_name(
            family,
            variant,
            task_name,
            asset_doc,
            context.data["projectName"],
            context.data["hostName"],
            project_settings=context.data["project_settings"]
        )

        # adding otio timeline to context
        with opfapi.maintained_segment_selection(sequence) as selected_seg:
            otio_timeline = flame_export.create_otio_timeline(sequence)

            instance_data = {
                "name": subset_name,
                "asset": asset_doc["name"],
                "subset": subset_name,
                "family": "workfile",
                "families": []
            }

            # create instance with workfile
            instance = context.create_instance(**instance_data)
            self.log.info("Creating instance: {}".format(instance))

            # update context with main project attributes
            context.data.update({
                "flameProject": project,
                "flameSequence": sequence,
                "otioTimeline": otio_timeline,
                "currentFile": "Flame/{}/{}".format(
                    project.name, sequence.name
                ),
                "flameSelectedSegments": selected_seg,
                "fps": float(str(sequence.frame_rate)[:-4])
            })


from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

def find_similar_names(garbage_name, name_list, topn=10):
    vectorizer = TfidfVectorizer()
    name_vectors = vectorizer.fit_transform(name_list + [garbage_name])
    similarity_scores = cosine_similarity(name_vectors[:-1], name_vectors[-1])
    similar_names_indices = similarity_scores.argsort(axis=0)[-topn:].flatten()[::-1]
    similar_names = [(name_list[i], similarity_scores[i]) for i in similar_names_indices]
    return similar_names

def find_best_match(predicted_names, first_two_letters):
    best_match = None
    best_score = 0
    for name, score in predicted_names:
        if name[:2].lower() == first_two_letters.lower() and len(name) > 2 and score > best_score:
            best_match = name
            best_score = score
    return best_match

def thisistheway(word):
    # Example usage
    file_path = 'words_alpha.txt'
    garbage_name = word.lower()
    name_list=[]
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            if(len(line.strip())>2 and line.strip()[:2]==garbage_name[:2]):
                name_list.append(line.strip())

    predicted_names = find_similar_names(garbage_name, name_list)

    if predicted_names:
        print(f"Similar names to '{garbage_name}':")
        #for name, score in predicted_names:
        #   print(f"{name} (Similarity Score: {score})")
        print(predicted_names[0][0])
    else:
        print(f"No similar names found for '{garbage_name}'.")


# -*- coding: utf-8 -*-
"""
Created on Wed Feb  1 01:02:25 2023

@author: Yunus
"""
# source: https://towardsdatascience.com/an-end-to-end-project-on-time-series-analysis-and-forecasting-with-python-4835e6bf050b

import warnings
import itertools
import numpy as np
import matplotlib.pyplot as plt

warnings.filterwarnings("ignore")
plt.style.use('fivethirtyeight')

import pandas as pd
import statsmodels.api as sm
import matplotlib

matplotlib.rcParams['axes.labelsize'] = 14
matplotlib.rcParams['xtick.labelsize'] = 12
matplotlib.rcParams['ytick.labelsize'] = 12
matplotlib.rcParams['text.color'] = 'k'

# We are going to do time series analysis and forecasting for furniture sales.

df = pd.read_excel("Superstore.xls")
furniture = df.loc[df['Category'] == 'Furniture']

print (furniture['Order Date'].min())

print (furniture['Order Date'].max())

# Data preprocessing

cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']

# removing columns we do not need
furniture.drop(cols, axis=1, inplace=True)

# sort sales by date
furniture = furniture.sort_values('Order Date')

# check missing values
furniture.isnull().sum()

# group sales by date
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()

# check the last version of the data
print (furniture.head())

# Indexing with time series data
furniture = furniture.set_index('Order Date')
furniture.index

# datetime data is tricky to work with
# So, we will use the averages daily sales for that month
y = furniture['Sales'].resample('MS').mean()

# check 2017 sales data
print (y['2017':])

# Visualizing Furniture Sales Time Series Data
y.plot(figsize=(15, 6))
plt.show()

"""
Some distinguishable patterns appear when we plot the data.
The time-series has seasonality pattern;
sales are always low at the beginning of the year and high at the end of the year.
There is always a strong upward trend within any single year with a couple of low months in the mid of the year.
"""

# We can also visualize our data using a method called time-series decomposition
# that allows us to decompose our time series into three distinct components:
# trend, seasonality, and noise.

from pylab import rcParams
rcParams['figure.figsize'] = 18, 8
decomposition = sm.tsa.seasonal_decompose(y, model='additive')
fig = decomposition.plot()
plt.show()

"""
The plot clearly shows that the sales of furniture is unstable, along with its obvious seasonality.
"""

# Time series forecasting with ARIMA
# ARIMA, which stands for Autoregressive Integrated Moving Average.

# Parameter Selection for the ARIMA Time Series Model
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
print('Examples of parameter combinations for Seasonal ARIMA...')
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[1]))
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[2]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[3]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[4]))

for param in pdq:
    for param_seasonal in seasonal_pdq:
        try:
            mod = sm.tsa.statespace.SARIMAX(y, order=param, seasonal_order=param_seasonal, enforce_stationarity=False, enforce_invertibility=False)
            results = mod.fit()
            print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
        except:
            continue

# Fitting the ARIMA model
mod = sm.tsa.statespace.SARIMAX(y,
                                order=(1, 1, 1),
                                seasonal_order=(1, 1, 0, 12),
                                #enforce_stationarity=False,
                                enforce_invertibility=False)
results = mod.fit()
print(results.summary().tables[1])

# run model diagnostics to investigate any unusual behavior
results.plot_diagnostics(figsize=(16, 8))
plt.show()

# Validating forecasts
"""
To help us understand the accuracy of our forecasts,
we compare predicted sales to real sales of the time series,
and we set forecasts to start at 2017-07-01 to the end of the data.
"""

pred = results.get_prediction(start=pd.to_datetime('2017-01-01'), dynamic=False)
pred_ci = pred.conf_int()

ax = y['2014':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead Forecast', alpha=.7, figsize=(14, 7))

ax.fill_between(pred_ci.index,
                pred_ci.iloc[:, 0],
                pred_ci.iloc[:, 1], color='k', alpha=.2)

ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()
plt.show()

"""
The line plot is showing the observed values compared to the rolling forecast predictions.
Overall, our forecasts align with the true values very well, showing an upward trend starts from the beginning of the year.
"""

y_forecasted = pred.predicted_mean
y_truth = y['2017-01-01':]

# Compute the mean square error
mse = ((y_forecasted - y_truth) ** 2).mean()
print('The Mean Squared Error of our forecasts is {}'.format(round(mse, 2)))
# The Mean Squared Error of our forecasts is 22993.58

print('The Root Mean Squared Error of our forecasts is {}'.format(round(np.sqrt(mse), 2)))
# The Root Mean Squared Error of our forecasts is 151.64

"""
In statistics, the mean squared error (MSE) of an estimator measures the average of the squares of the errors —
that is, the average squared difference between the estimated values and what is estimated.
The MSE is a measure of the quality of an estimator—it is always non-negative,
and the smaller the MSE, the closer we are to finding the line of best fit.

Root Mean Square Error (RMSE) tells us that our model was able to forecast the average daily furniture sales in the test set within 151.64 of the real sales.
Our furniture daily sales range from around 400 to over 1200. In my opinion, this is a pretty good model so far.
"""

# Producing and visualizing forecasts
pred_uc = results.get_forecast(steps=100)
pred_ci = pred_uc.conf_int()

ax = y.plot(label='observed', figsize=(14, 7))
pred_uc.predicted_mean.plot(ax=ax, label='Forecast')
ax.fill_between(pred_ci.index,
                pred_ci.iloc[:, 0],
                pred_ci.iloc[:, 1], color='k', alpha=.25)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')

plt.legend()
plt.show()

"""
Our model clearly captured furniture sales seasonality.
As we forecast further out into the future, it is natural for us to become less confident in our values.
This is reflected by the confidence intervals generated by our model, which grow larger as we move further out into the future.
"""

# Time Series of Furniture vs. Office Supplies
# Data preprocessing
furniture = df.loc[df['Category'] == 'Furniture']
office = df.loc[df['Category'] == 'Office Supplies']

furniture.shape, office.shape

cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
furniture.drop(cols, axis=1, inplace=True)
office.drop(cols, axis=1, inplace=True)

furniture = furniture.sort_values('Order Date')
office = office.sort_values('Order Date')

furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
office = office.groupby('Order Date')['Sales'].sum().reset_index()

# have a quick peek
furniture.head()
office.head()

# Data Exploration
furniture = furniture.set_index('Order Date')
office = office.set_index('Order Date')

y_furniture = furniture['Sales'].resample('MS').mean()
y_office = office['Sales'].resample('MS').mean()

furniture = pd.DataFrame({'Order Date':y_furniture.index, 'Sales':y_furniture.values})
office = pd.DataFrame({'Order Date': y_office.index, 'Sales': y_office.values})

store = furniture.merge(office, how='inner', on='Order Date')
store.rename(columns={'Sales_x': 'furniture_sales', 'Sales_y': 'office_sales'}, inplace=True)
store.head()

plt.figure(figsize=(20, 8))
plt.plot(store['Order Date'], store['furniture_sales'], 'b-', label = 'furniture')
plt.plot(store['Order Date'], store['office_sales'], 'r-', label = 'office supplies')
plt.xlabel('Date'); plt.ylabel('Sales'); plt.title('Sales of Furniture and Office Supplies')
plt.legend();

"""
We observe that sales of furniture and office supplies shared a similar seasonal pattern.
Early of the year is the off season for both of the two categories.
It seems summer time is quiet for office supplies too.
In addition, average daily sales for furniture are higher than those of office supplies in most of the months.
It is understandable, as the value of furniture should be much higher than those of office supplies.
Occationaly, office supplies passed furnitue on average daily sales.
"""

# Let's find out when was the first time office supplies' sales surpassed those of furniture's.

first_date = store.loc[np.min(list(np.where(store['office_sales'] > store['furniture_sales'])[0])), 'Order Date']
print("Office supplies first time produced higher sales than furniture is {}.".format(first_date.date()))

# Time Series Modeling with Prophet
# conda install -c conda-forge fbprophet -y
# pip install --upgrade plotly
from fbprophet import Prophet

furniture = furniture.rename(columns={'Order Date': 'ds', 'Sales': 'y'})
furniture_model = Prophet(interval_width=0.95)
furniture_model.fit(furniture)

office = office.rename(columns={'Order Date': 'ds', 'Sales': 'y'})
office_model = Prophet(interval_width=0.95)
office_model.fit(office)

furniture_forecast = furniture_model.make_future_dataframe(periods=36, freq='MS')
furniture_forecast = furniture_model.predict(furniture_forecast)

office_forecast = office_model.make_future_dataframe(periods=36, freq='MS')
office_forecast = office_model.predict(office_forecast)

plt.figure(figsize=(18, 6))
furniture_model.plot(furniture_forecast, xlabel = 'Date', ylabel = 'Sales')
plt.title('Furniture Sales');

plt.figure(figsize=(18, 6))
office_model.plot(office_forecast, xlabel = 'Date', ylabel = 'Sales')
plt.title('Office Supplies Sales');

# Compare forecasts
furniture_names = ['furniture_%s' % column for column in furniture_forecast.columns]
office_names = ['office_%s' % column for column in office_forecast.columns]

merge_furniture_forecast = furniture_forecast.copy()
merge_office_forecast = office_forecast.copy()

merge_furniture_forecast.columns = furniture_names
merge_office_forecast.columns = office_names

forecast = pd.merge(merge_furniture_forecast, merge_office_forecast, how = 'inner', left_on = 'furniture_ds', right_on = 'office_ds')

forecast = forecast.rename(columns={'furniture_ds': 'Date'}).drop('office_ds', axis=1)
forecast.head()

# Trend and Forecast Visualization
plt.figure(figsize=(10, 7))
plt.plot(forecast['Date'], forecast['furniture_trend'], 'b-')
plt.plot(forecast['Date'], forecast['office_trend'], 'r-')
plt.legend(); plt.xlabel('Date'); plt.ylabel('Sales')
plt.title('Furniture vs. Office Supplies Sales Trend');

plt.figure(figsize=(10, 7))
plt.plot(forecast['Date'], forecast['furniture_yhat'], 'b-')
plt.plot(forecast['Date'], forecast['office_yhat'], 'r-')
plt.legend(); plt.xlabel('Date'); plt.ylabel('Sales')
plt.title('Furniture vs. Office Supplies Estimate');

# Trends and patterns
furniture_model.plot_components(furniture_forecast);
office_model.plot_components(office_forecast);

"""
Good to see that the sales for both furniture and office supplies have been linearly increasing over time although office supplies' growth seems slightly stronger.

The worst month for furniture is April, the worst month for office supplies is February. The best month for furniture is December, and the best month for office supplies is November.

There are many time-series analysis we can explore from now on, such as forecast with uncertainty bounds, change point and anomaly detection, forecast time-series with external data source. We have only scratched the surface here. Stay tuned for future works on time-series analysis.
"""


word_list = ["abruptly", "absurd", "abyss",
             "frizzled", "funny", "galaxy",
             "mnemonic", "matrix", "luxury",
             "syndrome", "subway", "mystify",
             "gossip", "nymph", "ovary",
             "voodoo", "waxy", "puzzling",
             "buzzwords", "jiujitsu", "haphazard",
             "squawk", "zipper", "zombie",
             "fixable", "razzmatazz", "disavow",
             "joking", "rhythm", "khaki",
             "sphinx", "knapsack", "fluffiness",
             "peekaboo", "voyeurism", "wellspring"]


#!/usr/bin/python

from sorts import *
from time import time
import tracemalloc

def radix_sort(c, n, array):
    start = time()
    helper = array.copy()
    k = max(array)
    pwr = 1
    while k > 0:
        counting_sort(c, n, array, pwr)
        pwr *= 10
        k //= 10
    end = time() - start
    if c == "<=":
        check_asc(array)
    else:
        check_desc(array)
    num = get_num(helper, array)
    print("Posortowana tablica: {}\nLiczba posortowanych elementow: {}\nCzas: {}".format(array, num, end))

def radix_sort_stat(c, n, array, filename):
    tracemalloc.start()
    start = time()
    k = max(array)
    pwr = 1
    while k > 0:
        counting_sort(c, n, array, pwr)
        pwr *= 10
        k //= 10
    end = time() - start
    peak = tracemalloc.get_traced_memory()[1]
    tracemalloc.stop()
    if c == "<=":
        check_asc(array)
    else:
        check_desc(array)
    with open(filename, "a") as f:
        f.write("{};{};{}\n".format(n, end, peak/10**6))

def counting_sort(c, n, array, pwr):
    helper = array.copy()
    counter = [0 for _ in range(10)]
    for num in helper:
        counter[(num//pwr)%10] += 1
    if c == "<=":
        for i in range(1, 10):
            counter[i] += counter[i-1]
    else:
        for i in range(8, -1, -1):
            counter[i] += counter[i+1]
    for i in range(n-1, -1, -1):
        index = counter[(helper[i]//pwr)%10]-1
        array[index] = helper[i]
        counter[(helper[i]//pwr)%10] -= 1


from os import path
import re

import telebot
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton,\
ReplyKeyboardMarkup, KeyboardButton

from bot_config import CONFIG
from utils import *
from classes import Sound, Album, Author

sound = None
album = None
author = None

bot = telebot.TeleBot(CONFIG.get('TOKEN'))

keyboard = ReplyKeyboardMarkup()
keyboard.add(
    KeyboardButton('/start')
)

@bot.message_handler(commands=['start'])
def handle_start(message):
    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Search sound', callback_data='search_sound')
    )
    keyboard.add(
        InlineKeyboardButton('Get 10 random sounds', callback_data='rand_sounds')
    )

    keyboard.add(
        InlineKeyboardButton('Search author', callback_data='search_author')
    )
    keyboard.add(
        InlineKeyboardButton('Search album', callback_data='search_album')
    )

    keyboard.add(
        InlineKeyboardButton('Wanna upload something?', callback_data='upload_request')
    )
    keyboard.add(
        InlineKeyboardButton('About', callback_data='about')
    )

    bot.send_message(
        message.chat.id,
        f'Welcome',
        reply_markup=keyboard
    )

@bot.callback_query_handler(func=lambda cb: 'about' == cb.data)
def handle_about(query):
    bot.send_message(
        query.message.chat.id,
        'This bot allows you to store music in telegram.\nBot created by rogulik, github is https://github.com/rogulikAFC/sound-storage-bot-DRF-telebot'
    )

@bot.callback_query_handler(func=lambda cb: 'search_sound' == cb.data)
def handle_search_sound_cb(query):
    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Back', callback_data='back')    # Not working
    )

    message = bot.send_message(
        query.message.chat.id,
        'Say me name of sound',
        reply_markup=keyboard
    )

    bot.register_next_step_handler(message, process_sound_title_step)

def process_sound_title_step(message):
    # keyboard = InlineKeyboardMarkup()

    # keyboard.add(
    #     InlineKeyboardButton(
    #         'It isn\'t. Show me next 10, please', callback/start_data=f'send_sounds_list_{search_page + 1}'
    #     )
    # )
    # keyboard.add(
    #     InlineKeyboardButton('Back', callback_data='back')    # Not working
    # )

    for sound in Sound.search_sounds(message.text, ):  # pagination and filters needed
        id = sound.get('id')
        title = sound.get('title')
        authors = ', '.join(author.get('title') for author in sound.get('authors'))

        sound_keyboard = InlineKeyboardMarkup()
        sound_keyboard.add(
            InlineKeyboardButton('That\'s it!', callback_data=f'sound_detail_{id}')
        )

        bot.send_message(
            message.chat.id,
            f'{title} - {authors}',
            reply_markup=sound_keyboard
        )

    bot.send_message(
        message.chat.id,
        'That\'s all',
    )

@bot.callback_query_handler(func=lambda cb: 'sound_detail_' in cb.data)
def sound_detail(query):
    id = query.data.split('_')[-1]
    sound = Sound.get_sound_info(id)

    cover_url = sound.get('cover_url')
    cover = get_cover(cover_url)

    audio_url = sound.get('sound_url')
    audio = Sound.get_sound_file(audio_url)

    authors = ', '.join(author.get('title') for author in sound.get('authors'))

    bot.send_photo(
        query.message.chat.id,
        ('new_image.jpg', cover),
        f'{sound.get("title")} - {authors}'
    )
    bot.send_audio(
        query.message.chat.id,
        audio,
        title=f'{sound.get("title")} - {authors}'
    )

@bot.callback_query_handler(func=lambda cb: 'rand_sounds' == cb.data)
def handle_random_sounds(query):    # need to create endpoint
    for sound in get_random_sounds():
        id = sound.get('id')
        title = sound.get('title')
        authors = ', '.join(author.get('title') for author in sound.get('authors'))

        sound_keyboard = InlineKeyboardMarkup()
        sound_keyboard.add(
            InlineKeyboardButton('Listen to it!', callback_data=f'sound_detail_{id}')
        )

        bot.send_message(
            query.message.chat.id,
            f'{title} - {authors}',
            reply_markup=sound_keyboard
        )

    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Next 10', callback_data='rand_sound')
    )
    keyboard.add(
        InlineKeyboardButton('Back', callback_data='back')
    )

    bot.send_message(
        query.message.chat.id,
        'That\'s all',
        reply_markup=keyboard
    )

@bot.callback_query_handler(func=lambda cb: 'upload_request' == cb.data)
def handle_upload_request(query):
    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Sound', callback_data='upload_sound')
    )
    keyboard.add(
        InlineKeyboardButton('Group album from sounds', callback_data='group_album')
    )
    keyboard.add(
        InlineKeyboardButton('Create new author', callback_data='create_author')
    )
    keyboard.add(
        InlineKeyboardButton('Back', callback_data='back')
    )

    bot.send_message(
        query.message.chat.id,
        'What do you want to upload?',
        reply_markup=keyboard
    )

@bot.callback_query_handler(func=lambda cb: 'upload_sound' == cb.data)
def handle_upload_sound(query):
    message = bot.send_message(
        query.message.chat.id,
        'Write sound name'
    )

    bot.register_next_step_handler(message, process_upload_sound_title_step)

def process_upload_sound_title_step(message):
    global sound
    sound = Sound(message.text)

    msg = bot.send_message(
        message.chat.id,
        'Upload cover image'
    )

    bot.register_next_step_handler(msg, process_upload_sound_cover_step)

def process_upload_sound_cover_step(message):
    global sound

    cover_image_info = None

    try:
        cover_image_info = bot.get_file(message.photo[0].file_id)

    except:
        msg = bot.send_message(
            message.chat.id,
            'You have uploaded not png or jpg file, upload png or jpg file'
        )
        bot.register_next_step_handler(msg, process_upload_sound_cover_step)

    extension_pattern = r'\.[^.]+$'

    extension = re.search(
        extension_pattern, cover_image_info.file_path
    ).group(0)

    file = bot.download_file(cover_image_info.file_path)

    with open(f'new_image{extension}', 'wb') as new_file:
        new_file.write(file)

    file = open(f'new_image{extension}', 'rb')
    sound.cover = file.read()

    msg = bot.send_message(
        message.chat.id,
        'Upload sound file'
    )

    bot.register_next_step_handler(msg, process_upload_sound_file_and_authors_step)

def process_upload_sound_file_and_authors_step(message):
    global sound

    sound_file_info = None

    try:
        sound_file_info = bot.get_file(message.audio.file_id)

    except:
        msg = bot.send_message(
            message.chat.id,
            'You have uploaded not mp3 file, upload mp3 file'
        )
        bot.register_next_step_handler(msg, process_upload_sound_file_and_authors_step)
        return

    file = bot.download_file(sound_file_info.file_path)

    with open('new_file.mp3', 'wb') as new_file:
        new_file.write(file)

    file = open('new_file.mp3', 'rb')
    sound.sound = file.read()

    msg = bot.send_message(
        message.chat.id,
        'Ok, now select authors'
    )

    for author in get_authors_by_title(message.text):
        id = author.get('id')
        title = author.get('title')

        author_keyboard = InlineKeyboardMarkup()
        author_keyboard.add(
            InlineKeyboardButton('That\'s it!', callback_data=f'select_sound_author_{id}')
        )

        bot.send_message(
            message.chat.id,
            title,
            reply_markup=author_keyboard
        )

    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Yes, upload sound to database', callback_data='upload_sound_to_db')
    )
    keyboard.add(
        InlineKeyboardButton('No, exit', callback_data='upload_sound')
    )

    bot.send_message(
        message.chat.id,
        'Upload sound?',
        reply_markup=keyboard
    )

@bot.callback_query_handler(func=lambda cb: cb.data.startswith('select_sound_author_'))
def add_author_to_sound(query):
    id = query.data.split('_')[-1]

    try:
        sound.authors.append(id)

    except:
        bot.send_message(
            query.message.chat.id,
            'Ooops... Somthing went wrong. This error may occur, when you already selected all authors'
        )

    print(sound.title, sound.authors)

@bot.callback_query_handler(func=lambda cb: 'upload_sound_to_db' == cb.data)
def upload_sound_to_db(query):
    global sound

    if sound.upload_to_database():
        bot.send_message(
            query.message.chat.id,
            'Uploaded!'
        )

    else:
        bot.send_message(
            query.message.chat.id,
            'Something went wrong, file not uploaded'
        )

    sound = None

@bot.callback_query_handler(func=lambda cb: 'search_album' == cb.data)
def search_album(query):
    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Back', callback_data='back')    # Not working
    )

    message = bot.send_message(
        query.message.chat.id,
        'Say me name of album',
        reply_markup=keyboard
    )

    bot.register_next_step_handler(message, process_album_title_step)

def process_album_title_step(message):
    for album in Album.search_albums(message.text):
        id = album.get('id')
        title = album.get('title')
        authors = ', '.join(author.get('title') for author in album.get('authors'))

        album_keyboard = InlineKeyboardMarkup()
        album_keyboard.add(
            InlineKeyboardButton('That\'s it!', callback_data=f'album_detail_{id}')
        )

        bot.send_message(
            message.chat.id,
            f'{title} - {authors}',
            reply_markup=album_keyboard
        )

    bot.send_message(
        message.chat.id,
        'That\'s all',
    )

@bot.callback_query_handler(func=lambda cb: 'album_detail_' in cb.data)
def album_detail(query):
    id = query.data.split('_')[-1]
    album_info = Album.get_album_info(id)

    authors = ', '.join(author.get('title') for author in album_info.get('authors'))
    title = album_info.get('title')

    cover = get_cover(album_info.get('cover_url'))

    bot.send_photo(
        query.message.chat.id,
        ('new_image.jpg', cover),
        f'{title} - {authors}'
    )

    sounds = album_info.get('sounds')

    for sound in sounds:
        id = sound.get('id')
        title = sound.get('title')
        authors = ', '.join(author.get('title') for author in sound.get('authors'))

        sound_keyboard = InlineKeyboardMarkup()
        sound_keyboard.add(
            InlineKeyboardButton('Listen it', callback_data=f'sound_detail_{id}')
        )

        bot.send_message(
            query.message.chat.id,
            f'{title} - {authors}',
            reply_markup=sound_keyboard
        )

@bot.callback_query_handler(func=lambda cb: 'group_album' == cb.data)
def group_album(query):
    msg = bot.send_message(
        query.message.chat.id,
        'Write album title'
    )

    bot.register_next_step_handler(msg, group_album_title_step)

def group_album_title_step(message):
    global album
    album = Album(message.text)
    msg = bot.send_message(
        message.chat.id,
        'Upload album cover'
    )

    bot.register_next_step_handler(msg, group_album_cover_step)

def group_album_cover_step(message):
    cover_image_info = None

    try:
        cover_image_info = bot.get_file(message.photo[0].file_id)

    except:
        msg = bot.send_message(
            message.chat.id,
            'You have uploaded not png or jpg file, upload png or jpg file'
        )
        bot.register_next_step_handler(msg, group_album_cover_step)

    extension_pattern = r'\.[^.]+$'

    extension = re.search(
        extension_pattern, cover_image_info.file_path
    ).group(0)

    file = bot.download_file(cover_image_info.file_path)

    with open(f'new_image{extension}', 'wb') as new_file:
        new_file.write(file)

    file = open(f'new_image{extension}', 'rb')
    album.cover = file.read()

    bot.send_message(
        message.chat.id,
        'Select sounds'
    )

    for sound in get_sounds():
        id = sound.get('id')
        title = sound.get('title')
        authors = ', '.join(author.get('title') for author in sound.get('authors'))

        sound_keyboard = InlineKeyboardMarkup()
        sound_keyboard.add(
            InlineKeyboardButton('That\'s it!', callback_data=f'select_album_sound_{id}')
        )

        bot.send_message(
            message.chat.id,
            f'{title} - {authors}',
            reply_markup=sound_keyboard
        )

    keyboard = InlineKeyboardMarkup()
    keyboard.add(
        InlineKeyboardButton('Yes, upload album to database', callback_data='upload_album_to_db')
    )
    keyboard.add(
        InlineKeyboardButton('No, exit', callback_data='group_album')
    )

    bot.send_message(
        message.chat.id,
        'Upload album to database?',
        reply_markup=keyboard
    )

@bot.callback_query_handler(func=lambda cb: 'select_album_sound_' in cb.data)
def select_album_sound(query):
    global album

    id = query.data.split('_')[-1]

    try:
        album.sounds.append(id)

    except:
        bot.send_message(
            query.message.chat.id,
            'Ooops... Somthing went wrong. This error may occur, when you already selected all sounds'
        )

    print(album.title, album.sounds)

@bot.callback_query_handler(func=lambda cb: 'upload_album_to_db' == cb.data)
def upload_album_to_db(query):
    global album

    if album.upload_to_database():
        bot.send_message(
            query.message.chat.id,
            'Uploaded!'
        )

    else:
        bot.send_message(
            query.message.chat.id,
            'Something went wrong, album not uploaded'
        )

    album = None

@bot.callback_query_handler(func=lambda cb: 'create_author' == cb.data)
def upload_author(query):
    msg = bot.send_message(
        query.message.chat.id,
        'Write author title'
    )

    bot.register_next_step_handler(msg, upload_author_title)

def upload_author_title(message):
    global author
    author = Author(message.text)
    msg = bot.send_message(
        message.chat.id,
        'Upload author description'
    )

    bot.register_next_step_handler(msg, upload_author_description)

def upload_author_description(message):
    global author
    author.description = message.text

    if author.upload_to_database():
        bot.send_message(
            message.chat.id,
            'Uploaded!'
        )

    else:
        bot.send_message(
            message.chat.id,
            'Something went wrong, author not created'
        )

@bot.callback_query_handler(func=lambda cb: 'search_author' == cb.data)
def search_author(query):
    msg = bot.send_message(
        query.message.chat.id,
        'Write author title',
    )

    bot.register_next_step_handler(msg, search_author_title_step)

def search_author_title_step(message):
    for author in Author.search_authors(message.text):
        id = author.get('id')
        title = author.get('title')

        author_keyboard = InlineKeyboardMarkup()
        author_keyboard.add(
            InlineKeyboardButton(
                'View this author',
                callback_data=f'author_detail_{id}'
            )
        )

        bot.send_message(
            message.chat.id,
            title,
            reply_markup=author_keyboard
        )

    bot.send_message(
        message.chat.id,
        'That\'s all'
    )

@bot.callback_query_handler(func=lambda cb: 'author_detail_' in cb.data)
def author_detail(query):
    author_id = query.data.split('_')[-1]
    author = Author.get_author_info(author_id)

    bot.send_message(
        query.message.chat.id,
        f'''
        <b> {author.get("title")} </b> \n\n{author.get("description")}
        ''',
        parse_mode='HTML'
    )

    bot.send_message(
        query.message.chat.id,
        '<b> Sounds: </b>',
        parse_mode='HTML'
    )

    for sound in author.get('sounds'):
        id = sound.get('id')
        title = sound.get('title')

        sound_keyboard = InlineKeyboardMarkup()
        sound_keyboard.add(
            InlineKeyboardButton(
                'Listen it', callback_data=f'sound_detail_{id}'
            )
        )

        bot.send_message(
            query.message.chat.id,
            f'{title}',
            reply_markup=sound_keyboard
        )

    bot.send_message(
        query.message.chat.id,
        '<b> Albums: </b>',
        parse_mode='HTML'
    )

    for album in author.get('albums'):
        id = album.get('id')
        title = album.get('title')
        authors = ', '.join(author.get('title') for author in album.get('authors'))

        album_keyboard = InlineKeyboardMarkup()
        album_keyboard.add(
            InlineKeyboardButton(
                'View', callback_data=f'album_detail_{id}'
            )
        )

        bot.send_message(
            query.message.chat.id,
            f'{title} - {authors}',
            reply_markup=album_keyboard
        )

@bot.callback_query_handler(func=lambda cb: 'back' == cb.data)
def back(query):
    handle_start(query.message)

bot.infinity_polling()


# Line intersection technique
# https://stackoverflow.com/questions/3838329/how-can-i-check-if-two-segments-intersect

def side(a,b,c):
    """ Returns a position of the point c relative to the line going through a and b
        Points a, b are expected to be different
    """
    d = (c[1]-a[1])*(b[0]-a[0]) - (b[1]-a[1])*(c[0]-a[0])
    return 1 if d > 0 else (-1 if d < 0 else 0)

def is_point_in_closed_segment(a, b, c):
    """ Returns True if c is inside closed segment, False otherwise.
        a, b, c are expected to be collinear
    """
    if a[0] < b[0]:
        return a[0] <= c[0] and c[0] <= b[0]
    if b[0] < a[0]:
        return b[0] <= c[0] and c[0] <= a[0]

    if a[1] < b[1]:
        return a[1] <= c[1] and c[1] <= b[1]
    if b[1] < a[1]:
        return b[1] <= c[1] and c[1] <= a[1]

    return a[0] == c[0] and a[1] == c[1]

def closed_segment_intersect(a,b,c,d):
    """ Verifies if closed segments a, b, c, d do intersect.
    """
    if a == b:
        return a == c or a == d
    if c == d:
        return c == a or c == b

    s1 = side(a,b,c)
    s2 = side(a,b,d)

    # All points are collinear
    if s1 == 0 and s2 == 0:
        return \
            is_point_in_closed_segment(a, b, c) or is_point_in_closed_segment(a, b, d) or \
            is_point_in_closed_segment(c, d, a) or is_point_in_closed_segment(c, d, b)

    # No touching and on the same side
    if s1 and s1 == s2:
        return False

    s1 = side(c,d,a)
    s2 = side(c,d,b)

    # No touching and on the same side
    if s1 and s1 == s2:
        return False

    return True


from django import forms
from django.forms import ModelForm

from schedules.models import Schedule

class TimePickerInput(forms.TimeInput):
    input_type = 'time'

class DatePickerInput(forms.TimeInput):
    input_type = 'date'

class ScheduleCreateForm(ModelForm):
    class Meta:
        model = Schedule
        fields = ['date','start_time', 'end_time', 'is_available']

        widgets = {
            'date' : DatePickerInput(),
            'start_time' : TimePickerInput(),
            'end_time': TimePickerInput(),
        }

class ScheduleChangeForm(ModelForm):
    class Meta:
        model = Schedule
        fields = ['date','start_time', 'end_time', 'is_available']

        widgets = {
            # 'date' : DatePickerInput(),
            'start_time' : TimePickerInput(),
            'end_time': TimePickerInput(),
        }


from django.test import Client, TestCase
from django.core.urlresolvers import reverse

from smyt.forms import FORM_MAP
from smyt.models import MODEL_MAP, get_field_type
from smyt.views import get_items

import json
from datetime import date

class SimpleTest(TestCase):
    fixtures = ['smyt_data.json']
    conv = lambda self, x: json.loads(x.decode('utf-8'))
    data_map = {
        'int': 12345,
        'char': 'Огурец солёный',
        'date': date.today().strftime('%Y-%m-%d')
    }

    def setUp(self):
        self.client = Client()

    def test_details(self):
        response = self.client.get(reverse('dashboard'))
        self.assertEqual(response.status_code, 200)

    def test_view_items(self):
        for k, v in MODEL_MAP.items():
            response = self.client.get(reverse('view-items', args=(k,)))
            json_data = self.conv(response.content)
            self.assertEqual(json_data['ok'], 1)
            self.assertEqual(json_data['model'], k)

    def test_add_item(self):
        for k, v in FORM_MAP.items():
            data = {}
            for f in v().visible_fields():
                data[f.name] = ''
            response = self.client.post(reverse('add-item', args=(k,)), data)
            json_data = self.conv(response.content)
            self.assertEqual(json_data['ok'], 1)
            self.assertEqual('errs' in json_data, True)

            for f in v().visible_fields():
                data[f.name] = self.data_map[get_field_type(k, f.name)]
            response = self.client.post(reverse('add-item', args=(k,)), data)
            json_data = self.conv(response.content)
            self.assertEqual(json_data['ok'], 1)
            for v in data.values():
                self.assertEqual(v in json_data['item'][1], True)

    def test_update_item(self):
        for k, v in FORM_MAP.items():
            obj = MODEL_MAP[k].objects.get(pk=1)
            for f in v().visible_fields():
                data = {
                    'fname': f.name,
                    f.name: ''
                }
                response = self.client.post(reverse('update-item', args=(k, obj.id)), data)
                json_data = self.conv(response.content)
                self.assertEqual(json_data['ok'], 1)
                self.assertEqual('errs' in json_data, True)

                data = {
                    'fname': f.name,
                    f.name: self.data_map[get_field_type(k, f.name)]
                }
                response = self.client.post(reverse('update-item', args=(k, obj.id)), data)
                json_data = self.conv(response.content)
                self.assertEqual(json_data['ok'], 1)
                self.assertEqual(json_data['value'], str(data[f.name]))


import tensorflow as tf
#from tensorflow.math import exp, maimum
from tensorflow._api.v2.math import exp, maximum
from tensorflow.keras.layers import Activation

x = tf.random.normal(shape=(1,5))

sigmoid = Activation('sigmoid')
tanh = Activation('tanh')
relu = Activation('relu')

y_sigmoid_tf = sigmoid(x)
y_tanh_tf = tanh(x)
y_relu_tf = relu(x)

print('Sigmoid : {}\n{}'.format(y_sigmoid_tf, y_sigmoid_tf.numpy()))
print('tanh : {}\n{}'.format(y_tanh_tf, y_tanh_tf.numpy()))
print('relu : {}\n{}'.format(y_relu_tf, y_relu_tf.numpy()))

y_sigmoid_man = 1 / (1+exp(-x)) # sigmoid
y_tanh_man = (exp(x)-exp(x))/(exp(x) + exp(-x))  # tanh
y_relu_man = maximum(x,0)