from threading import Thread
import json
import re
from uuid import uuid1, uuid4
from collections import deque
import os
from telegram.ext import (Updater,
                          CommandHandler,
                          CallbackQueryHandler,
                          MessageHandler,
                          Filters,
                          CallbackContext,
                          ConversationHandler)
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Bot, TelegramError, ChatAction
from telegram.error import BadRequest
import logging
from datetime import datetime, timedelta
import shutil
from urllib.parse import urlparse
from os.path import splitext, basename
# Menu
from .utils import build_menu, check_key_id, isAdmin, filter_channel, restricted, rtype, zip_record, save_config, register

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)

BETA = False
START = 'start'
STOP = 'stop'

class Downloader(Thread):

    def __init__(self, newFile, path, file_name):
        super().__init__()
        self.newFile = newFile
        self.path = path
        self.file_name = file_name
        # https://www.bogotobogo.com/python/Multithread/python_multithreading_Daemon_join_method_threads.php
        self.daemon = True

    def run(self):
        logger.info(f"Downloading... {self.file_name}")
        self.newFile.download(self.path)
        logger.info(f"Downloaded {self.file_name}")

class Autoreply:

    def __init__(self, updater, context, chat_id, type, text, func_timeout, data, time=60, interval=10, keyID=None):
        # Usually th e keyID is automatically generated.
        # Do not use a keyID in a job timer message
        if keyID is None:
            # Generate ID and seperate value from command
            keyID = str(uuid4())
            # Set the user data
            context.user_data[keyID] = {'chat_id': chat_id}
        # set total time
        self.time = time
        # Interval update message
        self.interval = interval
        # Make control buttons
        msg = self.ctrl_buttons(context.bot, chat_id, keyID, type, text)
        # Initialize the context to share
        data_timer = {'timer': self.time,
                      'message': msg.message_id,
                      'chat_id': chat_id,
                      'keyID': keyID,
                      'text': text,
                      'func_timeout': func_timeout,
                      'data': data,
                      'type_cb': type}
        # add job in recording dictionary
        self.job = updater.job_queue.run_repeating(self.timer_autoreply, context=data_timer, interval=self.interval, first=self.interval)

    def stop(self):
        # Stop the autoreply timer
        self.job.enabled = False  # Temporarily disable this job
        self.job.schedule_removal()  # Remove this job completely

    def timer_autoreply(self, context: CallbackContext):
        # Get job data
        job = context.job
        data_timer = context.job.context
        # Extract all context parameter
        timer = data_timer['timer']
        message_id = data_timer['message']
        chat_id = data_timer['chat_id']
        keyID = data_timer['keyID']
        text = data_timer['text']
        type_cb = data_timer['type_cb']
        user = context.bot.get_me()
        # Update control buttons and text
        self.ctrl_buttons(context.bot, chat_id, keyID, type_cb, f"{text} _({timer}s left)_", message_id)
        # Decrease timer
        context.job.context['timer'] -= self.interval
        # If timer left remove timer and send a message
        if timer <= 0:
            job.schedule_removal()
            # Send the message
            func_timeout = data_timer['func_timeout']
            data = [type_cb, keyID, data_timer['data']]
            func_timeout(context.bot, message_id, chat_id, data, user)

    def ctrl_buttons(self, bot, chat_id, keyID, type_cb, message, edit_msg=None):
        yes = InlineKeyboardButton("✅", callback_data=f"{type_cb} {keyID} true")
        no = InlineKeyboardButton("❌", callback_data=f"{type_cb} {keyID} false")
        reply_markup = InlineKeyboardMarkup(build_menu([yes, no], 2))
        if edit_msg is not None:
            bot.edit_message_text(chat_id=chat_id,
                                          message_id=edit_msg,
                                          text=message,
                                          parse_mode='Markdown',
                                          reply_markup=reply_markup)
            return None
        else:
            return bot.send_message(chat_id=chat_id,
                                           text=message,
                                           parse_mode='Markdown',
                                           reply_markup=reply_markup)

RECORDS_FILE = "records.json"
IDLE, WAIT_START, WAIT_STOP, WRITING = range(4)
RECORDING = 'RECORDING'
MSG_TEXT_ORDER = ['date', 'user_id', 'firstname', 'msg_id', 'edit', 'reply_id', 'forward_from', 'text']

def make_dict_message(update, text, edit=False):
    # Message ID
    msg_id = update.message.message_id
    # date message
    date = update.message.date
    # User ID
    user_id = update.message.from_user.id
    # Username
    username = update.message.from_user.username
    # Name
    firstname = update.message.from_user.first_name
    # Reply id
    reply_id = update.message.reply_to_message.message_id if update.message.reply_to_message else ''
    # Reply id
    forward_from = update.message.forward_from.id if update.message.forward_from else ''
    # Make message
    msg = {'msg_id': msg_id,
            'date': date,
            'user_id': user_id,
            'firstname': firstname,
            'username': username,
            'text': text,
            'reply_id': reply_id,
            'forward_from': forward_from,
            'edit': edit}
    return msg

class Record:

    def __init__(self, updater, settings, settings_file, channels):
        self.updater = updater
        self.settings_file = settings_file
        self.settings = settings
        self.channels = channels
        # Timeout autostop
        self.timeout = 10 * 60
        self.extension = "csv"
        self.size_record_chat = 10
        self.min_delta = 10
        self.d_start = 10 * 60
        self.separator = "\t"
        # Recording status
        self.recording = {}
        # Initialize folder records
        records = self.settings['config'].get('records', {})
        self.records_folder = records.get('folder', 'records')
        if not os.path.isdir(self.records_folder):
            os.mkdir(self.records_folder)
            logger.info(f"Directory {self.records_folder} created")
        # Restore all conversations
        size_record_chat = int(records.get('msgs', self.size_record_chat))
        if os.path.isfile(f"{self.records_folder}/{RECORDS_FILE}"):
            with open(f"{self.records_folder}/{RECORDS_FILE}") as stream:
                data = json.load(stream)
            for chat_id in data:
                logger.info(f"Load {chat_id} records")
                status = data[chat_id]['status']
                # Convert all timestamps in datetime
                for msg in data[chat_id]['msgs']:
                    msg['date'] = datetime.fromtimestamp(msg['date'])
                    # Manage
                    msg['edit'] = False if 'edit' not in msg else msg['edit']
                # Restore all recording
                self.recording[int(chat_id)] = {'status': status, 'msgs': deque(data[chat_id]['msgs'], maxlen=size_record_chat)}
        # Job queue
        self.job = self.updater.job_queue
        # Get the dispatcher to register handlers
        dp = self.updater.dispatcher
        # Text recorder
        text_handler = MessageHandler(Filters.text, self.record)
        dp.add_handler(text_handler)
        photo_handler = MessageHandler(Filters.photo, self.record_photo)
        dp.add_handler(photo_handler)
        document_handler = MessageHandler(Filters.document, self.record_document)
        dp.add_handler(document_handler)
        voice_handler = MessageHandler(Filters.voice, self.record_voice)
        dp.add_handler(voice_handler)
        # Query player
        dp.add_handler(CommandHandler('player', self.player))
        dp.add_handler(CallbackQueryHandler(self.player_control, pattern='REC_PLAYER'))
        # Query messages
        dp.add_handler(CommandHandler('records', self.records))
        dp.add_handler(CallbackQueryHandler(self.rec_folder, pattern='REC_DATA'))
        dp.add_handler(CallbackQueryHandler(self.rec_download, pattern='REC_DOWNLOAD'))
        dp.add_handler(CallbackQueryHandler(self.rec_cancel, pattern='REC_CN'))
        dp.add_handler(CallbackQueryHandler(self.start, pattern='REC_START'))
        dp.add_handler(CallbackQueryHandler(self.stop, pattern='REC_STOP'))
        dp.add_handler(CallbackQueryHandler(self.timer_stop_cb, pattern='REC_TIMER_STOP'))
        # Restart all records
        for chat_id in self.recording:
            if self.recording[chat_id]['status'] in [WRITING, WAIT_STOP]:
                self.recording[chat_id]['folder_record'] = data[str(chat_id)]['folder_record']
                self.recording[chat_id]['file_name'] = data[str(chat_id)]['file_name']
                message_id = data[str(chat_id)]['edit_msg']
                # Start timer
                self.job_timer_start(chat_id)
                # Message to send
                try:
                    text = f"*RESTORE* 📼 *Recording*..."
                    self.updater.bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id, parse_mode='Markdown')
                except BadRequest:
                    pass
            if self.recording[chat_id]['status'] in [WAIT_START]:
                self.recording[chat_id]['status'] = IDLE

    def add_text(self, update, context, edit=False):
        chat_id = update.effective_chat.id
        # Text message
        text = update.message.text
        # Make message
        msg = make_dict_message(update, text, edit)
        # Add message in queue text
        self.recording[chat_id]['msgs'].append(msg)
        # Recording funcions
        if self.recording[chat_id]['status'] in [WRITING, WAIT_STOP]:
            # Write message on history
            self.writing(context.bot, chat_id, msg)
        return text

    def close_all_records(self, bot):
        data = {}
        infobot = bot.get_me()
        for chat_id in self.recording:
            # Store status
            data[chat_id] = {'status': self.recording[chat_id]['status']}
            # Close all records
            if self.recording[chat_id]['status'] not in [IDLE]:
                # Save recording informations
                data[chat_id]['folder_record'] = self.recording[chat_id]['folder_record']
                data[chat_id]['file_name'] = self.recording[chat_id]['file_name']
                # Set in idle mode
                self.recording[chat_id]['status'] = IDLE
                # Send message
                text = f"💤 Switch off {infobot.first_name}"
                msg = bot.send_message(chat_id=chat_id, text=text, parse_mode='HTML')
                data[chat_id]['edit_msg'] = msg.message_id
            # Store records
            for msg in self.recording[chat_id]['msgs']:
                msg['date'] = int(msg['date'].timestamp())
            data[chat_id]['msgs'] = list(self.recording[chat_id]['msgs'])
        # Save configuration
        save_config(f"{self.records_folder}/{RECORDS_FILE}", data)

    def get_folders_text(self, context, user_id):
        folder_list = [folder for folder in os.listdir(self.records_folder) if os.path.isdir(f"{self.records_folder}/{folder}")]
        if not folder_list:
            return f"No records!"
        text = "📼 Records:\n"
        for folder in folder_list:
            chat_id = "-" + folder
            try:
                chat = context.bot.getChat(chat_id)
                title = chat.title
                user_chat = context.bot.get_chat_member(chat_id, user_id)
                user_status = user_chat.status not in ['left', 'kicked']
            except BadRequest:
                title = f'No name {chat_id}'
                user_status = True
            if user_status and len(os.listdir(f"{self.records_folder}/{folder}") ) != 0:
                text += f" - {title}\n"
        return text

    def get_folders(self, context, user_id, keyID):
        buttons = []
        # List all folders
        folder_list = [folder for folder in os.listdir(self.records_folder) if os.path.isdir(f"{self.records_folder}/{folder}")]
        for folder in folder_list:
            chat_id = "-" + folder
            try:
                chat = context.bot.getChat(chat_id)
                title = chat.title
                user_chat = context.bot.get_chat_member(chat_id, user_id)
                user_status = user_chat.status not in ['left', 'kicked']
            except BadRequest:
                logger.warning(f"This {chat_id} does not exist!")
                title = f'No name {chat_id}'
                user_status = True
            if user_status and len(os.listdir(f"{self.records_folder}/{folder}") ) != 0:
                buttons += [InlineKeyboardButton(title, callback_data=f"REC_DATA {keyID} {folder}")]
        # Build reply markup
        if buttons:
            message = 'List of records:'
            reply_markup = InlineKeyboardMarkup(build_menu(buttons, 1, footer_buttons=InlineKeyboardButton("Cancel", callback_data=f"REC_CN {keyID}")))
        else:
            message = 'No records'
            reply_markup = InlineKeyboardMarkup(buttons)
        return message, reply_markup

    def get_records_text(self, context, keyID, folder_chat):
        path = f"{self.records_folder}/{folder_chat}"
        chat_id = "-" + folder_chat
        try:
            chat = context.bot.getChat(chat_id)
            title = chat.title
        except BadRequest:
            title = f'No name {chat_id}'
        folder_text = ""
        if os.path.isdir(path):
            list_dir = [x for x in os.listdir(path) if os.path.isdir(os.path.join(path, x))]
            for rec in list_dir:
                if int(chat_id) in self.recording:
                    if self.recording[int(chat_id)]['status'] in [WRITING, WAIT_STOP]:
                        continue
                filename = str(datetime.fromtimestamp(int(rec)))
                folder_text += f" - 📼 {filename}\n"
        if folder_text:
            text = f"Records from {title}\n"
            text += folder_text
        else:
            text = f'No records from {title}'
        return text

    def get_records_list(self, context, keyID, folder_chat):
        path = f"{self.records_folder}/{folder_chat}"
        buttons = []
        chat_id = "-" + folder_chat
        try:
            chat = context.bot.getChat(chat_id)
            title = chat.title
        except BadRequest:
            title = f'No name {chat_id}'
        message = f'No records from {title}'
        reply_markup = InlineKeyboardMarkup(buttons)
        if os.path.isdir(path):
            list_dir = [x for x in os.listdir(path) if os.path.isdir(os.path.join(path, x))]
            context.user_data[keyID]['folder'] = sorted(list_dir)
            for idx, rec in enumerate(context.user_data[keyID]['folder']):
                if int(chat_id) in self.recording:
                    if self.recording[int(chat_id)]['status'] in [WRITING, WAIT_STOP]:
                        continue
                filename = str(datetime.fromtimestamp(int(rec)))
                buttons += [InlineKeyboardButton("📼 " + filename, callback_data=f"REC_DOWNLOAD {keyID} {idx}")]
            # Build reply markup
            if buttons:
                message = f"📼 Records from {title}"
                reply_markup = InlineKeyboardMarkup(build_menu(buttons, 1, footer_buttons=InlineKeyboardButton("Cancel", callback_data=f"REC_CN {keyID} {folder_chat}")))
        return message, reply_markup

    @rtype(['private', 'channel'])
    @register
    def records(self, update, context):
        chat_id = update.effective_chat.id
        user_id = update.effective_user.id
        # Generate ID and seperate value from command
        keyID = str(uuid4())
        # Store value
        context.user_data[keyID] = {}
        # Check user type
        if update.message.chat.type == 'private':
            # List of all folders
            message, reply_markup = self.get_folders(context, user_id, keyID)
        else:
            # Attention chat in absolute value !!!!!!!!!!!!!
            folder_name = str(chat_id)[1:]
            context.user_data[keyID]['folder_name'] = folder_name
            message, reply_markup = self.get_records_list(context, keyID, folder_name)
        # Send message
        context.bot.send_message(chat_id=update.effective_user.id, text=message, parse_mode='HTML', reply_markup=reply_markup)

    @check_key_id('Error message')
    def rec_folder(self, update, context):
        query = update.callback_query
        data = query.data.split()
        # Extract keyID, chat_id and title
        keyID = data[1]
        folder_chat = data[2]
        context.user_data[keyID]['folder_name'] = folder_chat
        # Make list of records
        message, reply_markup = self.get_records_list(context, keyID, folder_chat)
        query.edit_message_text(text=message, reply_markup=reply_markup, parse_mode='HTML')

    @check_key_id('Error message')
    def rec_download(self, update, context):
        query = update.callback_query
        chat_id = query.message.chat.id
        data = query.data.split()
        # Extract keyID, chat_id and title
        keyID = data[1]
        folder_idx = data[2]
        folder_chat = context.user_data[keyID]['folder_name']
        folder_download = context.user_data[keyID]['folder'][int(folder_idx)]
        path_document = f"{self.records_folder}/{folder_chat}/{folder_download}"
        # Document info
        chat = context.bot.getChat("-" + folder_chat)
        #filename, _ = os.path.splitext(folder_download)
        filename = str(datetime.fromtimestamp(int(folder_download)))
        option = data[3] if len(data) == 4 else ""
        # Select extra option for admin
        if not self.channels.isRestricted(update, context) and not option:
            buttons = [InlineKeyboardButton("📼 Download", callback_data=f"REC_DOWNLOAD {keyID} {folder_idx} download"),
                       InlineKeyboardButton("🧹 Remove", callback_data=f"REC_DOWNLOAD {keyID} {folder_idx} delete")]
            reply_markup = InlineKeyboardMarkup(build_menu(buttons, 3, footer_buttons=InlineKeyboardButton("Cancel", callback_data=f"REC_CN {keyID}")))
            text = f"📼 {filename} from {chat.title}"
            query.edit_message_text(text=text, reply_markup=reply_markup, parse_mode='HTML')
        else:
            if option == 'download':
                context.bot.delete_message(chat_id=chat_id, message_id=query.message.message_id)
                # Record information
                self.send_record(context.bot, chat_id, folder_chat, folder_download)
            elif option == 'delete':
                # Remove file
                shutil.rmtree(path_document)
                # Write text information
                text = f"🧹 Removed 📼 {filename} from {chat.title}"
                query.edit_message_text(text=text, parse_mode='HTML')
            # remove key from user_data list
            del context.user_data[keyID]

    def send_record(self, bot, chat_id, folder_chat, folder_download):
        # Action message
        bot.send_chat_action(chat_id=chat_id, action=ChatAction.UPLOAD_DOCUMENT)
        # Make path
        path_document = f"{self.records_folder}/{folder_chat}/{folder_download}"
        # Document info
        chat = bot.getChat("-" + folder_chat)
        filename = str(datetime.fromtimestamp(int(folder_download)))
        # Record information
        data_folder = os.listdir(path_document)
        if len(data_folder) > 1:
            logger.info(f"Make a zip and send records of {folder_download} in {folder_chat}")
            # path zip file
            document = f"{path_document}/{filename}.zip"
            # Zip folder
            zip_record(document, path_document)
        else:
            # Extract name document
            file_record = data_folder[0]
            # make final path
            document = f"{path_document}/{file_record}"
        # Sending file
        bot.send_document(chat_id=chat_id, document=open(document, 'rb'), caption=f"📼 from {chat.title}", parse_mode='HTML')
        # Remove zip file if exist
        if os.path.isfile(f"{path_document}/{filename}.zip"):
            os.remove(f"{path_document}/{filename}.zip")

    @check_key_id('Error message')
    def rec_cancel(self, update, context):
        query = update.callback_query
        data = query.data.split()
        user_id = update.effective_user.id
        # Extract keyID, chat_id and title
        keyID = data[1]
        text = "Abort\n"
        if len(data) > 2:
            folder_chat = data[2]
            text += self.get_records_text(context, keyID, folder_chat)
        else:
            text += self.get_folders_text(context, user_id)
        # edit message
        query.edit_message_text(text=text, parse_mode='HTML')
        # remove key from user_data list
        del context.user_data[keyID]

    @rtype(['channel'])
    @register
    def player(self, update, context):
        chat_id = update.effective_chat.id
        # Filter and update data channel
        if self.record_filter(update, context):
            if not self.settings['channels'][str(chat_id)].get('records', True):
                text = "⛔ I'm disabled here!"
                # Send message
                context.bot.send_message(chat_id=chat_id, text=text, parse_mode='Markdown')
            return
        # Add text
        self.add_text(update, context)
        # Open an ask box only if is not in wait
        if self.recording[chat_id]['status'] not in [WAIT_START, WAIT_STOP]:
            if self.recording[chat_id]['status'] == IDLE:
                control = "record"
                self.recording[chat_id]['status'] = WAIT_START
                type_message = "📼 *RECORD*"
            elif self.recording[chat_id]['status'] == WRITING:
                control = "stop"
                self.recording[chat_id]['status'] = WAIT_STOP
                type_message = "🚫 *STOP*"
            else:
                control = "ERROR"
                type_message = control
            # Generate ID and seperate value from command
            keyID = str(uuid4())
            # Store value
            context.user_data[keyID] = {'control': control, 'chat_id': chat_id}
            text = f"Do you want {type_message} this chat?\n"
            text += f"_[You can also use #start and #stop in your message]_"
            self.recording[chat_id]['job_player'] = Autoreply(self.updater, context, chat_id, 'REC_PLAYER', text, self.cb_player, f'false {control}', keyID=keyID)
        else:
            text = "😅 *Ops!*\nI'm waiting your reply in another message"
            # Send message
            context.bot.send_message(chat_id=chat_id, text=text, parse_mode='Markdown')

    @check_key_id('Error message')
    def player_control(self, update, context):
        query = update.callback_query
        user = query.from_user
        data = query.data.split()
        # Extract keyID
        keyID = data[1]
        chat_id = context.user_data[keyID]['chat_id']
        control = context.user_data[keyID]['control']
        data[2] = f'{data[2]} {control}'
        # Start controller callback
        self.cb_player(context.bot, query.message.message_id, chat_id, data, user)
        # remove key from user_data list
        del context.user_data[keyID]

    def cb_player(self, bot, message_id, chat_id, data, user):
        # Extract keyID
        keyID = data[1]
        # Get type control
        status, control = data[2].split()
        # Stop the autoreply timer
        if chat_id in self.recording:
            if 'job_player' in self.recording[chat_id]:
                self.recording[chat_id]['job_player'].stop()
            # Extract status record
            logger.info(f"player status {control} {status}")
            if control == 'record':
                data = ['REC_START', keyID, status]
                self.cb_start(bot, message_id, chat_id, data, user)
            elif control == 'stop':
                # Fix sen message for stop
                data = ['REC_STOP', keyID, status]
                self.cb_stop(bot, message_id, chat_id, data, user)
        else:
            text = "Error message"
            bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id)

    def job_timer_reset(self, chat_id):
        # Stop the timer
        self.job_timer_stop(chat_id)
        # Start timer
        self.job_timer_start(chat_id)

    def job_timer_start(self, chat_id):
        # add job in recording dictionary
        records = self.settings['config'].get('records', {})
        timeout = int(records.get('timeout', self.timeout))
        self.recording[chat_id]['job'] = self.job.run_once(self.timer_stop, timeout, context=chat_id)

    def job_timer_stop(self, chat_id):
        if 'job' in self.recording[chat_id]:
            job = self.recording[chat_id]['job']
            # Stop the timer
            job.enabled = False  # Temporarily disable this job
            job.schedule_removal()  # Remove this job completely

    def job_timer_delete(self, chat_id):
        if 'job' in self.recording[chat_id]:
            # Remove timer
            del self.recording[chat_id]['job']

    def record_filter(self, update, context):
        # Check is the message is not empty
        if update.message is None:
            logger.info("Empty message or edited")
            return True
        # Check if not a private chat
        if update.message.chat.type == 'private':
            logger.info("Private chat")
            return True
        chat_id = update.effective_chat.id
        if str(chat_id) not in self.settings['channels']:
            logger.info("Chat not authorized")
            return True
        # Enable only beta channels
        if BETA:
            if not self.settings['channels'][str(chat_id)].get('beta', False):
                return True
        # Don't register if chat is disabled
        if not self.settings['channels'][str(chat_id)].get('records', True):
            if chat_id in self.recording:
                # Stop recording if enabled
                if 'status' in self.recording[chat_id]:
                    if self.recording[chat_id]['status'] not in [IDLE]:
                        # Set in idle mode and wait a new record
                        self.idle(context.bot, chat_id)
                        # Set in idle mode
                        self.recording[chat_id]['status'] = IDLE
                        # Send message
                        text = f"🛑 Recording *stop*!"
                        context.bot.send_message(chat_id=chat_id, text=text, parse_mode='Markdown')
                # Clean chat if is already created
                if 'msgs' in self.recording[chat_id]:
                    self.recording[chat_id]['msgs'].clear()
                # Remove record
                del self.recording[chat_id]
            return True
        # initialization recording chat
        records = self.settings['config'].get('records', {})
        if chat_id not in self.recording:
            size_record_chat = int(records.get('msgs', self.size_record_chat))
            self.recording[chat_id] = {'status': IDLE, 'msgs': deque(maxlen=size_record_chat)}
        # Update deque size
        new_size_msgs = int(records.get('msgs', self.size_record_chat))
        if new_size_msgs != self.recording[chat_id]['msgs'].maxlen:
            old_msgs = list(self.recording[chat_id]['msgs'])
            logger.info(f"Update msgs size from {self.recording[chat_id]['msgs'].maxlen} to {new_size_msgs}")
            # Update deque
            self.recording[chat_id]['msgs'] = deque(old_msgs, maxlen=new_size_msgs)
        # restart timer only if is active
        if 'job' in self.recording[chat_id] and self.recording[chat_id]['status'] in [WRITING]:
            # restart timer
            self.job_timer_reset(chat_id)
        return False

    def save_voice(self, bot, chat_id, msg):
        # Get file id picture big size
        file_id = msg['voice']
        newFile = bot.get_file(file_id)
        # Get filename
        # https://stackoverflow.com/questions/10552188/python-split-url-to-find-image-name-and-extension
        picture_page = newFile.file_path
        disassembled = urlparse(picture_page)
        file_name = basename(disassembled.path)
        # Write text
        msg['text'] = f"Attached voice {file_name}"
        # Attention chat in absolute value !!!!!!!!!!!!!
        folder_name = str(chat_id)[1:]
        folder_record = self.recording[chat_id]['folder_record']
        # Path document
        document = f"{self.records_folder}/{folder_name}/{folder_record}/{file_name}"
        # Download the document
        doc = Downloader(newFile, document, file_name)
        doc.start()
        return msg

    @register
    def record_voice(self, update, context):
        # Filter and update data channel
        if self.record_filter(update, context):
            return
        chat_id = update.effective_chat.id
        # Make message
        msg = make_dict_message(update, '')
        # Add photo message information
        msg['voice'] = update.message.voice.file_id
        # Add message in queue text
        self.recording[chat_id]['msgs'].append(msg)
        # Recording funcions
        if self.recording[chat_id]['status'] in [WRITING]:
            # Write message
            self.writing(context.bot, chat_id, msg)

    def save_document(self, bot, chat_id, msg):
        # Get file id picture big size
        file_id = msg['document']['file_id']
        newFile = bot.get_file(file_id)
        # Get filename
        file_name = msg['document']['file_name']
        # Attention chat in absolute value !!!!!!!!!!!!!
        folder_name = str(chat_id)[1:]
        folder_record = self.recording[chat_id]['folder_record']
        # Path document
        document = f"{self.records_folder}/{folder_name}/{folder_record}/{file_name}"
        # Download the document
        doc = Downloader(newFile, document, file_name)
        doc.start()

    @register
    def record_document(self, update, context):
        # Filter and update data channel
        if self.record_filter(update, context):
            return
        chat_id = update.effective_chat.id
        # Get filename
        file_name = update.message.document.file_name
        # Make message
        msg = make_dict_message(update, f"Attached document: {file_name}")
        # Document information
        msg['document'] = {'file_id': update.message.document.file_id, 'file_name': file_name}
        # Add message in queue text
        self.recording[chat_id]['msgs'].append(msg)
        # Recording funcions
        if self.recording[chat_id]['status'] in [WRITING]:
            # Write message
            self.writing(context.bot, chat_id, msg)

    def save_foto(self, bot, chat_id, msg):
        # Get file id picture big size
        file_id = msg['photo']
        newFile = bot.get_file(file_id)
        # Get filename
        # https://stackoverflow.com/questions/10552188/python-split-url-to-find-image-name-and-extension
        picture_page = newFile.file_path
        disassembled = urlparse(picture_page)
        file_name = basename(disassembled.path)
        # Write text
        text = f"Attached photo {file_name}"
        if msg['text']:
            text+= f" - Caption: {msg['text']}"
        msg['text'] = text
        # Attention chat in absolute value !!!!!!!!!!!!!
        folder_name = str(chat_id)[1:]
        folder_record = self.recording[chat_id]['folder_record']
        # Path document
        document = f"{self.records_folder}/{folder_name}/{folder_record}/{file_name}"
        # Download the document
        doc = Downloader(newFile, document, file_name)
        doc.start()
        return msg

    @register
    def record_photo(self, update, context):
        # Filter and update data channel
        if self.record_filter(update, context):
            return
        chat_id = update.effective_chat.id
        # Make message
        msg = make_dict_message(update, update.message.caption)
        # Add photo message information
        msg['photo'] = update.message.photo[-1].file_id
        # Add message in queue text
        self.recording[chat_id]['msgs'].append(msg)
        # Recording funcions
        if self.recording[chat_id]['status'] in [WRITING]:
            # Write message
            self.writing(context.bot, chat_id, msg)

    @register
    def record(self, update, context):
        # If is an edited message overload standard message
        edited_message = False
        if update.edited_message:
            edited_message = True
            update.message = update.edited_message
        # print(update.message)
        # Filter and update data channel
        if self.record_filter(update, context):
            return
        # Add text
        text = self.add_text(update, context, edited_message)
        # https://python-telegram-bot.readthedocs.io/en/latest/telegram.messageentity.html
        # entities = update.message.parse_entities()
        # Extract all hashtags
        hashtags = re.findall(r"#(\w+)", text)
        # check if there is the start hashtag
        start = True if START in hashtags else False
        stop = True if STOP in hashtags else False
        # Initialize recording
        chat_id = update.effective_chat.id
        # If start is only in this text start to record
        if start and self.recording[chat_id]['status'] == IDLE:
            # Wait reply
            self.recording[chat_id]['status'] = WAIT_START
            # Send message
            text = "📼 Do you want *record* this chat? 📼"
            self.recording[chat_id]['job_autoreply'] = Autoreply(self.updater, context, chat_id, 'REC_START', text, self.cb_start, 'false')
        elif stop and self.recording[chat_id]['status'] not in [IDLE, WAIT_START, WAIT_STOP]:
            # Wait reply
            self.recording[chat_id]['status'] = WAIT_STOP
            # Remove and stop the timer
            self.job_timer_stop(chat_id)
            self.job_timer_delete(chat_id)
            # Send message
            text = "🚫 Do you want *stop* now? 🚫"
            self.recording[chat_id]['job_autoreply'] = Autoreply(self.updater, context, chat_id, 'REC_STOP', text, self.cb_stop, 'true')
        # Check before to start if require to wait
        if not self.recording[chat_id].get('delay_autorestart', False):
            # Auto record start
            self.auto_start(context, chat_id)

    def auto_start(self, context, chat_id):
        # Minimum number of messages recordered
        records = self.settings['config'].get('records', {})
        size_record_chat = int(records.get('msgs', self.size_record_chat))
        rush_messages = False
        if len(self.recording[chat_id]['msgs']) > size_record_chat // 2:
            # Get first and last message
            first = self.recording[chat_id]['msgs'][-size_record_chat // 2]
            last = self.recording[chat_id]['msgs'][-1]
            # print(f"First: {first['date']} - {first['text']} -- Last: {last['date']} - {last['text']}")
            # Measure delta from last and first message
            delta = last['date'] - first['date']
            # print(f"Delta: {delta}")
            # If delta is minus or equal the minimum time enable rush_messages
            records = self.settings['config'].get('records', {})
            min_delta = int(records.get('min_start', self.min_delta))
            if delta <= timedelta(minutes=min_delta):
                rush_messages = True
        # Run Autostart
        if rush_messages and self.recording[chat_id]['status'] == IDLE:
            # Wait reply
            self.recording[chat_id]['status'] = WAIT_START
            # Set autorestart
            self.recording[chat_id]['autorestart'] = True
            # Send message
            text = "🔥 This chat getting *hot* 🔥\n📼 Do you want *record* this chat? 📼"
            self.recording[chat_id]['job_autoreply'] = Autoreply(self.updater, context, chat_id, 'REC_START', text, self.cb_start, 'false')

    def writing(self, bot, chat_id, msg):
        # Attention chat in absolute value !!!!!!!!!!!!!
        folder_name = str(chat_id)[1:]
        folder_record = self.recording[chat_id]['folder_record']
        file_name = self.recording[chat_id]['file_name']
        # Save photo if is included in msg
        if 'document' in msg:
            self.save_document(bot, chat_id, msg)
        # Save photo if is included in msg
        if 'photo' in msg:
            self.save_foto(bot, chat_id, msg)
        # Save voice if is included in msg
        if 'voice' in msg:
            self.save_voice(bot, chat_id, msg)
        # Append new line on file
        with open(f"{self.records_folder}/{folder_name}/{folder_record}/{file_name}", "a") as f:
            data = [str(msg[name]) for name in MSG_TEXT_ORDER]
            f.write(f"{self.separator}".join(data) + f"\n")
        # log status
        logger.info(f"Chat {chat_id} in WRITING {msg['text']}")

    def init_record(self, bot, chat_id):
        # log status
        logger.info(f"Chat {chat_id} in INITIALIZATION...")
        # Attention chat in absolute value !!!!!!!!!!!!!
        folder_name = str(chat_id)[1:]
        last_record = self.recording[chat_id]['msgs'][-1]
        # Make new folder and file name
        folder_record = str(last_record['date'].timestamp()).split('.')[0]
        self.recording[chat_id]['folder_record'] = folder_record
        file_name = str(last_record['date']) + "." + self.extension
        self.recording[chat_id]['file_name'] = file_name
        # Make chat folder if not exist
        if not os.path.isdir(f"{self.records_folder}/{folder_name}"):
            os.mkdir(f"{self.records_folder}/{folder_name}")
            logger.info(f"Chat directory {folder_name} created")
        # Make record folder
        if not os.path.isdir(f"{self.records_folder}/{folder_name}/{folder_record}"):
            os.mkdir(f"{self.records_folder}/{folder_name}/{folder_record}")
            logger.info(f"Record directory {folder_record} created")
        # Init file and write a file
        # "x" - Create - will create a file, returns an error if the file exist
        # "a" - Append - will create a file if the specified file does not exist
        # "w" - Write - will create a file if the specified file does not exist
        with open(f"{self.records_folder}/{folder_name}/{folder_record}/{file_name}", "x") as f:
            # Write header
            f.write(f"{self.separator}".join(MSG_TEXT_ORDER) + f"\n")
        # Copy all messages
        for msg in self.recording[chat_id]['msgs']:
            # Save all old messages
            self.writing(bot, chat_id, msg)
        # log status
        logger.info(f"Chat {chat_id} in INITIALIZATION... DONE!")

    def idle(self, bot, chat_id):
        # log status
        logger.info(f"Chat {chat_id} in IDLE")
        # Extract msgs
        # Attention chat in absolute value !!!!!!!!!!!!!
        folder_name = str(chat_id)[1:]
        if 'folder_record' not in self.recording[chat_id]:
            logger.warning(f"No \'folder_record\' in recording")
            return
        folder_record = self.recording[chat_id]['folder_record']
        # Send recordered registration
        self.send_record(bot, chat_id, folder_name, folder_record)

    def timer_stop(self, context: CallbackContext):
        # Extract chat ids
        chat_id = context.job.context
        logger.info(f"Timeout timer")
        if chat_id in self.recording:
            if self.recording[chat_id]['status'] in [WRITING]:
                # Wait reply
                self.recording[chat_id]['status'] = WAIT_STOP
                # Send message
                text = "*TOK TOK* There is anyone here?\n🚫 Do you want *stop* now? 🚫"
                self.recording[chat_id]['job_autoreply'] = Autoreply(self.updater, context, chat_id, 'REC_TIMER_STOP', text, self.cb_stop, 'true', keyID=chat_id)

    @check_key_id('Error message')
    def start(self, update, context):
        query = update.callback_query
        user = query.from_user
        data = query.data.split()
        # Extract keyID
        keyID = data[1]
        chat_id = context.user_data[keyID]['chat_id']
        # Start controller callback
        self.cb_start(context.bot, query.message.message_id, chat_id, data, user)
        # remove key from user_data list
        del context.user_data[keyID]

    def cb_start(self, bot, message_id, chat_id, data, user):
        # Stop the autoreply timer
        if chat_id in self.recording:
            if 'job_autoreply' in self.recording[chat_id]:
                self.recording[chat_id]['job_autoreply'].stop()
            # Extract status record
            status = True if data[2] == 'true' else False
            if status:
                # Start write mode
                self.init_record(bot, chat_id)
                # Start timer
                self.job_timer_start(chat_id)
                # Initialize writing mode
                self.recording[chat_id]['status'] = WRITING
                # Clear wait autorestart
                if 'delay_autorestart' in self.recording[chat_id]:
                    self.recording[chat_id]['delay_autorestart'] = False
                # Stop the timer
                if 'job_delay_autorestart' in self.recording[chat_id]:
                    job = self.recording[chat_id]['job_delay_autorestart']
                    job.enabled = False  # Temporarily disable this job
                    job.schedule_removal()  # Remove this job completely
                # Message to send
                text = f"📼 Recording...\n"
                text += f"[replied by {user.name}]"
                bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id, parse_mode='HTML')
            else:
                self.recording[chat_id]['status'] = IDLE
                # Clear wait autorestart
                if 'autorestart' in self.recording[chat_id]:
                    if self.recording[chat_id]['autorestart']:
                        self.recording[chat_id]['delay_autorestart'] = True
                # Send the message
                text = f"😉 Doesn't matter\n"
                text += f"[replied by {user.name}]"
                bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id, parse_mode='HTML')
            # Clear autorestart status
            if 'autorestart' in self.recording[chat_id]:
                self.recording[chat_id]['autorestart'] = False
            # Run delay autorestart
            if self.recording[chat_id].get('delay_autorestart', False):
                records = self.settings['config'].get('records', {})
                d_start = int(records.get('d_start', self.d_start))
                logger.info(f"Delay autorestart chat: {chat_id} for {d_start}s")
                self.recording[chat_id]['job_delay_autorestart'] = self.job.run_once(self.reset_delay_autorestart, d_start, context=chat_id)
        else:
            text = "Error message"
            bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id)

    def reset_delay_autorestart(self, context: CallbackContext):
        # Extract chat ids
        chat_id = context.job.context
        # Clear wait autorestart
        if 'delay_autorestart' in self.recording[chat_id]:
            self.recording[chat_id]['delay_autorestart'] = False
        logger.info(f"Reset delay autorestart chat: {chat_id}")

    @check_key_id('Error message')
    def stop(self, update, context):
        query = update.callback_query
        user = query.from_user
        data = query.data.split()
        # Extract keyID
        keyID = data[1]
        chat_id = context.user_data[keyID]['chat_id']
        # Run callback stop
        self.cb_stop(context.bot, query.message.message_id, chat_id, data, user)
        # remove key from user_data list
        del context.user_data[keyID]

    def timer_stop_cb(self, update, context):
        query = update.callback_query
        user = query.from_user
        data = query.data.split()
        chat_id = int(data[1])
        # Run callback stop
        self.cb_stop(context.bot, query.message.message_id, chat_id, data, user)

    def cb_stop(self, bot, message_id, chat_id, data, user):
        # Stop the autoreply timer
        if chat_id in self.recording:
            if 'job_autoreply' in self.recording[chat_id]:
                self.recording[chat_id]['job_autoreply'].stop()
            # Extract status record
            status = True if data[2] == 'true' else False
            if status:
                # Remove and stop the timer
                self.job_timer_stop(chat_id)
                self.job_timer_delete(chat_id)
                # Set in idle mode and wait a new record
                self.idle(bot, chat_id)
                # Set in idle mode
                self.recording[chat_id]['status'] = IDLE
                # Send message
                text = f"🛑 Recording stop!\n"
                text += f"[replied by {user.name}]"
                bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id, parse_mode='HTML')
            else:
                self.recording[chat_id]['status'] = WRITING
                # Start timer
                self.job_timer_reset(chat_id)
                # Send the message
                text = f"📼 Recording...\n"
                text += f"[replied by {user.name}]"
                bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id, parse_mode='HTML')
        else:
            text = "Error message"
            bot.edit_message_text(chat_id=chat_id, text=text, message_id=message_id)


from typing import List

class solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        res = []
        # Sort the array by the first small list in list of list
        intervals.sort(key=lambda x: x[0])
        for i in intervals:
            # if the list is empty add it to the list or if it is not then
            # compare the last element of list by[-1][1]-->[[1,3][5,10]]-->10
            # if smaller then append it else just update the bigger value in the last list last element
            if not res or res[-1][1] < i[0]:
                res.append(i)
            else:
                res[-1][1] = max(res[-1][1], i[1])
        return res

    '''
    merged=[]
    i=0-->[[1,3]]
    i=1-->[1,3]-->[1,6]
    i=2-->[1,6],8>6-->[1,6][8,10]
    i=3->-->[1,6][8,10][15,18]

    '''

if __name__ == '__main__':
    intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
    ob = solution()
    print(ob.merge(intervals))


# -*- coding: utf-8 -*-
"""
StoreTest
Class tested: StoreModel

Only test methods that depends on databases or work with other classes and methods of your app
"""
import unittest

from pricealerts import db
from pricealerts.common.base_model import DatabaseError
from pricealerts.models import StoreModel, ItemModel, ItemNotFoundError
from tests.base_test import BaseTest

class StoreTest(BaseTest):
    def setUp(self):
        super(StoreTest, self).setUp()
        self.store = StoreModel(name='store1', url_prefix='http://johnlewis.com', tag_name='span',
                           query_string={'id': 'priceblock_ourprice'})

    def test_create_store_empty(self):

        expected = {"id": None, "name": "store1", "items": []}

        self.assertEqual(expected, self.store.json())
        self.assertListEqual([], self.store.items.all())
        self.assertEqual(0, len(self.store.items.all()))

    def test_store_json_with_items(self):
        """
        Tests if json method returns a dic() instance with items list added on it
        :return: None
        """
        with self.app_context():

            self.store.save_to_db()
            ItemModel(name='Item1', url='http://johnlewis.com/item/1', store_id=self.store.id).save_to_db()
            expected = {
                'id': self.store.id,
                'name': 'store1',
                'items': [{
                    "name": "Item1",
                    "price": 0.0,
                    "store_id": self.store.id,
                    'url': 'http://johnlewis.com/item/1'

                }]
            }

            self.assertDictEqual(expected, self.store.json())

    def test_store_json_no_items(self):
        """
        Tests if json method returns a dic() instance of the store,
        with items list empty, and added on it
        :return: None
        """
        with self.app_context():
            expected = {"id": 1, "name": "store1", "items": []}
            self.store.save_to_db()

            self.assertDictEqual(expected, self.store.json())

    def test_crud(self):
        with self.app_context():
            self.assertIsNone(StoreModel.find_by_name('store1'))
            self.store.save_to_db()
            self.assertIsNotNone(StoreModel.find_by_name('store1'))
            self.store.name = 'Amazon'
            self.store.save_to_db()
            self.assertIsNotNone(StoreModel.find_by_name('Amazon'))
            self.store.delete_from_db()
            self.assertIsNone(StoreModel.find_by_name('test'))

    def test_crud_raises_exceptions_in_db_operations(self):
        with self.app_context():
            db.drop_all()

            with self.assertRaises(DatabaseError):
                StoreModel.find_by_name('store1')

            with self.assertRaises(DatabaseError):
                self.store.save_to_db()

    def test_get_item_from_store(self):
        with self.app_context():
            self.store.save_to_db()
            ItemModel(name='Item1', url='http://johnlewis.com/item/1', store_id=self.store.id).save_to_db()
            self.assertEqual('Item1', self.store.get_item(1).name)

    def test_get_item_from_store_not_found(self):
        with self.app_context():
            self.store.save_to_db()
            ItemModel(name='Item1', url='http://johnlewis.com/item/1', store_id=self.store.id).save_to_db()

            # Get an item with id = 2, raises ItemNotFoundError
            self.assertRaises(
                ItemNotFoundError, self.store.get_item, 2)

    def test_delete_item_not_found_from_store(self):

        with self.app_context():

            self.store.save_to_db()
            item = ItemModel(name='Item1', url='http://johnlewis.com/item/1', store_id=self.store.id).save_to_db()  # This item has id = 1
            self.assertEqual(1, item.id)

            # Deleting an item with id = 2, raises ItemNotFoundError
            self.assertRaises(
                ItemNotFoundError, self.store.delete_item, 2)

    def test_delete_item_from_store(self):

        with self.app_context():

            self.store.save_to_db()
            # This item has id = 1, after is saved
            item = ItemModel(name='Item1', url='http://johnlewis.com/item/1', store_id=self.store.id).save_to_db()
            self.assertEqual(1, item.id)

            self.assertEqual(1, self.store.items.count())
            self.store.delete_item(1)
            self.assertEqual(0, self.store.items.count())

if __name__ == '__main__':
    unittest.main()
    import doctest

    doctest.testmod()


# -*- coding: utf-8 -*-
"""
    celery.events.dumper
    ~~~~~~~~~~~~~~~~~~~~

    This is a simple program that dumps events to the console
    as they happen. Think of it like a `tcpdump` for Celery events.

"""
from __future__ import absolute_import

import sys

from datetime import datetime

from celery.app import app_or_default
from celery.datastructures import LRUCache
from celery.utils.timeutils import humanize_seconds

TASK_NAMES = LRUCache(limit=0xFFF)

HUMAN_TYPES = {'worker-offline': 'shutdown',
               'worker-online': 'started',
               'worker-heartbeat': 'heartbeat'}

CONNECTION_ERROR = """\
-> Cannot connect to %s: %s.
Trying again %s
"""

def humanize_type(type):
    try:
        return HUMAN_TYPES[type.lower()]
    except KeyError:
        return type.lower().replace('-', ' ')

def say(msg, out=sys.stdout):
    out.write(msg + '\n')

class Dumper(object):

    def __init__(self, out=sys.stdout):
        self.out = out

    def say(self, msg):
        say(msg, out=self.out)

    def on_event(self, ev):
        timestamp = datetime.utcfromtimestamp(ev.pop('timestamp'))
        type = ev.pop('type').lower()
        hostname = ev.pop('hostname')
        if type.startswith('task-'):
            uuid = ev.pop('uuid')
            if type in ('task-received', 'task-sent'):
                task = TASK_NAMES[uuid] = '%s(%s) args=%s kwargs=%s' % (
                    ev.pop('name'), uuid,
                    ev.pop('args'),
                    ev.pop('kwargs'))
            else:
                task = TASK_NAMES.get(uuid, '')
            return self.format_task_event(hostname, timestamp,
                                          type, task, ev)
        fields = ', '.join('%s=%s' % (key, ev[key]) for key in sorted(ev))
        sep = fields and ':' or ''
        self.say('%s [%s] %s%s %s' % (hostname, timestamp,
                                      humanize_type(type), sep, fields))

    def format_task_event(self, hostname, timestamp, type, task, ev):
        fields = ', '.join('%s=%s' % (key, ev[key]) for key in sorted(ev))
        sep = fields and ':' or ''
        self.say('%s [%s] %s%s %s %s' % (
            hostname, timestamp, humanize_type(type), sep, task, fields,
        ))

def evdump(app=None, out=sys.stdout):
    app = app_or_default(app)
    dumper = Dumper(out=out)
    dumper.say('-> evdump: starting capture...')
    conn = app.connection()

    def _error_handler(exc, interval):
        dumper.say(CONNECTION_ERROR % (
            conn.as_uri(), exc, humanize_seconds(interval, 'in', ' ')
        ))

    while 1:
        try:
            conn = conn.clone()
            conn.ensure_connection(_error_handler)
            recv = app.events.Receiver(conn, handlers={'*': dumper.on_event})
            recv.capture()
        except (KeyboardInterrupt, SystemExit):
            return conn and conn.close()
        except conn.connection_errors + conn.channel_errors:
            dumper.say('-> Connection lost, attempting reconnect')

if __name__ == '__main__':  # pragma: no cover
    evdump()


class animal:
  species = None
  name = None
  sound = None

  def __init__(self, name, species, sound,
               color):  # Include the 'self' in the 'init'
    self.name = name
    self.species = species
    self.sound = sound
    self.color = color

class bird(animal):

  def __init__(self):
    self.name = "Bird"
    self.species = "Avian"
    self.sound = "Tweet"
    self.color = "green"

cow = animal("Ermintrude", "Bo Taurus", "Moo", "black")

# print(cow.sound)

# create the generic class for job
# contains name, salary, hours worked
# init >> self, name, salary, hours_worked
# inherit to other kinds of job: doctor (years of experience, specialty), teacher (subjects and position)
# instanciate a lawyer
# inherit doctor (7 years exp, paediatric consultant)
# inherit teacher (subject comp science and position teacher)
# print out the info for all jobs

# original class
class Job:
  name = None
  salary = None
  hours_worked = None

  def __init__(self, name, salary, hours_worked):
    self.name = name
    self.salary = int(salary)
    self.hours_worked = int(hours_worked)

  def job(self):
    print(
      f'Job name: {self.name}\nWage: {self.salary} €\nHours worked: {self.hours_worked}'
    )

# inherited 1
class Doctor(Job):
  experience = None
  speciality = None

  def __init__(self, salary, hours_worked, exp, speciality):
    self.name = "doctor"
    self.salary = salary
    self.hours_worked = hours_worked
    self.experience = float(exp)
    self.speciality = speciality

  def doctor(self):
    print(
      f'Job name: {self.name}\nWage: {self.salary} €\nHours worked: {self.hours_worked}\nExp.: {self.experience} years\nSpeciality: {self.speciality}'
    )

# inherited 2
class Teacher(Job):
  subject = None
  position = None

  def __init__(self, salary, hours_worked, subject, position):
    self.name = "teacher"
    self.salary = salary
    self.hours_worked = hours_worked
    self.subject = subject
    self.position = position

  def teacher(self):
    print(
      f'Job name: {self.name}\nWage: {self.salary} €\nHours worked: {self.hours_worked}\nExp.: {self.subject} years\nSpeciality: {self.position}'
    )

# print job instances
# 1. original class
lawyer = Job("lawyer", 100000, 60)
lawyer.job()
print()
#2. inherited classes
doctor = Doctor(150000, 65, 7.5, "Paediatric consultant")
doctor.doctor()
print()
teacher = Teacher(65000, 40, "Mathematics", "Head Teacher")
teacher.teacher()


from flask import Flask, request, jsonify
import pickle
import numpy as np
from model import ContentBased, CollabBased, HybridBased
import os

app = Flask(__name__)

# the recommender module
class Recommender:

    def __init__(self):
        with open('./Files/map.pkl', 'rb') as f:
            self.movie_map = pickle.load(f)
        with open('./Files/rating.pkl', 'rb') as f:
            self.rating = pickle.load(f)
        with open('./Files/latent_collaborative.pkl', 'rb') as f:
            latent_collab = pickle.load(f)
        with open('./Files/latent_content.pkl', 'rb') as f:
            latent_content = pickle.load(f)

        self.clf_content = ContentBased(latent_content)
        self.clf_collab = CollabBased(latent_collab)
        self.clf_hybrid = HybridBased(latent_content, latent_collab)

    def get_all_recommendations(self, moviename, n):
        if moviename in self.movie_map.keys():
            output = {
                'content': {'content':
                            self.clf_content.predict_top_n(moviename, n)},
                'collaborative': {'collaborative':
                                  self.clf_collab.predict_top_n(moviename, n)},
                'hybrid': {'hybrid':
                           self.clf_hybrid.predict_top_n(moviename, n)},
                     }
        else:
            output = None
        return output

ex = Recommender()
@app.route('/movies/<basis>/',methods=['GET'])
def getMovieBasis(basis):
    movie = request.args.get("movie",None)
    n = request.args.get("limit",None)
    output = ex.get_all_recommendations(movie, int(n))
    return jsonify(output[basis])

if __name__=='__main__':
    port = int(os.environ.get('PORT', 33507))
    app.run(debug=True, port=port)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import glob
from conans import ConanFile, CMake, tools

class GlfwConan(ConanFile):
    name = "glfw"
    version = "3.3"
    description = "The GLFW library - Builds on Windows, Linux and Macos/OSX"
    settings = "os", "arch", "build_type", "compiler"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {'shared': False, "fPIC": True}
    license = "Zlib"
    url = "https://github.com/bincrafters/conan-glfw"
    homepage = "https://github.com/glfw/glfw"
    author = "Bincrafters <bincrafters@gmail.com>"
    topics = ("conan", "gflw", "opengl", "vulcan", "opengl-es")
    exports = "LICENSE"
    exports_sources = "CMakeLists.txt"
    generators = "cmake"
    _source_subfolder = "source_subfolder"
    _build_subfolder = "build_subfolder"

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def configure(self):
        del self.settings.compiler.libcxx

    def source(self):
        sha256 = "1092f6815d1f6d1f67479d2dad6057172b471122d911e7a7ea2be120956ffaa4"
        tools.get("{}/archive/{}.zip".format(self.homepage, self.version), sha256=sha256)
        extracted_folder = self.name + '-' + self.version
        os.rename(extracted_folder, self._source_subfolder)

    def _configure_cmake(self):
        cmake = CMake(self)
        cmake.definitions["GLFW_BUILD_EXAMPLES"] = False
        cmake.definitions["GLFW_BUILD_TESTS"] = False
        cmake.definitions["GLFW_BUILD_DOCS"] = False
        cmake.configure(build_dir=self._build_subfolder)
        return cmake

    def build(self):
        cmake = self._configure_cmake()
        cmake.build()

        if self.settings.os == "Macos" and self.options.shared:
            with tools.chdir(os.path.join(self._source_subfolder, 'src')):
                for filename in glob.glob('*.dylib'):
                    self.run('install_name_tool -id {filename} {filename}'.format(filename=filename))

    def package(self):
        self.copy("LICENSE.md", dst="licenses", src=self._source_subfolder)
        self.copy(pattern="*.pdb", dst="bin", keep_path=False)
        cmake = self._configure_cmake()
        cmake.install()

    def package_info(self):
        self.cpp_info.libs = tools.collect_libs(self)
        if self.settings.os == "Linux":
            self.cpp_info.libs.extend(['Xrandr', 'Xrender', 'Xi', 'Xinerama', 'Xcursor', 'GL', 'm', 'dl', 'drm', 'Xdamage', 'X11-xcb', 'xcb-glx', 'xcb-dri2', 'xcb-dri3', 'xcb-present', 'xcb-sync', 'Xxf86vm', 'Xfixes', 'Xext', 'X11', 'pthread', 'xcb', 'Xau'])
            if self.options.shared:
                self.cpp_info.exelinkflags.append("-lrt -lm -ldl")
        elif self.settings.os == "Macos":
            self.cpp_info.exelinkflags.append("-framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo")


'''
    tushare 数据源
        下载内容：
            1、交易日历
            2、股票列表
            3、日线数据

'''
import datetime

import tushare

from common.interface import Downloader

pro = tushare.pro_api("9ba459eb7da2bca7e1827a240a5654bacdf481a1ea997210e049ea91")

start = "20000101"

# ========================
#   交易日历
# ========================
class TradeCalendarDownloader(Downloader):

    def download(self,code,save_path=None):

        df = pro.trade_cal(exchange='', start_date=start, end_date=code)
        if save_path:
            df.to_csv(save_path)

    # def __init__(self, end=None):
    #     super(TradeCalendarDownloader, self).__init__()
    #     self.end = end
    #     self.name = "TradeCalendarDownloader_" + self.end
    #     self.logger = logging.getLogger("TradeCalendarDownloader")
    #     if end == None:
    #         raise ValueError("交易日历结束日期不能为空！")
    #
    # def run(self) -> None:
    #     self.logger.info("下载交易日历数据...")
    #     df = pro.trade_cal(exchange='', start_date=start, end_date=self.end)
    #     df.to_csv(FileConfig.CALENDAR_PATH)
    #     self.logger.info("交易日历更新成功！")

# ========================
#   股票列表
# ========================
class CodeListDownloader(Downloader):

    def download(self,code,save_path=None):

        data = pro.stock_basic()
        if save_path:
            data.to_csv(save_path)

    # def __init__(self):
    #     super(CodeListDownloader, self).__init__()
    #     self.name = "CodeDetailDownloader_" + datetime.datetime.now().strftime("%Y%m%d")
    #     self.logger = logging.getLogger("CodeDetailDownloader")
    #
    # def run(self):
    #     self.logger.info("更新股票列表...")
    #     data = pro.stock_basic()
    #     data.to_csv(FileConfig.CODEDETAIL_PATH)
    #     self.logger.info("股票列表更新完成！")

class DailyDownloader(Downloader):

    def download(self,code,save_path=None):
        df = pro.daily(ts_code=code, end_date=datetime.datetime.now().strftime("%Y%m%d"))
        if save_path:
        # path = os.path.join(FileConfig.CODEHISTORY_PATH,code[:code.rfind(".")]+".csv")
            df.to_csv(save_path,index=False, encoding="utf-8")

if __name__ == '__main__':
    # cd = CodeListDownloader()
    # cd.run()
    # df=pro.daily(ts_code='000783.SZ', start_date='20000101', end_date='20230417')
    # print(df)
    d=DailyDownloader()
    d.download('000783.SZ')


import numpy as np
import pandas as pd
import rasterio as rio

def sample_vals(aoi, x_coords,y_coords,raw_list):
    i = 0
    index_list = []
    filename = []
    if len(aoi) == 17:
        a = 57
    else:
        a = 52

    for raw in raw_list:
        sampled_values = []

        # Sample raster at each combo of x and y coordinates
        with rio.open(raw) as src:
            for val in src.sample(zip(x_coords, y_coords)):
                i = i+1
                # Note that each val is an individual numpy array
                sampled_values.append(val)
            arr = np.array([sampled_values]).squeeze()
            index_list.append([arr])
            filename.append(raw[a:77])

    labels = filename
    index_vals = pd.DataFrame(np.concatenate(index_list))
    index_vals = index_vals.transpose()
    index_vals.columns = labels
    return index_vals


class TablaHash:
    def __init__(self):
        #self.tamano = 11
        self.tamano = 997
        self.ranuras = [None] * self.tamano
        self.datos = [None] * self.tamano
    def agregar(self,clave,dato):
      valorHash = self.funcionHash(clave,len(self.ranuras))

      if self.ranuras[valorHash] == None:
        self.ranuras[valorHash] = clave
        self.datos[valorHash] = dato
      else:
        if self.ranuras[valorHash] == clave:
          self.datos[valorHash] = dato  #reemplazo
        else:
          proximaRanura = self.rehash(valorHash,len(self.ranuras))
          while self.ranuras[proximaRanura] != None and \
                          self.ranuras[proximaRanura] != clave:
            proximaRanura = self.rehash(proximaRanura,len(self.ranuras))

          if self.ranuras[proximaRanura] == None:
            self.ranuras[proximaRanura]=clave
            self.datos[proximaRanura]=dato
          else:
            self.datos[proximaRanura] = dato #reemplazo

    def funcionHash(self,clave,tamano):
         return clave%tamano

    def rehash(self,hashViejo,tamano):
        return (hashViejo+1)%tamano
    def obtener(self,clave):
      ranuraInicio = self.funcionHash(clave,len(self.ranuras))

      dato = None
      parar = False
      encontrado = False
      posicion = ranuraInicio
      while self.ranuras[posicion] != None and  \
                           not encontrado and not parar:
         if self.ranuras[posicion] == clave:
           encontrado = True
           dato = self.datos[posicion]
         else:
           posicion=self.rehash(posicion,len(self.ranuras))
           if posicion == ranuraInicio:
               parar = True
      return dato

    def existe(self,clave):
      ranuraInicio = self.funcionHash(clave,len(self.ranuras))

      dato = None
      parar = False
      encontrado = False
      posicion = ranuraInicio
      while self.ranuras[posicion] != None and  \
                           not encontrado and not parar:
         if self.ranuras[posicion] == clave:
           encontrado = True
           dato = self.datos[posicion]
         else:
           posicion=self.rehash(posicion,len(self.ranuras))
           if posicion == ranuraInicio:
               parar = True
      return encontrado

    def __getitem__(self,clave):
        return self.obtener(clave)

    def __setitem__(self,clave,dato):
        self.agregar(clave,dato)


from collections import Counter
class Solution(object):
    def findTheDifference(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: str
        """
        # General Idea: Create a counter from data in s and then iterate through letters in t to find the one with count 0 from counter.

        a = Counter(s)
        b = Counter(t)
        for letter in t:
            if b[letter] == 0 or a[letter] != b[letter]:
                return letter


from copy import deepcopy
from typing import Any, Dict, List, Optional, Sequence, TypeVar, Union

import numpy as np
import tensorflow as tf
import torch

from fastestimator.backend._abs import abs
from fastestimator.backend._argmax import argmax
from fastestimator.backend._clip_by_value import clip_by_value
from fastestimator.backend._percentile import percentile
from fastestimator.backend._random_normal_like import random_normal_like
from fastestimator.backend._random_uniform_like import random_uniform_like
from fastestimator.backend._reduce_max import reduce_max
from fastestimator.backend._reduce_min import reduce_min
from fastestimator.backend._reduce_sum import reduce_sum
from fastestimator.network import Network
from fastestimator.op.tensorop.gather import Gather
from fastestimator.op.tensorop.gradient.gradient import GradientOp
from fastestimator.op.tensorop.gradient.watch import Watch
from fastestimator.op.tensorop.model.model import ModelOp
from fastestimator.util.traceability_util import traceable
from fastestimator.util.util import to_number
from fastestimator.util.base_util import to_list

Tensor = TypeVar('Tensor', tf.Tensor, torch.Tensor)
Model = TypeVar('Model', tf.keras.Model, torch.nn.Module)

@traceable()
class SaliencyNet:
    """A class to generate saliency masks from a given model.

    Args:
        model: The model, compiled with fe.build, which is to be inspected.
        model_inputs: The key(s) corresponding to the model inputs within the data dictionary.
        model_outputs: The key(s) corresponding to the model outputs which are written into the data dictionary.
        outputs: The keys(s) under which to write the generated saliency images.
    """
    def __init__(self,
                 model: Model,
                 model_inputs: Union[str, Sequence[str]],
                 model_outputs: Union[str, Sequence[str]],
                 outputs: Union[str, List[str]] = "saliency"):
        mode = "test"
        self.model_op = ModelOp(model=model, mode=mode, inputs=model_inputs, outputs=model_outputs, trainable=False)
        self.outputs = to_list(outputs)
        self.mode = mode
        self.gather_keys = ["SaliencyNet_Target_Index_{}".format(key) for key in self.model_outputs]
        self.network = Network(ops=[
            Watch(inputs=self.model_inputs, mode=mode),
            self.model_op,
            Gather(inputs=self.model_outputs,
                   indices=self.gather_keys,
                   outputs=["SaliencyNet_Intermediate_{}".format(key) for key in self.model_outputs],
                   mode=mode),
            GradientOp(inputs=self.model_inputs,
                       finals=["SaliencyNet_Intermediate_{}".format(key) for key in self.model_outputs],
                       outputs=deepcopy(self.outputs),
                       mode=mode),
        ])

    @property
    def model_inputs(self):
        return deepcopy(self.model_op.inputs)

    @property
    def model_outputs(self):
        return deepcopy(self.model_op.outputs)

    @staticmethod
    def _convert_for_visualization(tensor: Tensor, tile: int = 99) -> np.ndarray:
        """Modify the range of data in a given input `tensor` to be appropriate for visualization.

        Args:
            tensor: Input masks, whose channel values are to be reduced by absolute value summation.
            tile: The percentile [0-100] used to set the max value of the image.

        Returns:
            A (batch X width X height) image after visualization clipping is applied.
        """
        if isinstance(tensor, torch.Tensor):
            channel_axis = 1
        else:
            channel_axis = -1
        flattened_mask = reduce_sum(abs(tensor), axis=channel_axis, keepdims=True)

        non_batch_axes = list(range(len(flattened_mask.shape)))[1:]

        vmax = percentile(flattened_mask, tile, axis=non_batch_axes, keepdims=True)
        vmin = reduce_min(flattened_mask, axis=non_batch_axes, keepdims=True)

        return clip_by_value((flattened_mask - vmin) / (vmax - vmin), 0, 1)

    def get_masks(self, batch: Dict[str, Any]) -> Dict[str, Union[Tensor, np.ndarray]]:
        """Generates greyscale saliency mask(s) from a given `batch` of data.

        Args:
            batch: A batch of input data to be fed to the model.

        Returns:
            The model's classification decisions and greyscale saliency mask(s) for the given `batch` of data.
        """
        # Shallow copy batch since we're going to modify its contents later
        batch = {key: val for key, val in batch.items()}
        grads_and_preds = self._get_mask(batch)
        for key in self.outputs:
            grads_and_preds[key] = self._convert_for_visualization(grads_and_preds[key])
        return grads_and_preds

    def _get_mask(self, batch: Dict[str, Any]) -> Dict[str, Tensor]:
        """Generates raw saliency mask(s) from a given `batch` of data.

        This method assumes that the Network is already loaded.

        Args:
            batch: A batch of input data to be fed to the model.

        Returns:
            The model outputs and the raw saliency mask(s) for the given `batch` of data. Model predictions are reduced
            via argmax.
        """
        for key in self.gather_keys:
            # If there's no target key, use an empty array which will cause the max-likelihood class to be selected
            batch.setdefault(key, [])
        prediction = self.network.transform(data=batch, mode=self.mode)
        for key in self.model_outputs:
            prediction[key] = argmax(prediction[key], axis=1)
        return prediction

    def _get_integrated_masks(self, batch: Dict[str, Any], nsamples: int = 25) -> Dict[str, Tensor]:
        """Generates raw integrated saliency mask(s) from a given `batch` of data.

        This method assumes that the Network is already loaded.

        Args:
            batch: A batch of input data to be fed to the model.
            nsamples: How many samples to consider during integration.

        Returns:
            The raw integrated saliency mask(s) for the given `batch` of data.
        """
        model_inputs = [batch[ins] for ins in self.model_inputs]

        # Use a random uniform baseline as advised in https://distill.pub/2020/attribution-baselines/
        input_baselines = [
            random_uniform_like(ins, minval=reduce_min(ins), maxval=reduce_max(ins)) for ins in model_inputs
        ]
        input_diffs = [
            model_input - input_baseline for model_input, input_baseline in zip(model_inputs, input_baselines)
        ]

        response = {}

        for alpha in np.linspace(0.0, 1.0, nsamples):
            noisy_batch = {key: batch[key] for key in self.gather_keys}
            for idx, input_name in enumerate(self.model_inputs):
                x_step = input_baselines[idx] + alpha * input_diffs[idx]
                noisy_batch[input_name] = x_step
            grads_and_preds = self._get_mask(noisy_batch)
            for key in self.outputs:
                if key in response:
                    response[key] += grads_and_preds[key]
                else:
                    response[key] = grads_and_preds[key]

        for key in self.outputs:
            grad = response[key]
            for diff in input_diffs:
                grad = grad * diff
            response[key] = grad

        return response

    def get_smoothed_masks(self,
                           batch: Dict[str, Any],
                           stdev_spread: float = .15,
                           nsamples: int = 25,
                           nintegration: Optional[int] = None,
                           magnitude: bool = True) -> Dict[str, Union[Tensor, np.ndarray]]:
        """Generates smoothed greyscale saliency mask(s) from a given `batch` of data.

        Args:
            batch: An input batch of data.
            stdev_spread: Amount of noise to add to the input, as fraction of the total spread (x_max - x_min).
            nsamples: Number of samples to average across to get the smooth gradient.
            nintegration: Number of samples to compute when integrating (None to disable).
            magnitude: If true, computes the sum of squares of gradients instead of just the sum.

        Returns:
            Greyscale saliency mask(s) smoothed via the SmoothGrad method.
        """
        # Shallow copy batch since we're going to modify its contents later
        batch = {key: val for key, val in batch.items()}
        model_inputs = [batch[ins] for ins in self.model_inputs]
        stdevs = [to_number(stdev_spread * (reduce_max(ins) - reduce_min(ins))).item() for ins in model_inputs]

        # Adding noise to the image might cause the max likelihood class value to change, so need to keep track of
        # which class we're comparing to
        response = self._get_mask(batch)
        for gather_key, output_key in zip(self.gather_keys, self.model_outputs):
            batch[gather_key] = response[output_key]

        if magnitude:
            for key in self.outputs:
                response[key] = response[key] * response[key]

        for _ in range(nsamples - 1):
            noisy_batch = {key: batch[key] for key in self.gather_keys}
            clean_batch = {key: val for key, val in noisy_batch.items()}
            for idx, input_name in enumerate(self.model_inputs):
                noise = random_normal_like(model_inputs[idx], std=stdevs[idx])
                x_plus_noise = model_inputs[idx] + noise
                clean_batch[input_name] = model_inputs[idx]
                noisy_batch[input_name] = x_plus_noise
            grads_and_preds = self._get_mask(noisy_batch) if not nintegration else self._get_integrated_masks(
                clean_batch, nsamples=nintegration)  # Integration introduces its own noise pattern
            for name in self.outputs:
                grad = grads_and_preds[name]
                if magnitude:
                    response[name] += grad * grad
                else:
                    response[name] += grad
        for key in self.outputs:
            grad = response[key]
            response[key] = self._convert_for_visualization(grad / nsamples)
        return response

    def get_integrated_masks(self, batch: Dict[str, Any], nsamples: int = 25) -> Dict[str, Union[Tensor, np.ndarray]]:
        """Generates integrated greyscale saliency mask(s) from a given `batch` of data.

        See https://arxiv.org/abs/1703.01365 for background on the IntegratedGradient method.

        Args:
            batch: An input batch of data.
            nsamples: Number of samples to average across to get the integrated gradient.

        Returns:
            Greyscale saliency masks smoothed via the IntegratedGradient method.
        """
        # Shallow copy batch since we're going to modify its contents later
        batch = {key: val for key, val in batch.items()}

        # Performing integration might cause the max likelihood class value to change, so need to keep track of
        # which class we're comparing to
        response = self._get_mask(batch)
        for gather_key, output_key in zip(self.gather_keys, self.model_outputs):
            batch[gather_key] = response[output_key]

        response.update(self._get_integrated_masks(batch, nsamples=nsamples))
        for key in self.outputs:
            response[key] = self._convert_for_visualization(response[key])

        return response


import time

import cv2
import kivy
import numpy as np
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.properties import NumericProperty

# from kivy.uix.boxlayout import BoxLayout
# from kivy.uix.button import Button
from kivy.uix.camera import Camera
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image

# from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.scatter import Scatter
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.scrollview import ScrollView

# from kivy.uix.slider import Slider
from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.screen import MDScreen as Screen, MDScreen
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel as Label, MDLabel
from kivymd.uix.slider import MDSlider as Slider
from kivymd.uix.button import MDFlatButton as Button, MDRectangleFlatButton
from kivymd.uix.boxlayout import MDBoxLayout as BoxLayout, MDBoxLayout

import transform

class AndroidCamera(Camera):
    camera_resolution = (640 * 2, 480 * 2)
    counter = 0

    def _camera_loaded(self, *largs):
        self.texture = Texture.create(
            size=np.flip(self.camera_resolution), colorfmt="rgb"
        )
        self.texture_size = list(self.texture.size)

    def on_tex(self, *l):
        if self._camera._buffer is None:
            return None
        frame = self.frame_from_buf()

        self.frame_to_screen(frame)
        super(AndroidCamera, self).on_tex(*l)

    def frame_from_buf(self):
        buffer = self._camera._buffer
        if buffer is None:
            return None

        w, h = self.resolution
        frame = np.frombuffer(self._camera._buffer.tostring(), "uint8").reshape(
            (h + h // 2, w)
        )
        frame_bgr = cv2.cvtColor(frame, 93)
        return np.rot90(frame_bgr, 3)

    def frame_to_screen(self, frame):
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        cv2.putText(
            frame_rgb,
            str(self.counter),
            (20, 50),
            cv2.FONT_HERSHEY_SIMPLEX,
            1,
            (0, 255, 0),
            2,
            cv2.LINE_AA,
        )
        self.counter += 1
        flipped = np.flip(frame_rgb, 0)
        buf = flipped.tostring()
        self.texture.blit_buffer(buf, colorfmt="rgb", bufferfmt="ubyte")

class LinuxCamera(Camera):
    camera_resolution = (-1, -1)
    counter = 0

    def frame_from_buf(self):
        image = np.frombuffer(self.texture.pixels, np.uint8).reshape(
            *self.texture.size[::-1], 4
        )
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        return image

class CameraWidget(Camera):
    index = 0
    allow_stretch = True
    play = True

    def __new__(cls, *args, **kwargs):
        if kivy.platform != "linux":
            return AndroidCamera(
                *args, **kwargs, resolution=AndroidCamera.camera_resolution
            )
        else:
            return LinuxCamera(
                *args, **kwargs, resolution=LinuxCamera.camera_resolution
            )

def numpy_to_image(img, image: Image):
    h, w, _ = img.shape
    # texture = Texture.create(size=(w, h))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = np.flip(img, 0)
    image.texture = Texture.create(size=(w, h), colorfmt="rgb")
    image.texture.blit_buffer(img.flatten(), colorfmt="rgb", bufferfmt="ubyte")
    # return texture
    # return Image(size=(w, h), texture=texture)

class MyLayout(BoxLayout):
    orientation = "vertical"
    slider_val = NumericProperty(100)

    def __init__(self, **kwargs):

        super().__init__(**kwargs)
        self.data = {}
        layout = self
        self.camera = CameraWidget(allow_stretch=True)
        self.slider = Slider(min=50, max=500, value=100, hint_radius=20)
        self.slider.fbind("value", self.on_slider_val)
        self.label = Label(text=str(self.slider_val), size_hint=(0.2, 1.0))

        self.pictureA = Image(source="assets/template.jpg")
        self.pictureB = Image(source="assets/templateB.jpg")

        buttonA = MDRectangleFlatButton(text="Photo A", size_hint=(1, 1.0))
        buttonB = MDRectangleFlatButton(text="Photo B", size_hint=(1, 1.0))
        buttonMatch = MDRectangleFlatButton(text="Match", size_hint=(1, 1.0))

        layout.add_widget(self.camera)

        hlayout = MDBoxLayout(pos_hint={"center_x": 0.5}, size_hint=(1, 0.25))
        hlayout.add_widget(self.pictureA)
        hlayout.add_widget(self.pictureB)

        layout.add_widget(hlayout)

        hlayout = BoxLayout(orientation="horizontal", size_hint=(1.0, 0.15))
        hlayout.add_widget(self.slider)
        layout.add_widget(hlayout)

        hlayout = MDBoxLayout(size_hint=(1.0, 0.15), spacing=5, padding=5)
        hlayout.add_widget(buttonA)
        hlayout.add_widget(buttonB)
        hlayout.add_widget(buttonMatch)
        layout.add_widget(hlayout)

        buttonA.bind(on_press=self.take_photo_A)
        buttonB.bind(on_press=self.take_photo_B)
        buttonMatch.bind(on_press=self.match)

    def on_slider_val(self, instance, val):
        self.label.text = str(int(val))

    def analyse_photo(self, key, picture):

        image = self.camera.frame_from_buf()

        orb = cv2.SIFT_create()
        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        # gray = cv2.resize(gray, (512, 512))
        keypoint, des = orb.detectAndCompute(gray, None)
        img_final = cv2.drawKeypoints(
            image, keypoint, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
        )

        self.data[key] = (keypoint, des, image)
        numpy_to_image(img_final, picture)

    def take_photo_A(self, *args):
        self.analyse_photo("A", self.pictureA)

    def take_photo_B(self, *args):
        self.analyse_photo("B", self.pictureB)

    def match(self, *args):

        # BFMatcher with default params
        if "A" not in self.data:
            return
        if "B" not in self.data:
            return

        kp1, des1, img1 = self.data["A"]
        kp2, des2, img2 = self.data["B"]

        FLANN_INDEX_KDTREE = 0
        index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
        search_params = dict(checks=50)

        bf = cv2.FlannBasedMatcher(index_params, search_params)
        matches = bf.knnMatch(des1, des2, k=2)

        # Apply ratio test
        good = []
        for m, n in matches:
            if m.distance < 0.7 * n.distance:
                good.append(m)

        src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
        dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)

        M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
        matchesMask = mask.ravel().tolist()

        if sum(matchesMask) > self.slider.value:

            h, w, _ = img1.shape
            pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(
                -1, 1, 2
            )
            dst = cv2.perspectiveTransform(pts, M)
            print("Homography", M)
            print("mask", sum(mask))

            print("img2", img2.shape, img2.dtype)
            print("dst", dst)
            color = (255, 0, 0)
            points = np.int32(dst).reshape([-1, 1, 2])

            # img3 = cv2.polylines(img2, pts=[points], isClosed=True, color=color, thickness=3, lineType=cv2.LINE_AA)

            img3, _ = transform.cv_blend_images(img2, img1, np.linalg.inv(M))

        else:
            good = [[p] for p, m in zip(good, matchesMask) if m == 1]

            img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, flags=2, outImg=None)

        render = Image()
        numpy_to_image(img3, render)

        root = ScrollView(do_scroll_x=False, do_scroll_y=False)

        scatter = Scatter(do_rotation=False)
        # dismiss = Button(text="close", on_press = self.popup.dismiss())
        # image = Image(source='sun.jpg')
        scatter.add_widget(render)
        scatter.scale = 10
        root.add_widget(scatter)

        def play(*args):
            print("Resuming")
            self.camera.play = True

        popup = Popup(
            title=f"Num matches: {sum(matchesMask)}", content=root, size_hint=(1, 0.8),
            on_dismiss = play
        )
        self.camera.play = False

        popup.open()
        popup.on_dismiss()

class MenuScreen(Screen):
    def __init__(self, **kw):
        super().__init__(**kw)
        layout = MyLayout()
        self.add_widget(layout)

class TrackerWidget(BoxLayout):
    orientation = "vertical"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        layout = self
        self.camera = CameraWidget(allow_stretch=True)
        self.image = Image(source="assets/template.jpg")
        layout.add_widget(self.image)

        nn_thresh = 0.7
        max_length = 15
        self.nn_thresh = nn_thresh
        self.min_length = 3
        import tracker

        # This class helps merge consecutive point matches into tracks.
        self.tracker = tracker.PointTracker(max_length, nn_thresh=nn_thresh)

        self.fe = cv2.SIFT_create(
            edgeThreshold=2, contrastThreshold=0.1, nOctaveLayers=2, sigma=0.5
        )
        # self.fe = cv2.ORB_create(1000, scoreType=cv2.ORB_FAST_SCORE)

        self.edgeThreshold = Slider(min=10, max=100, value=20, hint_radius=20)
        self.edgeThreshold.fbind("value", self.on_slider_val)

        self.contrastThreshold = Slider(min=5, max=100, value=10, hint_radius=20)
        self.contrastThreshold.fbind("value", self.on_slider_val)

        self.nOctaveLayers = Slider(min=1, max=8, value=2, hint_radius=20)
        self.nOctaveLayers.fbind("value", self.on_slider_val)

        self.sigma = Slider(min=10, max=200, value=50, hint_radius=20)
        self.sigma.fbind("value", self.on_slider_val)

        self.on_slider_val(None, None)

        slayout = MDBoxLayout(orientation="vertical", size_hint=(1, 0.2), padding=20)
        slayout.add_widget(self.edgeThreshold)
        slayout.add_widget(self.contrastThreshold)
        slayout.add_widget(self.nOctaveLayers)
        slayout.add_widget(self.sigma)
        layout.add_widget(slayout)

        Clock.schedule_interval(self.track_movement, 0.05)

    def on_slider_val(self, instance, val):
        print(
            f"sigma = {self.sigma.value / 100.0} "
            f"nOctaveLayers={int(self.nOctaveLayers.value)} "
            f"contrastThreshold={self.contrastThreshold.value / 1000} "
            f"edgeThreshold={self.edgeThreshold.value / 10}"
        )
        self.fe = cv2.SIFT_create(
            edgeThreshold=self.edgeThreshold.value / 10,
            contrastThreshold=self.contrastThreshold.value / 1000,
            nOctaveLayers=int(self.nOctaveLayers.value),
            sigma=self.sigma.value / 100.0,
        )

    def track_movement(self, dt):

        image = self.camera.frame_from_buf()
        if image is None:
            return

        image = cv2.resize(image, (200, 200), interpolation=cv2.INTER_CUBIC)

        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        start1 = time.time()
        pts, desc = self.fe.detectAndCompute(gray, None)

        if desc is None:
            return None

        pts = np.array([[*p.pt, 0.0] for p in pts])
        pts = pts.T
        desc = desc.T.astype(np.float32)

        desc /= np.linalg.norm(desc, axis=0)[np.newaxis, :]
        desc = np.concatenate([pts, desc], axis=0)

        self.tracker.update(pts, desc)

        # Get tracks for points which were match successfully across all frames.
        tracks = self.tracker.get_tracks(self.min_length)

        # Font parameters for visualizaton.
        font = cv2.FONT_HERSHEY_DUPLEX
        font_clr = (255, 255, 255)
        font_pt = (4, 20)
        font_sc = 0.8

        end1 = time.time()
        delta = end1 - start1

        # Primary output - Show point tracks overlayed on top of input image.
        out1 = (np.dstack((gray, gray, gray))).astype('uint8')
        tracks[:, 1] /= float(self.nn_thresh)  # Normalize track scores to [0,1].
        self.tracker.draw_tracks(out1, tracks)

        out1 = cv2.resize(out1, self.camera.texture_size)
        cv2.putText(out1, f'Point Tracks - {delta*1000:.2f} [ms]', font_pt, font, font_sc, font_clr, lineType=16)

        # out2 = (np.dstack((gray, gray, gray))).astype("uint8")
        # for pt in pts.T:
        #     pt1 = (int(round(pt[0])), int(round(pt[1])))
        #     cv2.circle(out2, pt1, 1, (0, 255, 0), -1, lineType=16)
        #
        # out2 = cv2.resize(out2, self.camera.texture_size)
        # cv2.putText(
        #     out2, f"Raw Point Detections - {delta*1000:.2f} [ms]", font_pt, font, font_sc, font_clr, lineType=16
        # )

        numpy_to_image(out1, self.image)

class MenuScreen2(Screen):
    def __init__(self, **kw):
        super().__init__(**kw)
        layout = TrackerWidget()
        self.add_widget(layout)

if kivy.platform == "linux":
    from kivy.core.window import Window

    Window.size = (480, 800)

class MyApp(MDApp):
    def build(self):

        sm = ScreenManager()
        sm.add_widget(MenuScreen(name='menu1'))
        # sm.add_widget(MenuScreen2(name="menu2"))

        return sm

if __name__ == "__main__":
    MyApp().run()


from chatterbot import ChatBot
# from chatterbot.trainers import ListTrainer
# from googletrans import Translator
# import chatterbot
# from chatterbot.comparisons import LevenshteinDistance
# from chatterbot.response_selection import get_first_response

# translator = Translator()

jarvis = ChatBot(
    name='Jarvis',
    read_only=True,
    ogic_adapters=[
        'chatterbot.logic.MathematicalEvaluation',
        'chatterbot.logic.BestMatch',
        'chatterbot.logic.TimeLogicAdapter'

    ],
    database_uri='sqlite:///db.sqlite3',

)

# def Train():
#     input_file = open('./data_train_jarvis.txt', "r")
#     data_talk = input_file.readlines()
#     # print(data_talk)
#     list_trainer = ListTrainer(jarvis)
#     list_trainer.train(data_talk)
#     input_file.close()

# def EnToVi(text):
#     text = translator.translate(text, src='en', dest='vi')
#     return text.text

# def ViToEn(text):
#     text = translator.translate(text, src='vi', dest='en')
#     return text.text

# def Chat():
#     reply = input("Reply: ")
#     i= ViToEn(reply)
#     jarvis_rep = jarvis.get_response(i)
#     t = EnToVi(jarvis_rep)
#     # print(t)
#     # print("Jarvis: ", t, "   ("+i+") (",jarvis_rep,")")
#     print("Jarvis: ", t)

def InputChat(message):
    # i= ViToEn(message)
    jarvis_rep = jarvis.get_response(message)
    # t = EnToVi(jarvis_rep)
    return jarvis_rep

# def Start():
#     print("Jarvis: Xin chào")
#     while 1==1:
#         Chat()

# Start()
# Train()
# print(jarvis.get_response("who are you?"))
# print(translator.translate("xin chào.", src='vi', dest='en'))


from __future__ import annotations
from collections.abc import Iterator
from contextlib import contextmanager
from subprocess import Popen

@contextmanager
def kill_on_error(p: Popen, timeout: float = 5) -> Iterator[Popen]:
    """
    When used like so::

        with kill_on_error(subprocess.Popen(...), timeout=...) as p:
            ...

    then the subprocess ``p``, in addition to being waited for on normal
    context manager exit, will be terminated if an error occurs in the body of
    the ``with:`` block; if it doesn't exit after ``timeout`` seconds, it will
    instead be killed.
    """

    with p:
        try:
            yield p
        except BaseException:
            p.terminate()
            try:
                p.wait(timeout)
            except TimeoutError:
                p.kill()
            raise


import bpy
from .base import EffectBase
from .widgets import exportbox, maskbox

class EffectGlow(EffectBase):
    @classmethod
    def getName(cls):
        return "Glow"

    @classmethod
    def add(cls, context, richstrip, data, effect):
        cls.enterEditMode(richstrip)

        richstrip.sequences.get(data.Effects[-2].EffectStrips[-1].value).select = True
        glowlayer = cls.addBuiltinEffectStrip_ClassLevel(context, richstrip, effect, 'GLOW', "glow")
        adjustlayer = cls.addBuiltinEffectStrip_ClassLevel(context, richstrip, effect, 'ADJUSTMENT', "adjust")

        modifier = glowlayer.modifiers.new(cls.genRegularStripName(data.RichStripID, effect.EffectId, "mask"), 'MASK')
        modifier.input_mask_type = 'ID'

        cls.addExportProperty(effect, [
            ["threshold", "glow", "threshold", False ],
            ["clamp", "glow", "clamp", False ],
            ["boost_factor", "glow", "boost_factor", False ],
            ["blur", "glow", "blur_radius", False ],
            ["quality", "glow", "quality", False ]
        ])
        cls.addBoolProperty(effect, "mask_through", False)

        cls.leaveEditMode(data)
        return

    @classmethod
    def relink(cls, context, richstrip, effect):
        return

    @classmethod
    def draw(cls, context, layout:bpy.types.UILayout, data, effect, richstrip):
        glowlayer = cls.getEffectStrip(richstrip, effect, "glow")

        box = layout.box()
        box.label(text="Glow", icon="LIGHT")
        exportbox.draw(box, richstrip, "threshold", glowlayer, "threshold", text="Threshold")
        exportbox.draw(box, richstrip, "clamp", glowlayer, "clamp", text="Clamp")
        exportbox.draw(box, richstrip, "boost_factor", glowlayer, "boost_factor", text="Boost Factor")
        box.prop(glowlayer, "use_only_boost", toggle=0)

        layout.separator()

        box = layout.box()
        box.label(text="Blur", icon="MATFLUID")
        exportbox.draw(box, richstrip, "blur", glowlayer, "blur_radius", text="Blur Radius")
        exportbox.draw(box, richstrip, "quality", glowlayer, "quality", text="Quality")

        layout.separator()

        maskbox.draw(layout, effect, data, glowlayer.modifiers.get(cls.genRegularStripName(data.RichStripID, effect.EffectId, "mask")), cls.getBoolProperty(effect, "mask_through"))

    @classmethod
    def update(cls, type, identify, context, data, effect, richstrip):
        if type == 'MASK_SET':
            mask_modifier = cls.getEffectStrip(richstrip, effect, "glow").modifiers.get(cls.genRegularStripName(data.RichStripID, effect.EffectId, "mask"))
            mask_modifier.input_mask_id = bpy.data.masks[identify]

        if type == 'BOOL':
            if identify == "mask_through":
                glowlayer = cls.getEffectStrip(richstrip, effect, "glow")
                glowlayer.blend_type = 'ALPHA_OVER' if cls.getBoolProperty(effect, identify).value else 'REPLACE'