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)


import pandas as pd
import numpy as np

import azure.functions as func

TEMPERATURE = "temperature"
HUMIDITY = "humidity"
TIMESTAMP = "timestamp"
VALUE = "value"

POOR_CONDITIONS = 0
TOLERABLE_CONDITIONS = 1
GOOD_CONDITIONS = 2

def main(req: func.HttpRequest) -> func.HttpResponse:

    try:
        # Convert request to json
        req_body = req.get_json()

        # If requst consists all of required keys,
        # convert it to series and process using process_environment
        # method.
        if TEMPERATURE in req_body and HUMIDITY in req_body:
            temperature = dict_to_series(req_body[TEMPERATURE])
            humidity = dict_to_series(req_body[HUMIDITY])

            kpi = process_environment(temperature, humidity)

    except ValueError as ex:
        return func.HttpResponse(status_code=401, body=str(ex))

    return func.HttpResponse(
        status_code=200, body=str({
            "Warunki srodowiskowe": kpi
        })
    )

def dict_to_series(post_request: dict) -> pd.Series:
    """
    Convert given dict input to pandas.Series collection.
    """
    df = pd.DataFrame(post_request)
    df[TIMESTAMP] = pd.to_datetime(df[TIMESTAMP])
    df = df.set_index(TIMESTAMP)
    series = df.squeeze('columns')
    return series

def process_environment(temperature: pd.Series, humidity: pd.Series) -> int:
    """
    Returns a status of environmental conditions based on given
    temperature and humidity.

    Args:
        temperature - pd.Series
        humidity - pd.Series

    Returns:
        int:
            0 - poor conditions
            1 - tolerable conditions
            2 - good conditions
    """
    mean_temperature = np.mean(temperature)
    mean_humidity = np.mean(humidity)

    if 20 <= mean_temperature < 50 or 15 <= mean_humidity < 40:
        return GOOD_CONDITIONS
    elif 0 <= mean_temperature < 20 or 50 <= mean_temperature < 70 \
        or 10 <= mean_humidity < 15 or 40 <= mean_humidity < 50:
        return TOLERABLE_CONDITIONS
    else:
        return POOR_CONDITIONS


# Pandas is used for data manipulation
import pandas as pd
# Read in data and display first 5 rows
train = pd.read_csv('C:/Users/TheUnlikelyMonk/Desktop/Analytics Vidhya/LTFS/newtraindata.csv')
train.head(5)
print('The shape of our features is:', train.shape)
test = pd.read_csv('C:/Users/TheUnlikelyMonk/Desktop/Analytics Vidhya/LTFS/newtestdata.csv')
test.shape

# One-hot encode the data using pandas get_dummies
features = pd.get_dummies(train)
# Display the first 5 rows of the last 12 columns
train.iloc[:,5:].head(5)

feature_list = list(train.columns)
print(feature_list)

# Use numpy to convert to arrays
import numpy as np
# Labels are the values we want to predict
labels = np.array(train['loan_default'])
# Remove the labels from the features
# axis 1 refers to the columns
features= train.drop('loan_default', axis = 1)
# Saving feature names for later use
feature_list = list(train.columns)
# Convert to numpy array
features = np.array(train)

train = train.drop(['Unnamed: 0'], axis=1)
test = test.drop(['Unnamed: 0'], axis=1)

x = train.drop(['loan_default'], axis=1)
y = train['loan_default'];

# Import the model we are using
from sklearn.ensemble import RandomForestRegressor
# Instantiate model with 1000 decision trees
rf = RandomForestRegressor(n_estimators = 1000, random_state = 42)
# Train the model on training data
rf.fit(x,y);

#import pickle
#filename = 'randomforest.sav'
#pickle.dump(rf, open(filename, 'wb'))

# Use the forest's predict method on the test data
predictions = rf.predict(test)
# Calculate the absolute errors
#errors = abs(predictions - test_labels)
# Print out the mean absolute error (mae)
#print('Mean Absolute Error:', round(np.mean(errors), 2), 'degrees.')
#Mean Absolute Error: 3.83 degrees.

print(predictions)
pred = []
for i in predictions:
    if i < 0.5:
        pred.append(0)
    else:
        pred.append(1)

fp = open("out.csv","w")
fp.write(repr(pred))


import numpy as np
from utils import draw_save_plane_with_points

if __name__ == "__main__":

    # load data, total 130 points inlcuding 100 inliers and 30 outliers
    noise_points = np.loadtxt("HM1_ransac_points.txt")
    size = len(noise_points[..., 0])
    #RANSAC
    # we recommend you to formulate the palnace function as:  A*x+B*y+C*z+D=0

    sample_time = 11  #more than 99.9% probability at least one hypothesis does not contain any outliers
    distance_threshold = 0.05

    # sample points group
    index = np.random.choice(size, sample_time * 3)
    sample = noise_points[index].reshape((sample_time, 3, 3))
    #x1=sample[-1,0,0] y1=sample[-1,0,1] z1=sample[-1,0,2]
    # estimate the plane with sampled points group
    # a,b,c,d
    ans = np.zeros((sample_time, 4))
    #a=(y2-y1)*(z3-z1)-(y3-y1)*(z2-z1)
    ans[...,
        0] = (sample[..., 1, 1] -
              sample[..., 0, 1]) * (sample[..., 2, 2] - sample[..., 0, 2]) - (
                  sample[..., 2, 1] - sample[..., 0, 1]) * (sample[..., 1, 2] -
                                                            sample[..., 0, 2])
    #b=(z2-z1)*(x3-x1)-(z3-z1)*(x2-x1)
    ans[...,
        1] = (sample[..., 1, 2] -
              sample[..., 0, 2]) * (sample[..., 2, 0] - sample[..., 0, 0]) - (
                  sample[..., 2, 2] - sample[..., 0, 2]) * (sample[..., 1, 0] -
                                                            sample[..., 0, 0])
    #c=(x2-x1)*(y3-y1)-(x3-x1)*(y2-y1)
    ans[...,
        2] = (sample[..., 1, 0] -
              sample[..., 0, 0]) * (sample[..., 2, 1] - sample[..., 0, 1]) - (
                  sample[..., 2, 0] - sample[..., 0, 0]) * (sample[..., 1, 1] -
                                                            sample[..., 0, 1])
    #d=-a*x1-b*y1-c*z1
    ans[..., 3] = -(ans[..., 0] * sample[..., 0, 0]) - (
        ans[..., 1] * sample[..., 0, 1]) - (ans[..., 2] * sample[..., 0, 2])
    #evaluate inliers (with point-to-plance distance < distance_threshold)
    dis = ans[..., 0:3] @ (noise_points.T)
    dis = dis / np.tile(
        (np.sqrt(ans[..., 0]**2 + ans[..., 1]**2 + ans[..., 3]**2)).reshape(
            11, 1), size)
    threshold = np.sum(dis < distance_threshold, axis=1)
    is_max = np.argmax(threshold)
    # minimize the sum of squared perpendicular distances of all inliers with least-squared method
    pf = ans[is_max]
    print(pf)
    # draw the estimated plane with points and save the results
    # check the utils.py for more details
    # pf: [A,B,C,D] contains the parameters of palnace function  A*x+B*y+C*z+D=0

    draw_save_plane_with_points(pf, noise_points, "result/HM1_RANSAC_fig.png")
    np.savetxt("result/HM1_RANSAC_plane.txt", pf)


# %%
# importar librerias de analisis y visualización
import pandas as pd
import matplotlib.pyplot as plt
import folium
#import numpy as np
#import calmap
#import seaborn as sns
#import datetime

# -------------------------------------------------------------------
# Default config | insert code here
# -------------------------------------------------------------------
# put the default path here
path_ = "E:/PROGRAMACION/PYTHON/PROYECTOS_PYTHON/ANALISIS_DATOS/"

# put here last date from new data for instance "2021-09-23"
end_date_in = "2020-10-17"  # default "2020-10-17"

# if you change the start date for any reason put it here
start_date_in = "2020-01-22"  # default "2020-01-22"

# if you want to change the default country put ir here
country_in = "Colombia"  # default Colombia
# -------------------------------------------------------------------

print("""        ********************************************************
        +                                                      +
        +    Script para visualización de datos de Covid-19    +
        +                      COVIS 1.0                       +
        +                                                      +
        ********************************************************\n""")

# -------------------------------------------------------------------
# File type config | insert code here
# -------------------------------------------------------------------
try:
    path = input("Selecciona la ruta o ENTER para ruta default: ")
    # CARGAR ARCHIVO active para habilitar diferente formato
    # df = pd.read_csv(f'{path}COVIS/input/Covid_confirmed.csv', delimiter=';') # csv
    df = pd.read_excel(f'{path}COVIS/input/Covid_template.xlsx')  # xlsx
    print("Ruta cargada leyendo datoos...\n")
except:
    path = path_
    # CARGAR ARCHIVO
    # df = pd.read_csv(f'{path}COVIS/input/Covid_confirmed.csv', delimiter=';') # csv
    df = pd.read_excel(f'{path}COVIS/input/Covid_template.xlsx')  # xlsx
    print("Ruta default cargada leyendo datoos...\n")

# -------------------------------------------------------------------

print(">>> Cargando datos...\n")

print(">>> Creando dataframe global...\n")

# muestra el dataframe original cargado
with pd.option_context('display.max_rows', 5, 'display.max_columns', 5):
    print(df)

# crear copia de dataframe para trabajar sin dañar el original
copy_df = df.copy()

print("\n>>> Creando dataframe casos mensuales por pais...")

# creando nuevas columnas para visualizcion en GDS
# cambiar valores nulos a str
copy_df.fillna("", inplace=True)

# creando columna para regiones en GDS
copy_df["Region"] = copy_df["Province/State"] + " " + copy_df["Country/Region"]

# creando columna para coordenadas en GDS
copy_df["lat_long"] = copy_df["Lat"].astype(
    str) + "," + copy_df["Long"].astype(str)

# reeordenar columnas del df para mejor visualización
cols_2 = copy_df.columns.to_list()
cols_2 = cols_2[0:4] + cols_2[-2:] + cols_2[4:-2]
df_completo = copy_df[cols_2]

# almacenar data de casos por fecha
casos = df_completo.iloc[:, 6:]

# crear nuevos dataframe para graficas
casos_pais = pd.concat([df_completo["Country/Region"], casos], axis=1)
casos_regiones = pd.concat([df_completo["Region"], casos], axis=1)
casos_coord = pd.concat([df_completo["lat_long"], casos], axis=1)

# cambiar  index de dataframe
tem_regiones = casos_regiones.set_index('Region')
tem_regiones.rename_axis(index=None, inplace=True)

# cambiar  index de dataframe
tem_coord = casos_coord.set_index('lat_long')
tem_coord.rename_axis(index=None, inplace=True)

# sumar los datos por regiones para encontrar el total por paises
tem_pais = casos_pais.groupby(["Country/Region"]).sum()
# eliminar nombre del index del df
tem_pais.rename_axis(index=None, inplace=True)

# transponer df para tener fechas en columnas
tem_pais_t = tem_pais.T
tem_regiones_t = tem_regiones.T
tem_coord_t = tem_coord.T

# lista para ciclos
list_tem_t = [tem_pais_t, tem_regiones_t, tem_coord_t]

# asignar nombre al index del df
for item in list_tem_t:
    item.rename_axis(index="index", inplace=True)

# crear nuevo indice para el dataframe y pasar la columna de date a valores
for item in list_tem_t:
    item["date"] = item.index

# crear copia de columna date para mappear meses
tem_pais_t['months'] = tem_pais_t['date']

# cambiar tipo de dato de la columna date
for item in list_tem_t:
    item['date'] = pd.to_datetime(item['date'])

# hasta aca estan listos los datos para crear cada una de las distintas graficas solicitadas, de este punto en adelante se mostrara la creacion de cada codigo

# 2. crear df para gráfica de casos acumulados de covid por meses por país

# reeordenar columnas del df para mejor visualización
cols = tem_pais_t.columns.to_list()
cols = cols[-2:] + cols[:-2]
tem_pais_t = tem_pais_t[cols]

# lista de paies del df
pais_list = tem_pais_t.columns.to_list()
pais_list = pais_list[2:]

# almacenando datos en diccionarios para crear dataframe
dict_pais = {}
for pais in pais_list:
    pais_sort = tem_pais_t[["date", "months", pais]]
    pais_sort_ = pais_sort.rename(columns={pais: 'casos/dia'})
    pais_sort_["Country/Region"] = pais
    pais_sort_.index = pais_sort_["date"]

    dict_pais[pais] = dict_pais.get(pais, pais_sort_)

# reduciendo datos de fechas para valores acumulados
list_country = []
for key, values in dict_pais.items():
    values_ = values.resample('M').max()

    list_country.append(values_)

# creado nuevo dataframe
final_df_meses = list_country[0].append(list_country[1:])
final_df_meses.rename_axis(index="index", inplace=True)

# creando columna meses para cada fecha en el dataframe
arr_tem = final_df_meses["months"].unique()
arr_tem.tolist().sort()
limiter = len(arr_tem)

# meses del año a mappear
'''meses = ["Enero","Febrero", "Marzo",
        "Abril", "Mayo", "Junio",
        "Julio", "Agosto", "Septiembre",
        "Octubre", "noviembre", "Diciembre"]
'''

# el valor de los meses debe ser numerico para que lo carge GoogleDataStudio
meses = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

# creamos un diccionario para mappear los valores de los meses a reemplazar
map_meses = {}
for key, values in zip(arr_tem, meses[0:limiter]):
    map_meses[key] = map_meses.get(key, values)

# reemplzamos meses en el dataframe
final_df_meses.months.replace(map_meses, inplace=True)

print(">>> Dataframe de casos mensuales creado \n")

# muestra el dataframe original cargado
with pd.option_context('display.max_rows', 5, 'display.max_columns', 5):
    print(final_df_meses)

print(">>> Guardando datos...")

# exportamos el data frame paravisualizarlo fuera de python meses
final_df_meses.to_csv(
    fr'{path_}COVIS\output\monthcases_CV19_by_country_2020.csv')

final_df_meses.to_excel(
    fr'{path_}COVIS\output\monthcases_CV19_by_country_2020.xlsx')

print(">>> Datos de casos mensuales por país guardados\n")

# %%
# ------------------------------------------------------------------
# 3. Crear dataframe para vizualizar casos de covid por días y regiones

# definir funcion para crear data frame a partir de una columna especifica
print(">>> Creando dataframe casos diarios por país")

def col_to_df(df_col):

    # lista de elementos en el df
    col_list = df_col.columns.to_list()
    list_col = col_list[:-1]
    list_df = []

    # almacenando datos en diccionarios para crear dataframe
    for item in list_col:
        # indexador
        ind = list_col.index(item)
        list_sort = df_col.iloc[:, [-1, ind]]
        list_sort_2 = list_sort.rename(columns={item: "casos/dia"})
        list_sort_2["default"] = item
        list_sort_2.index = list_sort_2["date"]
        list_df.append(list_sort_2)

    # creado nuevo dataframe casos por día
    final_df = list_df[0].append(list_df[1:])
    final_df.rename_axis(index="index", inplace=True)

    return final_df

# llamamos a la funcion para crear el cada df que necesitemos
regiones_df = col_to_df(tem_regiones_t)
coordenadas_df = col_to_df(tem_coord_t)

# cambiar nombre columna default
regiones_df.rename(columns={'default': 'region'}, inplace=True)
coordenadas_df.rename(columns={'default': 'Lat/long'}, inplace=True)

# ordenar dataframe
drop_reg = regiones_df.drop(["date", "casos/dia"], axis=1)
frames = [drop_reg, coordenadas_df]

full_dias_df = pd.concat(frames, axis=1)

print(">>> Dataframe de casos diarios por país creados\n")

# muestra el dataframe original cargado
with pd.option_context('display.max_rows', 5, 'display.max_columns', 5):
    print(full_dias_df)

print(">>> Guardando casos diarios por país ...")

# exportamos el data frame paravisualizarlo fuera de python dias
full_dias_df.to_csv(
    fr'{path_}COVIS\output\dailycases_CV19_by_country_large.csv')

full_dias_df.to_excel(
    fr'{path_}COVIS\output\dailycases_CV19_by_country_large.xlsx')

print(">>> Datos de casos diarios por país guardados\n")

# ----------------------------------------------------------------
# %%
# # 4. Crear dataframe para ver casos totales por país

# variables para ingreso de datos de usuario
cols_2 = df_completo.columns.to_list()
list_user = df_completo["Country/Region"].unique()
size_list = len(list_user)

print("-"*30)
print("\nGRAFICA DE CASOS DIARIOS POR PAIS")
print(f"""\nSi desea cambiar la fecha y el país de la gráfica de casos diarios, inroduzca un numero para selecionar fecha y el nombre del país:
    -> Fechas entre 6-{len(cols_2)}, 6-> {cols_2[6]} {len(cols_2)}-> {cols_2[-1]}
    -> Nombre país, Ejem: Colombia\n""")

print(
    f"¿Desea ver la lista de paises almacenados?, hay {size_list} paises en la lista.\n")

try:
    answer = str(input("Y/N o enter para default:")).upper()
    if answer == "Y":
        print(list_user)
    else:
        print("No se mostrará la lista de paises\n")
except:
    print("Default cargado\n")

print("\n>>> Ingrese datos de fecha y pais, o enter para cargar default: ")

try:
    # determina fecha de grafica
    date_g = int(input(f"Fechas (6-{len(cols_2)}): "))
    country_ = str(input("Nombre pais (Colombia): ")).capitalize()
    tester_var = tem_pais_t[country_]
    print(country_, date_g)
    print("\nDatos cargados\n")
except:
    print("**Valores errones, se cargarán datos defualt \n")
    date_g = -1  # -1 last date
    country_ = country_in
    print("País ->", country_)

indice = cols_2[date_g]
print(f"Fecha -> {indice}\n")

# -----------------------------------------------------------------
# 4.1 Grafica de casos acumulados de covid por pais

# creamos una funcion para vizulizar por pais

print(">>> Creando gráfica de acumulado de casos por día por país\n")

def country_graph(country, df, graph_tittle, x_label,
                  y_label, st_d, end_dt, s_fig, s_fig2):

    # filtering dataframe
    filtered_df = df[["date", country]].loc[df["date"].between(st_d, end_dt)]
    total_cases = filtered_df[country].loc[filtered_df["date"] == end_dt]
    total_cases = total_cases.iloc[0]

    # line plot
    plt.plot("date", country, data=filtered_df, markersize=7,
             color='skyblue', linewidth=2)

    # x values
    plt.xticks(
        horizontalalignment='right',
        fontweight='light',
        fontsize='9',
        rotation=90
    )

    # graph tittle
    plt.title(graph_tittle,
              fontweight='bold',
              fontsize=13,
              color='black'
              )

    # labels
    plt.xlabel(x_label, fontweight='bold', color='black',
               fontsize='11', horizontalalignment='center')
    plt.ylabel(y_label, fontweight='bold', color='black',
               fontsize='11', horizontalalignment='center')

    # graph padding
    plt.subplots_adjust(top=0.7)

    plt.savefig(s_fig, bbox_inches='tight')
    plt.savefig(s_fig2, bbox_inches='tight')

    plt.show()

    print(
        f"+ El total de casos de Covid-19 en {country} para el {end_dt} fue de {total_cases} infectados")

    return filtered_df

# generar grafica de casos por dia por pais segun se requiera
country = country_
data = tem_pais_t
tittle = f"Acumulado casos Covid-19 por día en {country} 2020"
y_label = "Casos por dia"
x_label = "Fecha"
start_date = start_date_in   # default date:  2020-01-22
end_date = end_date_in  # default date:  2020-10-17
save_fig = f"{path_}COVIS\output\{country}_dailycases_CV19_{start_date}_{end_date}.svg"
save_fig_2 = f"{path_}COVIS\output\{country}_dailycases_CV19_{start_date}_{end_date}.png"

print(f"País graficado -> {country}")
print(f"Fecha -> {indice}\n")

save_country = country_graph(
    country, data, tittle, x_label,
    y_label, start_date, end_date,
    save_fig, save_fig_2)

# guardando datos de grafica
save_country.to_csv(
    fr'{path_}COVIS\output\{country}_dailycases_CV19_{start_date}_{end_date}.csv')

save_country.to_excel(
    fr'{path_}COVIS\output\{country}_dailycases_CV19_{start_date}_{end_date}.xlsx')

print("\n>>> Datos y gráfica de acumulados de casos totales por país guardados en carpeta output")

print("-"*60)

# %%

# 5. creando data frame y grafica de casos totales por pais para vizualizar en folium leaflet

# como leaflet no tiene filtro se deja codigo listo para poder cambiar la grafica introduciendo un numero que corresponde a una fecha en el punto cuadro variable    date_g

print(">>> Creando dataframe de casos totales por país\n")

tem_3 = df_completo.copy()

# creando dataframe para visualizar
tem_3 = tem_3.iloc[:, [0, 1, 2, 3, 4, 5,  date_g]]

tem_3.fillna("", inplace=True)
tem_3.rename(columns={indice: 'Casos totales '}, inplace=True)

total_cases = tem_3

# colocando nombre al index para evitar error en GDS
total_cases.rename_axis(index="index", inplace=True)

save = indice.replace("/", '-')

print(">>> Dataframe de casos totales por país creados\n")

# muestra el dataframe original cargado
with pd.option_context('display.max_rows', 5, 'display.max_columns', 5):
    print(total_cases)

print(">>> Guardando casos totales por país ...\n")

# exportamos el data frame para visualizarlo fuera de python dias
total_cases.to_csv(
    rf"{path_}COVIS\output\totalcases_country_date_{save}.csv")

total_cases.to_excel(
    rf'{path_}COVIS\output\totalcases_country_date_{save}.xlsx')

print(">>> Datos de casos totales por país guardados\n")

# %%
# ----------------------------------------------------------------------

# 5.1 Crear bubble map de Leaflet y casos totales por país

print("\nGRAFICA DE CASOS GLOBALES ACUMULADOS")
print(f"""\nSe cargará la fecha seleccionada anteriormente para generar esta gráfica\n""")
print(f"Fecha de grafica -> {indice}\n")

resp = input("ENTER para continuar...")

print(">>> Creando mapa de Leaflet...")

# creamos plantilla de mapa
map = folium.Map(location=[4.5709, -74.2973],
                 tiles="OpenStreetMap", zoom_start=2)

# añadimos marcadores al mapa uno por uno por cada dato del df
for i in range(0, len(total_cases)):
    # parametros de ingreso de funcion mapa
    radio = total_cases.iloc[i]['Casos totales ']/230000
    locacion = [total_cases.iloc[i]['Lat'], total_cases.iloc[i]['Long']]
    reg, cas = total_cases.iloc[i][['Region', "Casos totales "]]
    pop = f" Región: {reg} | Casos Totales: {cas} infectados"

    folium.CircleMarker(
        location=locacion,
        popup=pop,
        radius=float(radio),
        color='crimson',
        fill=False,
        fill_color='crimson'
    ).add_to(map)

titulo = f"Casos totales de covid por paises en el {indice}."
title_html = f'''

{titulo}

             '''
map.get_root().html.add_child(folium.Element(title_html))

print(">>> Mapa de Leaflet creado \n")
print(map)

print("\n>>> Guardando mapa en formato html...")

# guardar mapa en formato html para visualizar en navegador
map.save(
    fr'{path_}COVIS\output\map_totalcases_country_date_{save}.html')

print(">>> El mapa de Leaflet ha sido guardado en la carpeta output puede visulizarlo en su navegador predeterminado\n")

# %%
print("Fin del análisis por ahora vaya a su carpeta output para ver los resultados | Disfruta => ...\n")

print("-"*60)
print("-"*60)

print("   *Script made by Hardroco | 2021 - MIT license.")

print("-"*60)
print("-"*60)

fin = input("Presione cualquier tecla para finalizar")

# fin


import os
import sys
import ctypes
import numpy as np
import math
import OpenGL.GL as gl
import OpenGL.GLUT as glut
from perlin_noise import PerlinNoise

# Initializing Global Parameters
noise = PerlinNoise()
fieldOfView = 80
byteOffset = 0
perlinNoiseFactor = 0.12

# Setting perlin Noise factor through Command Line Arguments
if len(sys.argv) == 2:
    perlinNoiseFactor = float(sys.argv[1])

# Paramters [ Mesh Size, Screen Reoslution, Scaling Size, Perlin Factor]
parameters = [48, 25, 8, 4, 4, perlinNoiseFactor]
startPoint = 0

# Vetex Shader Definition
vertexShaderCode = """
    attribute vec3 position;
    uniform mat4 transformationMatrix[2];
    uniform mat4 projectionMatrix;
    uniform mat4 translate;

    void main(){
        gl_Position = projectionMatrix * transformationMatrix[0] * transformationMatrix[1] * translate * vec4(position, 1.0);
    }
    """

# Fragment Shader Definition
fragmentShaderCode = """
    void main(){
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
    """

# -- Building Data --
def generateTerrain(value):
    global data
    global noise

    data = []

    halfDataX = int(value[0] * value[4] / 2)
    halfDataY = int(value[1] * value[4] / 2)

    scaledResolutionX = int(value[2] * value[4] / 2)
    scaledResolutionY = int(value[3] * value[4] / 2)

    startX = 0
    startY = 0
    for j in range(-halfDataY, halfDataY):
        for i in range(-halfDataX, halfDataX):
            iData = i / scaledResolutionX
            jData = j / scaledResolutionY
            nextiData = (i + 1) / scaledResolutionX
            nextjData = (j + 1) / scaledResolutionY
            data.append([iData, jData, mapNoise(noise([startX, startY]))])
            data.append([iData, nextjData, mapNoise(noise([startX, (startY + value[5])]))])
            data.append([nextiData, jData, mapNoise(noise([(startX + value[5]), startY]))])
            startX = startX + value[5]

        startY = startY + value[5]
        startX = 0

    data = np.array(data, dtype = np.float32)
    return data

# Animate function to create a dynamically flowing terrain through y-axis [called every 10 microsecond]
def animate(offset):
    global noise
    global byteOffset
    global parameters
    global fieldOfView

    offsetY = int(parameters[1] * parameters[4] / 2) + offset

    halfDataX = int(parameters[0] * parameters[4] / 2)

    scaledResolutionX = int(parameters[2] * parameters[4] / 2)
    scaledResolutionY = int(parameters[3] * parameters[4] / 2)

    noOfData = 2 * halfDataX * 3

    translation = generateTranslation([0.0, -offset/scaledResolutionY, 0.0])

    loc = gl.glGetUniformLocation(program, "translate")
    gl.glUniformMatrix4fv(loc, 1, gl.GL_TRUE, translation)

    projectionMatrix = generatePerspective()

    loc = gl.glGetUniformLocation(program, "projectionMatrix")
    gl.glUniformMatrix4fv(loc, 1, gl.GL_TRUE, projectionMatrix)

    data = []
    nextjData = (offsetY + 1) / scaledResolutionY
    jData = offsetY / scaledResolutionY
    startY = ((parameters[1] * parameters[4]) + offset) * parameters[5]
    startX = 0
    for i in range(-halfDataX, halfDataX):
        iData = i / scaledResolutionX
        nextiData = (i + 1) / scaledResolutionX
        data.append([iData, jData, mapNoise(noise([startX, startY]))])
        data.append([iData, nextjData, mapNoise(noise([startX, (startY + parameters[5])]))])
        data.append([nextiData, jData, mapNoise(noise([(startX + parameters[5]), startY]))])
        startX = startX + parameters[5]
    data = np.array(data, dtype = np.float32)

    rowBytes = noOfData * data.strides[0]
    maxBuffer = int(parameters[1] * parameters[4]) -1

    # Set Byte Offset to 0 on reaching maximum bytes
    if  byteOffset > maxBuffer:
        byteOffset = 0

    # Substitute the Buffer Data based on byte offset
    gl.glBufferSubData(gl.GL_ARRAY_BUFFER, byteOffset * rowBytes, rowBytes, data)

    byteOffset = byteOffset + 1

    # Call oneself every 10 microsecond
    glut.glutTimerFunc(10, animate, offset + 1)

# funtion to map noise values between Min and Max
def mapNoise(noiseValue, maxMap = 1, minMap = 0.75):
    if noiseValue > 0:
        return noiseValue * maxMap
    else:
        return noiseValue * minMap

# function to request and compiler shader slots from GPU
def createShader(source, type):
    # request shader
    shader = gl.glCreateShader(type)

    # set shader source using the code
    gl.glShaderSource(shader, source)

    gl.glCompileShader(shader)
    if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
        error = gl.glGetShaderInfoLog(shader).decode()
        print(error)
        raise RuntimeError(f"{source} shader compilation error")

    return shader

# function to build and activate program
def createProgram(vertex, fragment):
    program = gl.glCreateProgram()

    # attach shader objects to the program
    gl.glAttachShader(program, vertex)
    gl.glAttachShader(program, fragment)

    gl.glLinkProgram(program)
    if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
        print(gl.glGetProgramInfoLog(program))
        raise RuntimeError("Linking error")

    # Get rid of shaders (no more needed)
    gl.glDetachShader(program, vertex)
    gl.glDetachShader(program, fragment)

    return program

# Rotation Matrix Generator
def generateRotation(transformationData = None):
    if not transformationData or transformationData[0] == "":
        transformationMatrix = np.array(
            [
                1.0,0.0,0.0,0.0,
                0.0,1.0,0.0,0.0,
                0.0,0.0,1.0,0.0,
                0.0,0.0,0.0,1.0,
            ],
            np.float32,
        )
        return transformationMatrix
    cTheta = np.cos(transformationData[1] / 180 * math.pi)
    sTheta = np.sin(transformationData[1] / 180 * math.pi)

    # x - axis rotation
    if transformationData[0] == "pitch":
        transformationMatrix = np.array(
            [
                1.0,0.0,0.0,0.0,
                0.0,cTheta,-sTheta,0.0,
                0.0,sTheta,cTheta,0.0,
                0.0,0.0,0.0,1.0,
            ],
            np.float32,
        )

    # y - axis rotation
    elif transformationData[0] == "yaw":
        transformationMatrix = np.array(
            [
                cTheta,0.0,sTheta,0.0,
                0.0,1.0,0.0,0.0,
                -sTheta,0.0,cTheta,0.0,
                0.0,0.0,0.0,1.0,
            ],
            np.float32,
        )

    # z - axis rotation
    elif transformationData[0] == "roll":
        transformationMatrix = np.array(
            [
                cTheta,-sTheta,0.0,0.0,
                sTheta,cTheta,0.0,0.0,
                0.0,0.0,1.0,0.0,
                0.0,0.0,0.0,1.0,
            ],
            np.float32,
        )

    return transformationMatrix

# Translation Matrix Generator
def generateTranslation(translationData = None):
    if not translationData or translationData[0] == "":
        transformationMatrix = np.array(
            [
                1.0,0.0,0.0,0.0,
                0.0,1.0,0.0,0.0,
                0.0,0.0,1.0,0.0,
                0.0,0.0,0.0,1.0,
            ],
            np.float32,
        )

        return transformationMatrix

    transformationMatrix = np.array(
            [
                1.0,0.0,0.0,translationData[0],
                0.0,1.0,0.0,translationData[1],
                0.0,0.0,1.0,translationData[2],
                0.0,0.0,0.0,1.0,
            ],
            np.float32,
        )

    return transformationMatrix

# Perspective Projection Generatior based on Field of View
def generatePerspective():
    global fieldOfView

    fov = (fieldOfView * math.pi)/180
    tanFOVHalf = np.tan(fov / 2.0)
    f = 1/tanFOVHalf
    projectionMatrix = np.array([f, 0.0, 0.0, 0.0,
                                0.0, 1.0, 0.0, 0.0,
                                0.0, 0.0, 1.0, 0.0,
                                0.0, 0.0, 1.0, 1.0], dtype = np.float32)
    return projectionMatrix

# initialization function
def initialize():
    global program
    global data
    global parameters
    global vertexBuffer
    global fieldOfView

    # Enabling Depth Buffer Test
    gl.glEnable(gl.GL_DEPTH_TEST)
    gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
    gl.glClearColor(0.0, 0.0, 0.0, 0.0)
    gl.glLoadIdentity()

    # Initializing the Global Program
    program = createProgram(
        createShader(vertexShaderCode, gl.GL_VERTEX_SHADER),
        createShader(fragmentShaderCode, gl.GL_FRAGMENT_SHADER),
    )

    # building data
    data = generateTerrain(parameters)

    # generating projection, translation and rotation matrix
    projectionMatrix = generatePerspective()

    translationMatrix =generateTranslation([0, 0.0, 0.3])

    translation = generateTranslation()
    rotationMatrix = generateRotation(["pitch", 45])

    transformationMatrix = np.array([rotationMatrix, translationMatrix])

    # make program the default program
    gl.glUseProgram(program)

    # Generate Vexter Buffer
    vertexBuffer = gl.glGenBuffers(1)

    # Make this generated Buffer to be Array Buffer
    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vertexBuffer)

    # Bind the position attribute
    stride = data.strides[0]
    offset = ctypes.c_void_p(0)
    loc = gl.glGetAttribLocation(program, "position")
    gl.glEnableVertexAttribArray(loc)
    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vertexBuffer)
    gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)

    # Bind the transformation, rotation and projection matrices to vertex shader's uniforms
    loc = gl.glGetUniformLocation(program, "transformationMatrix")
    gl.glUniformMatrix4fv(loc, 2, gl.GL_TRUE, transformationMatrix)

    loc = gl.glGetUniformLocation(program, "projectionMatrix")
    gl.glUniformMatrix4fv(loc, 1, gl.GL_TRUE, projectionMatrix)

    loc = gl.glGetUniformLocation(program, "translate")
    gl.glUniformMatrix4fv(loc, 1, gl.GL_TRUE, translation)

    # Upload data
    gl.glBufferData(gl.GL_ARRAY_BUFFER, data.nbytes, data, gl.GL_DYNAMIC_DRAW)

def display():
    gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)

    # Set Drwaing mode to Lines instead of Fill
    gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)

    # Draw Traingular Meshes
    gl.glDrawArrays(gl.GL_TRIANGLES, 0, data.shape[0])
    glut.glutSwapBuffers()
    glut.glutPostRedisplay()

def reshape(width, height):
    gl.glViewport(0, 0, width, height)

# Exit on Escape
def keyboard(key, x, y):
    if key == b"\x1b":
        os._exit(1)

# Track the change in Mouse Pointer on Drag to modify Field of View
def mouse(x ,y):
    global startPoint
    global fieldOfView

    delX = x - startPoint

    if (delX == 0):
        fieldOfView = 70
    else:
        fieldOfView = (fieldOfView + (delX * 0.0050)) % 180

# GLUT init
glut.glutInit()
glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH)
glut.glutCreateWindow("Graphics Window")
glut.glutReshapeWindow(1920, 1080)
glut.glutReshapeFunc(reshape)

initialize()
animate(0)

glut.glutDisplayFunc(display)
glut.glutPostRedisplay()
glut.glutKeyboardFunc(keyboard)
glut.glutMotionFunc(mouse)

# enter the mainloop
glut.glutMainLoop()


import random
from typing import Optional

from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
from flamapy.core.operations import Sampling

from flamapy.metamodels.bdd_metamodel.models import BDDModel
from flamapy.metamodels.bdd_metamodel.operations import BDDProductsNumber

class BDDSampling(Sampling):
    """Uniform Random Sampling (URS) using a Binary Decision Diagram (BDD).

    This is an adaptation of
    [Heradio et al. 2021.
    Uniform and Scalable Sampling of Highly Configurable Systems.
    Empirical Software Engineering]
    which relies on counting-based sampling inspired in the original Knuth algorithm.

    This implementation supports samples with and without replacement,
    as well as samples from a given partial configuration.
    """

    def __init__(self, size: int, with_replacement: bool = False,
                 partial_configuration: Optional[Configuration] = None) -> None:
        self.result: list[Configuration] = []
        self.bdd_model = None
        self.size = size
        self.with_replacement = with_replacement
        self.partial_configuration = partial_configuration

    def execute(self, model: BDDModel) -> 'BDDSampling':
        self.bdd_model = model
        self.result = sample(self.bdd_model, self.size, self.with_replacement,
                             self.partial_configuration)
        return self

    def get_result(self) -> list[Configuration]:
        return self.result

    def sample(self, size: int, with_replacement: bool = False,
               partial_configuration: Optional[Configuration] = None) -> list[Configuration]:
        return sample(self.bdd_model, size, with_replacement, partial_configuration)

def sample(bdd_model: BDDModel, size: int, with_replacement: bool = False,
           partial_configuration: Optional[Configuration] = None) -> list[Configuration]:
    nof_configs = BDDProductsNumber(partial_configuration).execute(bdd_model).get_result()
    if size < 0 or (size > nof_configs and not with_replacement):
        raise ValueError('Sample larger than population or is negative.')

    configurations = []
    for _ in range(size):
        config = random_configuration(bdd_model, partial_configuration)
        configurations.append(config)

    if not with_replacement:
        set_configurations = set(configurations)
        while len(set_configurations) < size:
            config = random_configuration(bdd_model, partial_configuration)
            set_configurations.add(config)

    return list(set_configurations)

def random_configuration(bdd_model: BDDModel,
                         p_config: Optional[Configuration] = None) -> Configuration:
    # Initialize the configurations and values for BDD nodes with already known features
    values = {} if p_config is None else dict(p_config.elements.items())

    # Set the BDD nodes with the already known features values
    u_func = bdd_model.bdd.let(values, bdd_model.root)

    care_vars = set(bdd_model.variables) - values.keys()
    n_vars = len(care_vars)
    for feature in care_vars:
        # Number of configurations with the feature selected
        v_sel = bdd_model.bdd.let({feature: True}, u_func)
        nof_configs_var_selected = bdd_model.bdd.count(v_sel, nvars=n_vars - 1)
        # Number of configurations with the feature unselected
        v_unsel = bdd_model.bdd.let({feature: False}, u_func)
        nof_configs_var_unselected = bdd_model.bdd.count(v_unsel, nvars=n_vars - 1)

        # Randomly select or not the feature
        selected = random.choices([True, False],
                                  [nof_configs_var_selected, nof_configs_var_unselected],
                                  k=1)[0]

        # Update configuration and BDD node for the new feature
        values[feature] = selected
        u_func = bdd_model.bdd.let({feature: selected}, u_func)

        n_vars -= 1
    return Configuration(values)


"""Use canny edge detection to extract salient parts of the time series
"""
import logging
import numpy as np
from scipy.ndimage.filters import gaussian_filter1d
from scipy.ndimage import sobel

LOG = logging.getLogger("M.CannyEdge")

def detect_edge_scipy(data, sigma, lo=0.9, hi=0.99):
    """Apply canny edge detector from scipy image
    """
    from skimage import feature

    edge = np.empty_like(data)
    data = np.stack([data, data, data], axis=1)
    for row in range(data.shape[0]):
        _edge = feature.canny(
            data[row],
            sigma=sigma,
            low_threshold=lo,
            high_threshold=hi,
            use_quantiles=True,
        )
        edge[row] = _edge[1]
        del _edge
    return edge

def detect_edge(data, sigma, truncate, intermediate_out=None):
    """Apply gaussian filtering, sobel filtering, and threshold filtering
    """
    data = (
        _PipelinedArray(data, intermediate_out)
        .apply(
            lambda x: gaussian_filter1d(x, sigma, axis=1, truncate=truncate),
            label="Gaussian",
        )
        .apply(lambda x: sobel(x, axis=1), label="Sobel")
        # .apply(_non_maximum_suppression, label="Non-maximum Suppression")
        .apply(lambda x: _sigma_filter(x, 2), label="Filter by 2-sigma")
        .data
    )

    return data

def _sigma_filter(data, sigma):
    mean = np.mean(data, axis=1, keepdims=True)
    std = np.std(data, axis=1, keepdims=True)
    mask = np.abs(data - mean) > (sigma * std)
    return data * mask

def _non_maximum_suppression(array):
    larger_left = np.ones_like(array, dtype=np.bool)
    larger_right = np.ones_like(array, dtype=np.bool)
    less_left = np.ones_like(array, dtype=np.bool)
    less_right = np.ones_like(array, dtype=np.bool)

    larger_left[:, 1:] = array[:, 1:] > array[:, :-1]
    larger_right[:, :-1] = array[:, :-1] > array[:, 1:]
    less_left[:, 1:] = array[:, 1:] < array[:, :-1]
    less_right[:, :-1] = array[:, :-1] < array[:, 1:]

    maxima = larger_left & larger_right
    minima = less_left & less_right

    return array * (maxima | minima)

class _PipelinedArray(object):
    def __init__(self, original: np.ndarray, intermediate_out: dict = None):
        self.data = original
        self.intermediate_out = intermediate_out
        self._add_to_intermediate("orginial", original)

    def apply(self, func, label="Unamed"):
        """Applying a new function on the array
        """
        LOG.info("Pipelined Array applying funciton %s", label)
        self.data = func(self.data)
        self._add_to_intermediate(label, self.data)
        return self

    def _add_to_intermediate(self, label, data):
        if self.intermediate_out is not None:
            if label in self.intermediate_out:
                label = label + "+"
            self.intermediate_out[label] = np.copy(data)


#!/usr/bin/env python

import inspect
import os

import utils

# import git

################################################################################
## PATH
################################################################################
def mk_spath(sfname, makedirs=False):

    __file__ = inspect.stack()[1].filename
    if "ipython" in __file__:  # for ipython
        __file__ = "/tmp/fake.py"

    ## spath
    fpath = __file__
    fdir, fname, _ = split_fpath(fpath)
    sdir = fdir + fname + "/"
    spath = sdir + sfname

    if makedirs:
        os.makedirs(utils.general.split_fpath(spath)[0], exist_ok=True)

    return spath

def find_the_git_root_dir():
    repo = git.Repo(".", search_parent_directories=True)
    return repo.working_tree_dir

def split_fpath(fpath):
    """Split a file path to (1) the directory path, (2) the file name, and (3) the file extention
    Example:
        dirname, fname, ext = split_fpath('../data/01/day1/split_octave/2kHz_mat/tt8-2.mat')
        print(dirname) # '../data/01/day1/split_octave/2kHz_mat/'
        print(fname) # 'tt8-2'
        print(ext) # '.mat'
    """
    dirname = os.path.dirname(fpath) + "/"
    base = os.path.basename(fpath)
    fname, ext = os.path.splitext(base)
    return dirname, fname, ext


# Prepare ScanNet Data into the form we want

import os
import glob
import plyfile
import numpy as np
import multiprocessing as mp
import torch
import open3d as o3d
import argparse

def load_obj_with_normals(filepath):
    mesh = o3d.io.read_triangle_mesh(str(filepath))
    if not mesh.has_vertex_normals():
        mesh.compute_vertex_normals()
    coords = np.asarray(mesh.vertices)
    normals = np.asarray(mesh.vertex_normals)
    colors = np.asarray(mesh.vertex_colors)
    feats = np.hstack((colors, normals))

    return coords, feats

def newf(fn):
    fn2 = fn[:-3]+'labels.ply'
    coords, feats = load_obj_with_normals(fn)
    a = plyfile.PlyData().read(fn2)
    labels = remapper[np.array(a.elements[0]['label'])]
    torch.save((coords, feats, labels, fn.split('/')[-1]), fn[:-4]+'.pth')
    print(fn, fn2)

def newf_test(fn):
    coords, feats = load_obj_with_normals(fn)
    num_points = coords.shape[0]
    labels = np.zeros(num_points).astype(np.int32)
    torch.save((coords, feats, labels, fn.split('/')[-1]), fn[:-4] + '.pth')
    print(fn)

if __name__ == '__main__':
    # Map relevant classes to {0,1,...,19}, and ignored classes to -100
    parser = argparse.ArgumentParser(description="Prepare ScanNet Data")
    parser.add_argument("basepath", help="base path of the downloaded ScanNet dataset")
    parser.add_argument("split", help="The split that you want to parse (train/validation/test). For test, the labels are set to 0")
    args = parser.parse_args()
    data_path = args.basepath
    split = args.split
    if split != "test":
        scan_path = os.path.join(data_path, "scans")
    else:
        scan_path = os.path.join(data_path, "scans_test")

    with open("scannetv2_" + split + ".txt") as f:
        scans_name = [x.strip() for x in f.readlines()]

    print(len(scans_name))

    for scan_name in scans_name:
        print("copy scene: ", scan_name)
        ply_path = os.path.join(scan_path, scan_name, scan_name + "_vh_clean_2.ply")
        label_path = os.path.join(scan_path, scan_name, scan_name + "_vh_clean_2.labels.ply")
        segsjson_path = os.path.join(scan_path, scan_name, scan_name + "_vh_clean_2.0.010000.segs.json")
        aggjson_path = os.path.join(scan_path, scan_name, scan_name + ".aggregation.json")

        os.system('cp %s %s' % (ply_path, split))

        if split != "test":
            os.system('cp %s %s' % (label_path, split))
            os.system('cp %s %s' % (segsjson_path, split))
            os.system('cp %s %s' % (aggjson_path, split))

    remapper = np.ones(150)*(-100)
    for i, x in enumerate([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 24, 28, 33, 34, 36, 39]):
        remapper[x] = i

    files = sorted(glob.glob(os.path.join(data_path, '*/*_vh_clean_2.ply')))
    if split != 'test':
        files2 = sorted(glob.glob(os.path.join(data_path, '*/*_vh_clean_2.labels.ply')))
        assert len(files) == len(files2)
    p = mp.Pool(processes=mp.cpu_count())
    if split != 'test':
        p.map(newf, files)
    else:
        p.map(newf_test, files)
    p.close()
    p.join()


# Author: Jochen Gast <jochen.gast@visinf.tu-darmstadt.de>

import functools
import random

import torch
from torch.nn import functional as tf

from contrib.cmap import ColorMap
from contrib.middlebury import FlowToColor
from utils import summary
from visualizers import factory
from visualizers.visualizer import Visualizer

def random_indices(k, n):
    indices = list(range(n))
    random.shuffle(indices)
    return sorted(indices[0:k])

class GoProVisualizer(Visualizer):
    def __init__(self,
                 args,
                 model_and_loss,
                 optimizer,
                 param_scheduler,
                 lr_scheduler,
                 train_loader,
                 validation_loader,
                 images_every_n_epochs=5,
                 keep_num_training_steps=30,
                 keep_num_validation_steps=30,
                 track_epochs=False):
        super().__init__()
        self.args = args
        self.global_train_step = 0
        self.optimizer = optimizer
        self.param_scheduler = param_scheduler
        self.lr_scheduler = lr_scheduler
        self.validation_loader = validation_loader
        self.training_loader = train_loader
        self.images_every_n_epochs = images_every_n_epochs
        self.keep_num_training_steps = keep_num_training_steps
        self.keep_num_validation_steps = keep_num_validation_steps
        self.flow2rgb = FlowToColor()
        self.err2rgb = ColorMap(cmap='jet')
        self.do_track_epochs = track_epochs
        self.model = model_and_loss.model
        self.epoch = None
        self.total_epochs = None
        self.prefix = None
        self.track_epoch = None
        self.random_step_indices = None

    def on_epoch_init(self, lr, train, epoch, total_epochs):
        self.epoch = epoch
        self.total_epochs = total_epochs
        self.prefix = 'train' if train else 'valid'
        self.random_step_indices = []

        if train:
            summary.scalar('train/lr', lr[0], global_step=epoch)

        # we track the first 2 epochs and then some others
        # self.track_epoch = False
        self.track_epoch = self.do_track_epochs and (epoch <= 2) or (epoch % self.images_every_n_epochs) == 0
        if self.track_epoch:
            if train:
                if self.keep_num_training_steps > 0:
                    self.random_step_indices = random_indices(
                        k=self.keep_num_training_steps,
                        n=len(self.training_loader))
            else:
                if self.keep_num_validation_steps > 0:
                    self.random_step_indices = random_indices(
                        k=self.keep_num_validation_steps,
                        n=len(self.validation_loader))

    @staticmethod
    def downsample(x, rscale=2):
        h, w = x.size()[2:4]
        h0, w0 = [s // rscale for s in [h, w]]
        return tf.interpolate(x, (h0, w0))

    def on_step_finished(self, example_dict, model_dict, loss_dict, train, step, total_steps):
        if train:
            self.global_train_step += 1
            for key, value in loss_dict.items():
                summary.scalar('train/%s' % key, value, global_step=self.global_train_step)

        def from_basename(name, in_basename):
            return "{}_{}/{}".format(in_basename, self.prefix, name)

        # ------------------------------------------------------------------------------
        # We also track some epochs completely on a subset of simple
        # ------------------------------------------------------------------------------
        if self.track_epoch:
            global_step = self.global_train_step if train else self.epoch
            if step in self.random_step_indices:
                batch_idx = 0
                basename = example_dict['basename'][batch_idx]
                basename = basename.replace('/', '_')
                make_name = functools.partial(from_basename, in_basename=basename)

                # visualize inputs
                input1 = example_dict['input1'][batch_idx:batch_idx + 1, ...]
                input2 = example_dict['input2'][batch_idx:batch_idx + 1, ...]
                input3 = example_dict['input3'][batch_idx:batch_idx + 1, ...]
                input4 = example_dict['input4'][batch_idx:batch_idx + 1, ...]
                input5 = example_dict['input5'][batch_idx:batch_idx + 1, ...]
                summary.images(make_name('input1'), input1, global_step=global_step)
                summary.images(make_name('input2'), input2, global_step=global_step)
                summary.images(make_name('input3'), input3, global_step=global_step)
                summary.images(make_name('input4'), input4, global_step=global_step)
                summary.images(make_name('input5'), input5, global_step=global_step)

                # visualize target
                target1 = example_dict['target1'][batch_idx:batch_idx + 1, ...]
                summary.images(make_name('gt'), target1, global_step=global_step)

                # visualize output
                output1 = model_dict['output1'][batch_idx:batch_idx + 1, ...]
                summary.images(make_name('output'), output1, global_step=global_step)

                # visualize error
                b, _, h, w = output1.size()
                error1 = torch.sum((output1 - target1) ** 2, dim=1, keepdim=True)
                error1 = self.err2rgb(error1)
                summary.images(make_name('error'), error1, global_step=global_step)

                # visualize concatenated summary image
                x1 = torch.cat((input3, target1), dim=3)
                x2 = torch.cat((error1.float() / 255, output1), dim=3)
                x = torch.cat((x1, x2), dim=2)
                summary.images(make_name('summary'), self.downsample(x), global_step=global_step)

                # visualize warped images
                if 'warped1' in model_dict.keys():
                    warped1 = model_dict['warped1'][batch_idx:batch_idx + 1, ...]
                    warped2 = model_dict['warped2'][batch_idx:batch_idx + 1, ...]
                    warped4 = model_dict['warped4'][batch_idx:batch_idx + 1, ...]
                    warped5 = model_dict['warped5'][batch_idx:batch_idx + 1, ...]
                    summary.images(make_name('warped1'), warped1, global_step=global_step)
                    summary.images(make_name('warped2'), warped2, global_step=global_step)
                    summary.images(make_name('warped4'), warped4, global_step=global_step)
                    summary.images(make_name('warped5'), warped5, global_step=global_step)

    def on_epoch_finished(self, avg_loss_dict, train, epoch, total_epochs):
        if not train:
            for key, value in avg_loss_dict.items():
                summary.scalar('valid/%s' % key, value, global_step=epoch)

factory.register('GoProVisualizer', GoProVisualizer)