#!/usr/bin/env python
"""clashogram - Clash of Clans war moniting for telegram channels."""
import os
import time
import json
import shelve
import locale
import gettext
import hashlib

import click
import jdatetime
import pytz
import requests
from dateutil.parser import parse as dateutil_parse

gettext.bindtextdomain('messages',
                       localedir=os.path.join(
                           os.path.dirname(os.path.realpath(__file__)),
                           'locales'))
gettext.textdomain('messages')
_ = gettext.gettext

POLL_INTERVAL = 60

@click.command()
@click.option('--coc-token',
              help='CoC API token. Reads COC_API_TOKEN env var.',
              envvar='COC_API_TOKEN',
              prompt=True)
@click.option('--clan-tag',
              help='Tag of clan without hash. Reads COC_CLAN_TAG env var.',
              envvar='COC_CLAN_TAG',
              prompt=True)
@click.option('--bot-token',
              help='Telegram bot token. The bot must be admin on the channel.'
                   'Reads TELEGRAM_BOT_TOKEN env var.',
              envvar='TELEGRAM_BOT_TOKEN',
              prompt=True)
@click.option('--channel-name',
              help='Name of telegram channel for updates.'
                   'Reads TELEGRAM_CHANNEL env var.',
              envvar='TELEGRAM_CHANNEL',
              prompt=True)
@click.option('--mute-attacks',
              is_flag=True,
              help='Do not send attack updates.')
@click.option('--warlog',
              help='Warlog file path.',
              envvar='WARLOG',
              default='warlog.db',
              type=click.Path())
def main(coc_token, clan_tag, bot_token, channel_name, mute_attacks, warlog):
    """Publish war updates to a telegram channel."""
    coc_api = CoCAPI(coc_token)
    notifier = TelegramNotifier(bot_token, channel_name)

    with shelve.open(warlog, writeback=True) as db:
        dbwrapper = SimpleKVDB(db)
        monitor = WarMonitor(dbwrapper, coc_api, clan_tag, notifier)
        monitor.mute_attacks = mute_attacks
        try:
            monitor.start()
        finally:
            db.sync()
            db.close()

def serverless(db, coc_token, clan_tag, bot_token, channel_name):
    """Publish war updates to a telegram channel."""
    coc_api = CoCAPI(coc_token)
    notifier = TelegramNotifier(bot_token, channel_name)
    monitor = WarMonitor(db, coc_api, clan_tag, notifier)
    monitor.update()

def save_wardata(wardata):
    if wardata['state'] != 'notInWar':
        war_id = "{0}{1}".format(wardata['clan']['tag'][1:],
                                 wardata['preparationStartTime'])
        if not os.path.exists('warlog'):
            os.mkdir('warlog')
        path = os.path.join('warlog', war_id)
        json.dump(wardata,
                  open(path, 'w', encoding='utf-8'), ensure_ascii=False)

def save_latest_data(wardata, monitor):
    if wardata:
        save_wardata(wardata)
        json.dump(wardata,
                  open('latest_downloaded_wardata.json',
                       'w',
                       encoding='utf-8'),
                  ensure_ascii=False)

########################################################################
# Notifiers
########################################################################

class TelegramNotifier(object):
    def __init__(self, bot_token, channel_name):
        self.bot_token = bot_token
        self.channel_name = channel_name

    def send(self, msg, silent=False):
        endpoint = "https://api.telegram.org/bot{bot_token}/sendMessage?"\
                   "parse_mode={mode}&chat_id=@{channel_name}&text={text}"\
                   "&disable_notification={silent}"\
                   .format(bot_token=self.bot_token,
                           mode='HTML',
                           channel_name=self.channel_name,
                           text=requests.utils.quote(msg),
                           silent=silent)
        requests.post(endpoint)

########################################################################
# CoC API Calls
########################################################################

class CoCAPI(object):
    def __init__(self, coc_token):
        self.coc_token = coc_token

    def get_currentwar(self, clan_tag):
        return WarInfo(self._call_api(self._get_currentwar_endpoint(clan_tag)))

    def get_claninfo(self, clan_tag):
        return ClanInfo(self._call_api(self._get_claninfo_endpoint(clan_tag)))

    def _call_api(self, endpoint):
        s = requests.Session()
        res = s.get(endpoint,
                    headers={'Authorization': 'Bearer %s' % self.coc_token})
        if res.status_code == requests.codes.ok:
            return json.loads(res.content.decode('utf-8'))
        else:
            raise Exception('Error calling CoC API: %s' % res)

    def _get_currentwar_endpoint(self, clan_tag):
        return 'https://api.clashofclans.com/v1/clans/{clan_tag}/currentwar'\
            .format(clan_tag=requests.utils.quote(clan_tag))

    def _get_claninfo_endpoint(self, clan_tag):
        return 'https://api.clashofclans.com/v1/clans/{clan_tag}'.format(
                clan_tag=requests.utils.quote(clan_tag))

########################################################################
# Models according to CoC API
########################################################################

class ClanInfo(object):
    def __init__(self, clandata):
        self.data = clandata

    @property
    def location(self):
        return self.data['location']['name']

    @property
    def country_flag_imoji(self):
        if self.data['location']['isCountry']:
            return self._get_country_flag_imoji(
                    self.data['location']['countryCode'])
        elif self.data['location']['name'] == 'International':
            # The unicode character for planet earth, not empty string!
            return '🌎'
        else:
            return ''

    def _get_country_flag_imoji(self, country_code):
        return "{}{}".format(chr(127397 + ord(country_code[0])),
                             chr(127397 + ord(country_code[1])))

    @property
    def winstreak(self):
        return self.data['warWinStreak']

    @property
    def is_warlog_public(self):
        return self.data['isWarLogPublic']

class WarInfo(object):
    def __init__(self, wardata):
        self.data = wardata
        self.clan_members = {}
        self.opponent_members = {}
        self.players = {}
        self.ordered_attacks = None
        self._populate()

    @property
    def state(self):
        return self.data['state']

    @property
    def clan_tag(self):
        return self.data['clan']['tag']

    @property
    def op_tag(self):
        return self.data['opponent']['tag']

    @property
    def clan_name(self):
        return self.data['clan']['name']

    @property
    def op_name(self):
        return self.data['opponent']['name']

    @property
    def clan_level(self):
        return self.data['clan']['clanLevel']

    @property
    def op_level(self):
        return self.data['opponent']['clanLevel']

    @property
    def clan_destruction(self):
        return self.data['clan']['destructionPercentage']

    @property
    def op_destruction(self):
        return self.data['opponent']['destructionPercentage']

    @property
    def clan_stars(self):
        return self.data['clan']['stars']

    @property
    def op_stars(self):
        return self.data['opponent']['stars']

    @property
    def clan_attacks(self):
        return self.data['clan']['attacks']

    @property
    def op_attacks(self):
        return self.data['opponent']['attacks']

    @property
    def start_time(self):
        return self.data['startTime']

    @property
    def team_size(self):
        return self.data['teamSize']

    def _populate(self):
        if self.is_not_in_war():
            return
        for member in self.data['clan']['members']:
            self.clan_members[member['tag']] = member
            self.players[member['tag']] = member
        for opponent in self.data['opponent']['members']:
            self.opponent_members[opponent['tag']] = opponent
            self.players[opponent['tag']] = opponent
        self.ordered_attacks = self.get_ordered_attacks()

    def get_ordered_attacks(self):
        ordered_attacks = {}
        for player in self.players.values():
            for attack in self.get_player_attacks(player):
                ordered_attacks[attack['order']] = (player, attack)
        return ordered_attacks

    def get_player_attacks(self, player):
        if 'attacks' in player:
            return player['attacks']
        else:
            return []

    def get_player_info(self, tag):
        if tag not in self.players:
            raise Exception('Player %s not found.' % tag)
        return self.players[tag]

    def is_not_in_war(self):
        return self.data['state'] == 'notInWar'

    def is_in_preparation(self):
        return self.data['state'] == 'preparation'

    def is_in_war(self):
        return self.data['state'] == 'inWar'

    def is_war_over(self):
        return self.data['state'] == 'warEnded'

    def is_clan_member(self, player):
        return player['tag'] in self.clan_members

    def is_win(self):
        if self.data['clan']['stars'] > self.data['opponent']['stars']:
            return True
        elif self.data['clan']['stars'] == self.data['opponent']['stars'] and\
                (self.data['clan']['destructionPercentage'] >
                 self.data['opponent']['destructionPercentage']):
            return True
        else:
            return False

    def is_draw(self):
        return \
            self.data['clan']['stars'] == self.data['opponent']['stars'] and\
            (self.data['clan']['destructionPercentage'] ==
             self.data['opponent']['destructionPercentage'])

    def create_war_id(self):
        return "{0}{1}{2}".format(self.data['clan']['tag'],
                                  self.data['opponent']['tag'],
                                  self.data['preparationStartTime'])

########################################################################
# War statistics
########################################################################

class WarStats(object):
    def __init__(self, warinfo):
        self.warinfo = warinfo

    def calculate_war_stats_sofar(self, attack_order):
        """Calculate latest war stats.

        CoC data is updated every 10 minutes and reflects stats after the
        last attack. We have to calculate the necesssary info for the
        previous ones"""
        info = {}
        info['clan_destruction'] = 0
        info['op_destruction'] = 0
        info['clan_stars'] = 0
        info['op_stars'] = 0
        info['clan_used_attacks'] = 0
        info['op_used_attacks'] = 0
        for order in range(1, attack_order + 1):
            player, attack = self.warinfo.ordered_attacks[order]
            if self.warinfo.is_clan_member(player):
                info['clan_destruction'] +=\
                    self.get_attack_new_destruction(attack)
                info['clan_stars'] += self.get_attack_new_stars(attack)
                info['clan_used_attacks'] += 1
            else:
                info['op_destruction'] +=\
                    self.get_attack_new_destruction(attack)
                info['op_stars'] += self.get_attack_new_stars(attack)
                info['op_used_attacks'] += 1
        info['op_destruction'] /= self.warinfo.team_size
        info['clan_destruction'] /= self.warinfo.team_size
        return info

    def get_latest_war_stats(self):
        return {'clan_destruction': self.warinfo.clan_destruction,
                'op_destruction': self.warinfo.op_destruction,
                'clan_stars': self.warinfo.clan_stars,
                'op_stars': self.warinfo.op_stars,
                'clan_used_attacks': self.warinfo.clan_attacks,
                'op_used_attacks': self.warinfo.op_attacks,}

    def get_attack_new_destruction(self, attack):
        if (attack['destructionPercentage'] >
                self.get_best_attack_destruction_upto(attack)):
            return (attack['destructionPercentage'] -
                    self.get_best_attack_destruction_upto(attack))
        else:
            return 0

    def get_best_attack_destruction(self, attack):
        defender = self.warinfo.get_player_info(attack['defenderTag'])
        if 'bestOpponentAttack' in defender and\
                (defender['bestOpponentAttack']['attackerTag'] !=
                 attack['attackerTag']):
            return defender['bestOpponentAttack']['destructionPercentage']
        else:
            return 0

    def get_best_attack_destruction_upto(self, in_attack):
        best_score = 0
        for order in range(1, in_attack['order'] + 1):
            player, attack = self.warinfo.ordered_attacks[order]
            if attack['defenderTag'] == in_attack['defenderTag'] and\
               attack['destructionPercentage'] > best_score and\
               attack['attackerTag'] != in_attack['attackerTag']:
                best_score = attack['destructionPercentage']
        return best_score

    def get_attack_new_stars(self, attack):
        existing_stars = self.get_best_attack_stars_upto(attack)
        stars = attack['stars'] - existing_stars
        if stars > 0:
            return stars
        else:
            return 0

    def get_best_attack_stars_upto(self, in_attack):
        best_score = 0
        for order in range(1, in_attack['order'] + 1):
            player, attack = self.warinfo.ordered_attacks[order]
            if attack['defenderTag'] == in_attack['defenderTag'] and\
               attack['stars'] > best_score and\
               attack['attackerTag'] != in_attack['attackerTag']:
                best_score = attack['stars']
        return best_score

########################################################################
# Message formatters
########################################################################

class MessageFactory(object):
    def __init__(self, coc_api, warinfo):
        self.coc_api = coc_api
        self.warinfo = warinfo
        self.warstats = WarStats(warinfo)

    def create_preparation_msg(self):
        msg_template = _("""{top_imoji} {war_size} fold war is ahead!

▫ Clan {ourclan: <{cwidth}} L {ourlevel: <2} +{clanwinstreak} {clanloc}{clanflag}
▪ Clan {opponentclan: <{cwidth}} L {theirlevel: <2} +{opwinstreak} {oploc}{opflag}

Game begins at {start}.
Have fun! {final_emoji}
""")
        clan_extra_info = self.get_clan_extra_info(self.warinfo.clan_tag)
        op_extra_info = self.get_clan_extra_info(self.warinfo.op_tag)

        ourclan = self.warinfo.clan_name
        opclan = self.warinfo.op_name

        msg = msg_template.format(
                top_imoji='\U0001F3C1',
                ourclan=ourclan,
                ourlevel=self.warinfo.clan_level,
                opponentclan=opclan,
                theirlevel=self.warinfo.op_level,
                ourtag=self.warinfo.clan_tag,
                opponenttag=self.warinfo.op_tag,
                start=self.format_time(self.warinfo.start_time),
                war_size=self.warinfo.team_size,
                final_emoji='\U0001F6E1',
                clanloc=clan_extra_info.location,
                clanflag=clan_extra_info.country_flag_imoji,
                oploc=op_extra_info.location,
                opflag=op_extra_info.country_flag_imoji,
                clanwinstreak=clan_extra_info.winstreak,
                opwinstreak=op_extra_info.winstreak,
                cwidth=max(len(ourclan), len(opclan)))
        return msg

    def create_players_msg(self):
        msg = "⚪" + _(" Players")
        msg += "\n▪" + _("Position, TH, name")
        sorted_players_by_map_position = sorted(
            self.warinfo.clan_members.items(),
            key=lambda x: x[1]['mapPosition'])
        for player_tag, player_info in sorted_players_by_map_position:
            line = "▫{map_position: <2d} {thlevel: <2d} {name}"\
                .format(thlevel=player_info['townhallLevel'],
                        map_position=player_info['mapPosition'],
                        name=player_info['name'])
            msg += "\n" + line

        return "
" + msg + "
"

    def create_war_msg(self):
        return _('War has begun!')

    def create_clan_full_destruction_msg(self, attacker, attack, war_stats):
        return _('⚪ We destroyed them 100% boss!')

    def create_clan_attack_msg(self, member, attack, war_stats):
        return self.create_attack_msg(member, attack, war_stats,
                                      imoji='\U0001F535')

    def create_opponent_attack_msg(self, member, attack, war_stats):
        return self.create_attack_msg(member,
                                      attack, war_stats, imoji='\U0001F534')

    def create_attack_msg(self, member, attack, war_stats, imoji=''):
        msg_template = _("""
{top_imoji} [{order}] {ourclan} vs {opponentclan}
Attacker: TH {attacker_thlevel: <2} MP {attacker_map_position} {attacker_name}
Defender: TH {defender_thlevel: <2} MP {defender_map_position} {defender_name}
Result: {stars} | {destruction_percentage}%
{war_info}

""")
        defender = self.warinfo.get_player_info(attack['defenderTag'])
        msg = msg_template.format(
                order=attack['order'],
                top_imoji=imoji,
                ourclan=self.warinfo.clan_name,
                opponentclan=self.warinfo.op_name,
                attacker_name=member['name'],
                attacker_thlevel=member['townhallLevel'],
                attacker_map_position=member['mapPosition'],
                defender_name=defender['name'],
                defender_thlevel=defender['townhallLevel'],
                defender_map_position=defender['mapPosition'],
                stars=self.format_star_msg(attack),
                destruction_percentage=attack['destructionPercentage'],
                war_info=self.create_war_info_msg(war_stats),
                nwidth=max(len(member['name']), len(defender['name'])))
        return msg

    def format_star_msg(self, attack):
        new_stars = self.warstats.get_attack_new_stars(attack)
        cookies = (attack['stars'] - new_stars) * '🍪'
        stars = new_stars * '⭐'
        return cookies + stars

    def create_war_info_msg(self, war_stats):
        template = _("""▪ {clan_attack_count: >{atkwidth}}/{total} ⭐ {clan_stars: <{swidth}} ⚡ {clan_destruction:.2f}%
▪ {opponent_attack_count: >{atkwidth}}/{total} ⭐ {opponent_stars: <{swidth}} ⚡ {opponent_destruction:.2f}%""")

        clan_stars = war_stats['clan_stars']
        op_stars = war_stats['op_stars']
        clan_attack_count = war_stats['clan_used_attacks']
        op_attack_count = war_stats['op_used_attacks']

        return template.format(
            total=self.warinfo.team_size * 2,
            clan_attack_count=clan_attack_count,
            opponent_attack_count=op_attack_count,
            clan_stars=clan_stars,
            clan_destruction=war_stats['clan_destruction'],
            opponent_stars=op_stars,
            opponent_destruction=war_stats['op_destruction'],
            swidth=len(str(max(clan_stars, op_stars))),
            atkwidth=len(str(max(clan_attack_count, op_attack_count))))

    def create_opponent_full_destruction_msg(self, attacker, attack,
                                             war_stats):
        return _('⚫ They destroyed us 100% boss!')

    def create_war_over_msg(self):
        msg_template = _("""
{win_or_lose_title}
Clan {ourclan: <{cwidth}} L {ourlevel: <2}
Clan {opponentclan: <{cwidth}} L {theirlevel: <2}
{war_info}

""")

        ourclan = self.warinfo.clan_name
        opclan = self.warinfo.op_name
        msg = msg_template.format(
                win_or_lose_title=self.create_win_or_lose_title(),
                ourclan=ourclan,
                ourlevel=self.warinfo.clan_level,
                opponentclan=opclan,
                theirlevel=self.warinfo.op_level,
                war_info=self.create_war_info_msg(
                        self.warstats.get_latest_war_stats()),
                cwidth=max(len(ourclan), len(opclan)))
        return msg

    def create_win_or_lose_title(self):
        if self.warinfo.is_win():
            return "{} {}".format('🎉', _('We won!'))
        elif self.warinfo.is_draw():
            return "{} {}".format('🏳', _('It\'s a tie!'))
        else:
            return "{} {}".format('💩', _('We lost!'))

    def format_time(self, timestamp):
        utc_time = dateutil_parse(timestamp, fuzzy=True)
        langs = set([locale.getlocale()[0],
                    os.environ.get('LANG'),
                    os.environ.get('LANGUAGE')])
        if langs.intersection(['fa_IR', 'fa', 'fa_IR.UTF-8', 'Persian_Iran']):
            self.patch_jdatetime()
            tehran_time = utc_time.astimezone(pytz.timezone("Asia/Tehran"))
            fmt = jdatetime.datetime.fromgregorian(
                    datetime=tehran_time).strftime("%a، %d %b %Y %H:%M:%S")
            return self.convert_to_persian_numbers(fmt)
        return utc_time.strftime("%a, %d %b %Y %H:%M:%S")

    def patch_jdatetime(self):
        jdatetime.date._is_fa_locale = lambda self: True

    def convert_to_persian_numbers(self, text):
        # Supper intelligent and super efficient :)
        return text.replace('0', '۰')\
                   .replace('1', '۱')\
                   .replace('2', '۲')\
                   .replace('3', '۳')\
                   .replace('4', '۴')\
                   .replace('5', '۵')\
                   .replace('6', '۶')\
                   .replace('7', '۷')\
                   .replace('8', '۸')\
                   .replace('9', '۹')

    def get_clan_extra_info(self, clan_tag):
        return self.coc_api.get_claninfo(clan_tag)

########################################################################
# DB helper classes (mainly to faciliate serveress)
########################################################################

class SimpleKVDB(object):
    def __init__(self, db):
        self._db = db

    def __contains__(self, key):
        return key in self._db

    def __getitem__(self, key):
        return self._db[key]

    def __setitem__(self, key, value):
        self._db[key] = value
        self._db.sync()

########################################################################
# Main war monitor class
########################################################################

class WarMonitor(object):
    def __init__(self, db, api, tag, notifier):
        """Scan warlog for war updates.

        This is the top most class that puts everything together.
        Calling `start` method will block forever. Calling `update`
        will fetch one update, notify the changes and return.

        Arguments:
            db -- A persistant dictionary-like object.
            api -- Api object
            tag -- Clantag
            notifier -- Notifier object
        """
        self.db = db
        self.clan_tag = tag
        self.coc_api = api
        self.notifier = notifier
        self.warinfo = None
        self.msg_factory = None
        self.warstats = None
        self._mute_attacks = False

    @property
    def mute_attacks(self):
        return self._mute_attacks

    @mute_attacks.setter
    def mute_attacks(self, value):
        self._mute_attacks = value

    def update(self):
        warinfo = self.coc_api.get_currentwar(self.clan_tag)
        #save_latest_data(warinfo.data, monitor)
        if warinfo.is_not_in_war():
            if self.warinfo is not None:
                self.send_war_over_msg()
            self.reset()
            return

        self.populate_warinfo(warinfo)
        if warinfo.is_in_preparation():
            self.send_preparation_msg()
        elif warinfo.is_in_war():
            self.send_war_msg()
            if not self.mute_attacks:
                self.send_attack_msgs()
        elif warinfo.is_war_over():
            if not self.mute_attacks:
                self.send_attack_msgs()
            self.send_war_over_msg()
            self.reset()
        else:
            print("Current war status is uknown. We stay quiet.")

    def populate_warinfo(self, warinfo):
        self.warinfo = warinfo
        self.warstats = WarStats(warinfo)
        self.msg_factory = MessageFactory(self.coc_api, warinfo)
        if self.get_war_id() not in self.db:
            self.db[self.get_war_id()] = {}

    def get_war_id(self):
        if not self.warinfo:
            raise ValueError('Warinfo is empty.')
        return self.warinfo.create_war_id()

    def send_preparation_msg(self):
        self.send_once(
            self.msg_factory.create_preparation_msg(),
            msg_id='preparation_msg')
        self.send_once(
            self.msg_factory.create_players_msg(),
            msg_id='players_msg')

    def send_war_msg(self):
        self.send_once(self.msg_factory.create_war_msg(), 'war_msg')

    def send_attack_msgs(self):
        for order, items in sorted(self.warinfo.ordered_attacks.items()):
            player, attack = items
            self.send_single_attack_msg(player, attack)

    def send_single_attack_msg(self, player, attack):
        war_stats = self.warstats.calculate_war_stats_sofar(attack['order'])
        if self.warinfo.is_clan_member(player):
            self.send_clan_attack_msg(player, attack, war_stats)
        else:
            self.send_opponent_attack_msg(player, attack, war_stats)

    def send_clan_attack_msg(self, attacker, attack, war_stats):
        self.send_once(
            self.msg_factory.create_clan_attack_msg(
                attacker, attack, war_stats),
            msg_id=self.get_attack_id(attack))
        if war_stats['clan_destruction'] == 100:
            self.send_once(
                self.msg_factory.create_clan_full_destruction_msg(
                    attacker, attack, war_stats),
                msg_id='clan_full_destruction')

    def is_msg_sent(self, msg_id):
        return self.db[self.get_war_id()].get(msg_id, False)

    def mark_msg_as_sent(self, msg_id):
        tmp = self.db[self.get_war_id()]
        tmp[msg_id] = True
        self.db[self.get_war_id()] = tmp

    def get_attack_id(self, attack):
        return "attack{}{}".format(attack['attackerTag'][1:],
                                   attack['defenderTag'][1:])

    def send_opponent_attack_msg(self, attacker, attack, war_stats):
        self.send_once(self.msg_factory.create_opponent_attack_msg(
            attacker, attack, war_stats),
            msg_id=self.get_attack_id(attack))
        if war_stats['op_destruction'] == 100:
            self.send_once(
                self.msg_factory.create_opponent_full_destruction_msg(
                    attacker, attack, war_stats),
                msg_id='op_full_destruction')

    def send_war_over_msg(self):
        self.send_once(
            self.msg_factory.create_war_over_msg(), msg_id='war_over_msg')

    def reset(self):
        self.warinfo = None
        self.warstats = None
        self.msg_factory = None

    def send_once(self, msg, msg_id=None):
        if not msg_id:
            msg_id = hashlib.md5(msg.encode('utf-8')).hexdigest()

        if not self.is_msg_sent(msg_id):
            self.send(msg)
            self.mark_msg_as_sent(msg_id)

    def send(self, msg):
        self.notifier.send(msg)

    def start(self):
        """Send war news to telegram channel."""
        while True:
            try:
                self.update()
                time.sleep(POLL_INTERVAL)
            except Exception as err:
                if '403' in str(err):
                    # Check whether warlog is public
                    if not self.coc_api.get_claninfo(self.clan_tag).is_warlog_public:
                        print('Warlog must be public. Exiting.')
                        self.notifier.send(_("Warlog must be public boss! ☠"))
                elif '500' in str(err):
                    print('CoC internal server error, retrying.')
                    self.notifier.send(
                        'CoC internal server error, retrying in {} seconds.'
                        .format(POLL_INTERVAL * 10), silent=True)
                    time.sleep(POLL_INTERVAL * 10)
                    continue
                elif '502' in str(err):
                    print('CoC bad gateway, retrying.')
                    self.notifier.send(
                        'CoC bad gateway, retrying in {} seconds.'
                        .format(POLL_INTERVAL * 10), silent=True)
                    time.sleep(POLL_INTERVAL * 10)
                    continue
                elif '503' in str(err):
                    print('CoC maintenance error, retrying.')
                    self.notifier.send(
                        'CoC maintenance error, retrying in {} seconds.'
                        .format(POLL_INTERVAL * 10), silent=True)
                    time.sleep(POLL_INTERVAL * 10)
                    continue
                elif '504' in str(err):
                    print('504 Gateway Timeout, retrying.')
                    self.notifier.send(
                        '504 Gateway Timeout, retrying in {} seconds.'
                        .format(POLL_INTERVAL * 10), silent=True)
                    time.sleep(POLL_INTERVAL * 10)
                    continue

                else:
                    self.notifier.send(
                        _("☠ 😵 App is broken boss! Come over and fix me please!"))
                raise

if __name__ == '__main__':
    main()


#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "Jonny"
# Email: jonnysps@yeah.net
# Date: 2017/12/26

import sys
sys.path.append("/usr/local/git_d_local/Test_SePy_base1/Action_Case_Test/Unittest")
#sys.path.append("..")

print(sys.path)
import unittest
import ddt
from time import sleep as s
from selenium import webdriver

from Test_SePy_base1.ObjectPage.BaiduHome import baidupage
from Test_SePy_base1.ObjectPage.BasePage import basefunction
from Test_SePy_base1.ObjectPage.DecoratorScreen import Screen
from Test_SePy_base1.ddt.dataoop import ExcelDate

exclepath = "D:\\git\\Test_SePy_base1\\ddt\\dataoop_t1.xlsx"
sheetname = 'Sheet2'

data = ExcelDate(exclepath, sheetname)

data_o = data.getexceldata()

#@unittest.skip
@ddt.ddt
class BaiDuLogin_Test(unittest.TestCase):

    driver = webdriver.Chrome()

    #print("data_o=%s"% data_o)

    @classmethod
    def setUpClass(cls):
        #cls.driver = webdriver.Chrome()
        cls.bd = baidupage(cls.driver)
        cls.base = basefunction(cls.driver)

    @classmethod
    def tearDownClass(cls):
        pass
        #cls.driver.quit()

    #@Screen
    @ddt.data(*data_o)
    def test_search01(self,data):
        #print('data=%s'%data)
        self.base.open(data['url'])
        s(2)

        #print("url=%s"% data["url"])
        #self.base.
        self.bd.baidu_input_text(data['input'])
        s(2)
       # print("input=%s" % data["input"])
        self.bd.baidu_click()
        s(2)
        #self.base.open(data['url'])
        #self.assertEqual()

   # @Screen
    #@ddt.data(*data_o)
    # def test_search02(self,data):
    #     self.base.open(data["url"])
    #     self.bd.baidu_input_text(data["input"])
    #     self.bd.baidu_click()

if __name__ == "__main__":

    unittest.main()


# ejercicio 1
cantidad = int(input("ingresela cantidad de lugares que se movera: "))
for i in range(5):
    num_auxiliar = ""
    str_auxiliar = ""
    men_cod = ""
    cantidad_aux = ""
    men = input("ingrese el mensaje que desea mandar: ")
    abcd = "abcdefghijklmnñopqrstuvwxyz"
    long = len(men)
    list_men = list(men)

    for n in range(long):
        num_auxiliar = abcd.find(list_men[n])
        if list_men[n] in abcd:
            num_auxiliar += cantidad
            if num_auxiliar > 26:
                cantidad_aux = num_auxiliar - 26
                num_auxiliar = 0
                num_auxiliar += cantidad_aux
            str_auxiliar = abcd[num_auxiliar]
            list_men[n] = str_auxiliar
    for c in range(long):
        men_cod += list_men[c]
    print(men_cod)

# ejercicio 2
contador_auxiliar_pares = 0
contador_auxiliar_impares=0
num = 1
contador_pares = 0
contador_inpares = 0
auxiliar = ""
contador = 0
list_temporal = ""
while num != 0:
    contador_auxiliar_pares = 0
    contador_auxiliar_impares=0
    contador = 0
    num = int(input("ingrese un numero para averiguar la cantidad de pares o impares y/o ponga un 0 para finalizar la ejecucion: "))
    if num == 0:
        print("fin de la ejecucion")
    elif num > 9 or num < (-9):
        auxiliar = num
        while auxiliar > 0:
            contador = (auxiliar % 10)
            auxiliar = auxiliar // 10
            if contador % 2 == 0:
                print("el numero es par")
                contador_pares += 1
                contador_auxiliar_pares += 1
            else:
                print("el numero es inpar")
                contador_inpares += 1
                contador_auxiliar_impares += 1
    else:
        if num % 2 == 0:
            print("el numero es par")
            contador_pares += 1
            contador_auxiliar_pares += 1
        else:
            print("el numero es inpar")
            contador_inpares += 1
            contador_auxiliar_impares += 1
    print(f"la cantidad de pares que ingreso es {contador_auxiliar_pares} y la cantidad de impares es de {contador_auxiliar_impares}")
print(f"la cantidad de digitos pares fue de {contador_pares} y de inpares fue de {contador_inpares}")


def p(a, n):
    if (n < 0):
        return 1.0 / p(a, -n)
    if (n == 0):
        return 1
    else:
        return a * p(a, n - 1)

a, n = map(int, input().split())
print(p(a, n))


audio_script = """ ffmpeg -f alsa  `# * Use alsa [6]` \
        -channels 4 `# Channel numbers are 4 for ps eyes` \
        -i hw:1,0 `# * Audio input [6]` \
        -t 65 audio/%d/%d/%d/%d.wav """
import os, datetime
while 1:
    time = datetime.datetime.now()
    os.system("mkdir audio/%d/%d/%d -p" % (time.month, time.day, time.hour))
    os.system("mkdir audio/%d/%d/%d -p" % (time.month, time.day, time.hour + 1)) # In case it is right on the time between hours
    os.system(audio_script % (time.month, time.day, time.hour, time.minute))


##############################################################################
#
# Author: Frank Bieberly
# Date: 14 July 2019
# Name: realtime_decoder.py
# Description:
# This is a class designed for real time decoding of orbcomm satellite passes
#
#
##############################################################################

import numpy as np
from scipy.signal import firwin, welch
from datetime import datetime, timedelta

from orbcomm_packet import packet_dict
from helpers import complex_mix, rrcosfilter
from helpers import fletcher_checksum, ecef_to_lla

class RealtimeDecoder():

    def __init__(self, sat_freq, center_freq=137.5e6, sample_rate=1.2288e6):
        self.sample_rate = sample_rate
        self.center_frequency = center_freq
        self.sat_center_frequency = sat_freq
        self.init_default_values()

    def init_default_values(self):
        # Initialize all required variables
        # Some are hardcoded
        self.doppler = 0.0
        self.frequency_offset = 0.0
        self.symbol_offset = -1
        self.first_median = 1.0
        self.mix_phase = 0.0

        self.size_of_packets = int(12*8)
        self.bit_offset = -1
        self.baud_rate = 4800.0
        self.samples_per_symbol = 2
        self.decimation = int(self.sample_rate/(self.samples_per_symbol*self.baud_rate))

        self.bit_string = ''
        self.packets =[]
        self.reverse = False
        self.err = 0.0

        # Debug info
        self.ave_angles_above_zero = 0.0
        self.ave_angles_below_zero = 0.0
        self.good_packets = 0.0
        self.bad_packets = 0.0

        self.packet_dict = packet_dict
        self.packet_headers = [packet_dict[packet_type]['header'] for packet_type in packet_dict]

        # Timing recovery loop initialization
        self.tr_tau     = 0.                        # initial timing offset estimate
        self.tr_dtau    = 0.                        # initial timing _rate_ offset estimate
        self.tr_buf     = np.zeros(5, dtype=np.complex64) # filter buffer
        self.tr_th      = np.array([-2., -1., 0., 1., 2.]) # time vector for interpolating filter
        self.tr_w       = np.sqrt(np.hamming(5).T)  # window function for interpolating filter
        self.tr_alpha   = 0.0005                    # loop filter bandwidth (timing _rate_ adjustment factor)
        self.tr_beta    = 2*np.sqrt(self.tr_alpha)          # (timing _phase_ adjustment factor)
        self.tr_counter = 0         # interpolating filter adds delay
        self.tr_buf_dz  = [0.,0.,0.]
        self.tr_last_sample = (0 + 0j)
        self.last_symbol = (0 + 0j)

        # Initialize the costas loop parameters
        self.cl_alpha = 0.05
        self.cl_beta = 0.2 * self.cl_alpha**2
        self.cl_frequency_out = 0.0
        self.cl_phase_est = 0.0

        # Initialize LPF parameters
        self.lpf_filter_freq = 10e3
        self.lpf_order = 181   # filter order
        self.lpf_nyq = 0.5 * self.sample_rate
        self.lpf_normal_cutoff = self.lpf_filter_freq / self.lpf_nyq
        self.lpf_fir_taps = firwin(self.lpf_order, self.lpf_filter_freq, fs=self.sample_rate)

        # Create RRC taps
        rrc_alpha = 0.4
        rrc_baudrate = 1.
        rrc_num_of_symbols_half_filter = 8.
        self.rrc_num_taps = self.samples_per_symbol * rrc_num_of_symbols_half_filter * 2. + 1.
        rrc_t_idx, self.rrc_taps = rrcosfilter(self.rrc_num_taps, rrc_alpha, rrc_baudrate, self.samples_per_symbol)

        # sample buffers
        self.lpf_remaining_samples = np.array([])
        self.rrc_remaining_samples = np.array([])

        # store satellite info from packets
        self.sat_id = ''
        self.sat_lat = 0.0
        self.sat_lon = 0.0
        self.sat_alt = 0.0

        self.decimated_samples = np.array([])
        self.symbols = np.array([])
        self.mixed_down_samples = np.array([])

    def clean(self):
        # reset the decoder to receive a new satellite pass
        self.init_default_values()

    def first_samples(self, samples, doppler=0.0):
        # Use the first block of samples to estimate frequency offsets
        self.doppler=doppler
        self.first_median = np.median(np.abs(samples))
        norm_sample_block = samples / self.first_median

        # Mix the samples to baseband (compensating for doppler shift)
        # There will be a residual carrier error because of the RLTSDR frequency offset
        freq_shift = self.center_frequency - self.sat_center_frequency - self.doppler
        mixed_down_samples, _ = complex_mix(norm_sample_block, freq_shift, self.sample_rate)

        samps_to_filter = int(len(mixed_down_samples) - self.lpf_order)
        while samps_to_filter % self.decimation != 0:
            samps_to_filter -= 1

        # Low pass filter and decimate in one step
        decimated_samples = np.zeros(int(samps_to_filter/self.decimation), dtype=np.complex64)
        for yy in range(0, len(decimated_samples)):
            temp_samples = mixed_down_samples[yy*self.decimation:int(yy*self.decimation+self.lpf_order)]
            decimated_samples[yy] = np.dot(self.lpf_fir_taps, temp_samples)

        # estimate remaining carrier error (RTLSDR frequency error)
        # signal to the fourth power, take the fft, peak is at frequency offset
        rbw = 1
        nperseg = int(len(decimated_samples))
        signal_to_4th_power = np.power(decimated_samples, 4)
        f, pxx = welch(signal_to_4th_power, fs=self.sample_rate/self.decimation, \
                              return_onesided=False, nperseg=nperseg, scaling='density')
        f = (np.roll(f, int(len(f)/2)))
        pxx = np.roll(pxx, int(len(pxx)/2))
        search_window = int(1000. / ((self.sample_rate/self.decimation)/nperseg))  # search +/- 1 kHz around fc
        frequency_peak = np.argmax(pxx[int(len(pxx)/2 - search_window):int(len(pxx)/2 + search_window)])
        self.frequency_offset = -(frequency_peak - search_window)*(self.sample_rate/self.decimation/nperseg) / 4
        print("Frequency offset: {}".format(self.frequency_offset))

    def decode_samples_to_packets(self, samples, doppler=0.0):

        # on the very first call, first_samples will try to determine the frequency offset
        # of the SDR.
        if self.frequency_offset == None:
            self.first_samples(samples, doppler=doppler)

        # process samples
        # if doppler shift is provided, it is compensated for
        # otherwise, the phase recovery algorithm attempts to control it
        self.doppler = doppler
        freq_shift = self.center_frequency - self.sat_center_frequency - self.doppler + self.frequency_offset
        # norm_sample_block = samples / self.first_median
        norm_sample_block = samples / np.median(np.abs(samples))

        mixed_down_samples, self.mix_phase = complex_mix(norm_sample_block, freq_shift, self.sample_rate, self.mix_phase)
        mix_sample_buffer = np.concatenate([self.lpf_remaining_samples, mixed_down_samples])
        self.mixed_down_samples = mixed_down_samples

        samps_to_filter = int(len(mix_sample_buffer) - self.lpf_order)
        while samps_to_filter % self.decimation != 0:
            samps_to_filter -= 1

        # Low pass filter and decimate in one step
        self.decimated_samples = np.zeros(int(samps_to_filter / self.decimation), dtype=np.complex64)
        for yy in range(0, len(self.decimated_samples)):
            temp_samples = mix_sample_buffer[yy*self.decimation:int(yy * self.decimation + self.lpf_order)]
            self.decimated_samples[yy] = np.dot(self.lpf_fir_taps, temp_samples)
        self.lpf_remaining_samples = mix_sample_buffer[int((yy + 1) * self.decimation):]

        decim_sample_buffer = np.concatenate([self.rrc_remaining_samples, self.decimated_samples])
        matched_filtered_samples = np.zeros(int(len(decim_sample_buffer) - self.rrc_num_taps), dtype=np.complex64)
        for yy in range(len(matched_filtered_samples)):
            matched_filtered_samples[yy] = np.dot(self.rrc_taps, decim_sample_buffer[yy:int(yy+self.rrc_num_taps)])

        self.rrc_remaining_samples = decim_sample_buffer[yy+1:]

        time_recovery_samples = np.zeros(len(matched_filtered_samples), dtype=np.complex64)
        for i in range(0, len(matched_filtered_samples)):
            # push sample into interpolating filter
            self.tr_buf[:-1] = self.tr_buf[1:]
            self.tr_buf[-1] = matched_filtered_samples[i]

            # interpolate matched filter output
            self.tr_hi  = np.sinc(self.tr_th - self.tr_tau) * self.tr_w  # interpolating filter coefficients
            if i == 0:
                self.tr_hi /= np.sum(self.tr_hi)
            time_recovery_samples[i] = np.dot(self.tr_buf, self.tr_hi[::-1])    # compute matched filter output

            # take (approximate) derivative of filter output
            self.tr_buf_dz[:-1] = self.tr_buf_dz[1:]
            self.tr_buf_dz[-1] = time_recovery_samples[i]
            dz = -np.dot(self.tr_buf_dz, np.array([-1, 0, 1]))

            # determine if an output sample needs to be computed
            self.tr_counter += 1
            if self.tr_counter >= self.samples_per_symbol:
                # decrement counter by samples per symbol
                self.tr_counter -= self.samples_per_symbol

                # compute timing error signal, accounting for delay
                if i == 0:
                    self.err = np.tanh( (dz * np.conj(self.tr_last_sample)).real )
                else:
                    self.err = np.tanh( (dz * np.conj(time_recovery_samples[i-1])).real )

                # update timing rate change
                self.tr_dtau += self.tr_alpha * self.err
                self.tr_tau  += self.tr_beta * self.err

            # update timing error
            self.tr_tau += self.tr_dtau / self.samples_per_symbol

        self.tr_last_sample = time_recovery_samples[-1]

        phase_comp_samples = np.zeros(len(time_recovery_samples), dtype=np.complex64)

        # Costas loop
        for idx, sample in enumerate(time_recovery_samples):
            signal_out = sample * np.exp(-1.0j * self.cl_phase_est)
            phase_comp_samples[idx] = signal_out
            phase_error = np.sign(signal_out.real)*signal_out.imag - np.sign(signal_out.imag)*signal_out.real
            self.cl_frequency_out += self.cl_beta * phase_error
            self.cl_phase_est += self.cl_alpha * phase_error + self.cl_frequency_out

        if self.symbol_offset == -1:
            self.symbol_offset = 0
            sym_offset_0 = np.sum(np.abs(phase_comp_samples[::self.samples_per_symbol]))
            sym_offset_1 = np.sum(np.abs(phase_comp_samples[1::self.samples_per_symbol]))
            if sym_offset_1 < sym_offset_0:
                self.symbol_offset = 1
        demod_symbols = phase_comp_samples[self.symbol_offset::self.samples_per_symbol]
        self.symbols = demod_symbols

        bits = np.zeros(len(demod_symbols))
        angles = np.zeros(len(demod_symbols))

        for xx in range(0, len(demod_symbols)):
            if xx == 0:
                angle = np.angle(demod_symbols[xx], deg=True) - np.angle(self.last_symbol, deg=True)
            else:
                angle = np.angle(demod_symbols[xx], deg=True) - np.angle(demod_symbols[xx-1], deg=True)
            if angle > 180:
                angle -= 360
            if angle < -180:
                angle += 360
            angles[xx] = angle
            bit = 0
            if angle > 0: bit = 1
            bits[xx] = bit

        pos_angles = angles[np.where(angles > 0)]
        neg_angles = angles[np.where(angles < 0)]
        if len(pos_angles) > 0:
            self.ave_angles_above_zero = np.mean(pos_angles)
        if len(neg_angles) > 0:
            self.ave_angles_below_zero = np.mean(neg_angles)

        self.last_symbol = demod_symbols[-1]
        self.bit_string += ''.join([str(int(bit)) for bit in bits])

        num_of_possible_packets = len(self.bit_string) / self.size_of_packets
        if num_of_possible_packets > 20 and self.bit_offset == -1:
            # print("Calculate possible bit offset")
            # for all bit offsets (of the length of the packets)
            # calculate a score for most valid headers of that offset
            # this also checks a bit-reversed score (in case my endianness is reversed)
            scores = np.zeros(self.size_of_packets)
            revscores = np.zeros(self.size_of_packets)
            for xx in range(0, self.size_of_packets):
                for yy in range(xx, len(self.bit_string)-xx-8, self.size_of_packets):
                    if self.bit_string[yy:yy+8][::-1] in self.packet_headers:
                        scores[xx] += 1
                    if self.bit_string[yy:yy+8] in self.packet_headers:
                        revscores[xx] += 1

            self.reverse = False
            max_score = max(np.max(scores), np.max(revscores))
            if max_score > 15 and float(max_score)/num_of_possible_packets > 0.5:
                if np.max(scores) < np.max(revscores):
                    self.reverse = True
                if self.reverse:
                    self.bit_offset = np.argmax(revscores)
                else:
                    self.bit_offset = np.argmax(scores)
                self.bit_string = self.bit_string[self.bit_offset:]
                print("Bit stream offset: {}".format(self.bit_offset))

        self.packets = []
        if len(self.bit_string) > 4 * self.size_of_packets and self.bit_offset != -1:
            bits_to_remove = 0
            last_packet_epheris = False
            for xx in range(0, len(self.bit_string) - 2 * self.size_of_packets, self.size_of_packets):
                if last_packet_epheris == True:
                    last_packet_epheris = False
                    continue
                packet = ''
                if self.reverse:
                    header = '{:02X}'.format(int(self.bit_string[xx:xx+8], 2))
                else:
                    header = '{:02X}'.format(int(self.bit_string[xx:xx+8][::-1], 2))

                ephemeris_header = self.packet_dict['Ephemeris']['hex_header']
                packet_length = 12*8
                if header == ephemeris_header:
                    packet_length = 24*8
                    last_packet_epheris = True

                for yy in range(0, packet_length, 8):
                    if len(self.bit_string) < xx+yy+8:
                        packet = ''
                        break
                    if self.reverse:
                        packet += '{:02X}'.format(int(self.bit_string[xx+yy:xx+yy+8], 2))
                    else:
                        packet += '{:02X}'.format(int(self.bit_string[xx+yy:xx+yy+8][::-1], 2))
                if packet != '':
                    bits_to_remove += packet_length
                    self.packets.append(packet)
            # self.bit_string = self.bit_string[bits_to_remove:]

        # If 90% or more of the packets don't pass the checksum
        # leave the bits in the bit string and reset the bit offset to -1
        # on the next loop the code will recheck the bit offset to see if a
        # better bit offset will allow us to decode the bits
        if len(self.packets) > 0:
            total_packets = len(self.packets)
            bad_packets = 0.0
            for packet in self.packets:
                if fletcher_checksum(packet) != '0000':
                    bad_packets += 1
            if float(bad_packets)/total_packets > 0.9:
                self.packets = []
                self.bit_offset = -1
                print("Packets failing checksum. Resetting bit offset.")
            else:
                self.bit_string = self.bit_string[bits_to_remove:]

        if num_of_possible_packets > 60 and self.bit_offset == -1:
            print("RESETTING!")
            self.init_default_values()

        packet_dict_list = []
        for packet in self.packets:

            # Compute the fletcher16 checksum over the whole packet
            # 0000 output is a good packet
            if fletcher_checksum(packet) != '0000':
                self.bad_packets += 1
                continue
            else:
                self.good_packets += 1
            if self.good_packets + self.bad_packets > 500:
                self.good_packets -= 0.5
                self.bad_packets -= 0.5
                if self.good_packets < 0: self.good_packets = 0.0
                if self.bad_packets < 0: self.bad_packets = 0.0

            packet_dict = {}
            for packet_type in self.packet_dict:
                packet_info = self.packet_dict[packet_type]
                if packet[:2] == packet_info['hex_header']:
                    packet_dict['packet_type'] = packet_type

                    for part, (start, stop) in packet_info['message_parts']:
                        packet_dict[part] = packet[start:stop]

                    if packet_type == 'Ephemeris':
                        payload = ''.join([packet[xx:xx+2] for xx in range(42, 2, -2)])

                        # calculate current satellite time
                        start_date = datetime(year=1980, month=1, day=6, hour=0, minute=0)
                        week_number = payload[:4]
                        time_of_week = payload[4:10]
                        this_week = start_date + timedelta(weeks=int(week_number, 16))
                        this_time = this_week + timedelta(seconds=int(time_of_week, 16))

                        # calculate satellite ECEF position
                        zdot = payload[10:15][::-1]
                        ydot = payload[15:20][::-1]
                        xdot = payload[20:25][::-1]
                        zpos = payload[25:30][::-1]
                        ypos = payload[30:35][::-1]
                        xpos = payload[35:40][::-1]

                        max_r_sat = 8378155.0
                        val_20_bits = 1048576.0

                        x_temp = int(xpos[:2][::-1], 16) + 256. * int(xpos[2:4][::-1], 16) + 256**2 * int(xpos[4:], 16)
                        x_ecef = ((2*x_temp*max_r_sat)/val_20_bits - max_r_sat)
                        y_temp = int(ypos[:2][::-1], 16) + 256. * int(ypos[2:4][::-1], 16) + 256**2 * int(ypos[4:], 16)
                        y_ecef = ((2*y_temp*max_r_sat)/val_20_bits - max_r_sat)
                        z_temp = int(zpos[:2][::-1], 16) + 256. * int(zpos[2:4][::-1], 16) + 256**2 * int(zpos[4:], 16)
                        z_ecef = ((2*z_temp*max_r_sat)/val_20_bits - max_r_sat)

                        lat, lon, alt = ecef_to_lla(x_ecef, y_ecef, z_ecef)
                        self.sat_lat = lat
                        self.sat_lon = lon
                        self.sat_alt = alt
                        packet_dict['lat'] = lat
                        packet_dict['lon'] = lon
                        packet_dict['alt'] = alt
                        packet_dict['week_num'] = int(week_number, 16)
                        packet_dict['gps_time'] = this_time
                    break

            # Unrecognized just means I don't know what these packets are for
            # would also happen if the header is corrupted
            if 'packet_type' not in packet_dict:
                packet_dict['packet_type'] = 'Unrecognized'
                packet_dict['data'] = packet[2:]
        return packet_dict_list

    def parse_packets(self):
        for packet in self.packets:
            output = ''

            # Compute the fletcher16 checksum over the whole packet
            # 0000 output is a good packet
            if fletcher_checksum(packet) != '0000':
                self.bad_packets += 1
                output += '### '
                continue
            else:
                self.good_packets += 1
            if self.good_packets + self.bad_packets > 500:
                self.good_packets -= 0.5
                self.bad_packets -= 0.5
                if self.good_packets < 0: self.good_packets = 0.0
                if self.bad_packets < 0: self.bad_packets = 0.0
            for packet_type in self.packet_dict:
                packet_info = self.packet_dict[packet_type]
                if packet[:2] == packet_info['hex_header']:
                    output += '{}: '.format(packet_type)
                    for part, (start, stop) in packet_info['message_parts']:
                        output += '{}: {} '.format(part, packet[start:stop])
                    print(output)
                    if packet_type in ['Sync', 'Ephemeris']:
                        for part, (start, stop) in packet_info['message_parts']:
                            if part == 'Sat ID':
                                self.sat_id = packet[start:stop]
                    if packet_type == 'Ephemeris':
                        payload = ''.join([packet[xx:xx+2] for xx in range(42, 2, -2)])

                        # calculate current satellite time
                        start_date = datetime(year=1980, month=1, day=6, hour=0, minute=0)
                        week_number = payload[:4]
                        time_of_week = payload[4:10]
                        this_week = start_date + timedelta(weeks=int(week_number, 16))
                        this_time = this_week + timedelta(seconds=int(time_of_week, 16))
                        print("\tCurrent satellite time: {} Z".format(this_time))

                        # calculate satellite ECEF position
                        zdot = payload[10:15][::-1]
                        ydot = payload[15:20][::-1]
                        xdot = payload[20:25][::-1]
                        zpos = payload[25:30][::-1]
                        ypos = payload[30:35][::-1]
                        xpos = payload[35:40][::-1]

                        max_r_sat = 8378155.0
                        val_20_bits = 1048576.0

                        x_temp = int(xpos[:2][::-1], 16) + 256. * int(xpos[2:4][::-1], 16) + 256**2 * int(xpos[4:], 16)
                        x_ecef = ((2*x_temp*max_r_sat)/val_20_bits - max_r_sat)
                        y_temp = int(ypos[:2][::-1], 16) + 256. * int(ypos[2:4][::-1], 16) + 256**2 * int(ypos[4:], 16)
                        y_ecef = ((2*y_temp*max_r_sat)/val_20_bits - max_r_sat)
                        z_temp = int(zpos[:2][::-1], 16) + 256. * int(zpos[2:4][::-1], 16) + 256**2 * int(zpos[4:], 16)
                        z_ecef = ((2*z_temp*max_r_sat)/val_20_bits - max_r_sat)

                        lat, lon, alt = ecef_to_lla(x_ecef, y_ecef, z_ecef)
                        self.sat_lat = lat
                        self.sat_lon = lon
                        self.sat_alt = alt
                        print("\tLat/Lon:       {:8.4f}, {:8.4f}, Altitude: {:6.1f} km".format(lat, lon, alt/1000.0))
                    break

            # Unrecognized just means I don't know what these packets are for
            # would also happen if the header is corrupted
            if output in ['', '### ']:
                print("{}Unrecognized packet: {}".format(output, packet))


import cv2
import numpy as np
import math
import RPi.GPIO as GPIO

# Motors
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)

motor_1_pin = 40 #FIND PWM PIN
motor_1_1 = 29
motor_1_2 = 31

motor_2_pin = 38 #ABOVE
motor_2_1 = 16
motor_2_2 = 18

default_speed = 90

# Image
rho = 1
theta = np.pi / 180
min_threshold = 10
minLineLength = 5
maxLineGap = 20

video = cv2.VideoCapture(0)

video.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
video.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)

def convert_to_hsv(frame):
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    # cv2.imshow("HSV", hsv)

    return hsv

def detect_edges(frame):
    lower_limit = np.array([90, 120, 0], dtype="uint8")  # lower limit of blue
    upper_limit = np.array([150, 255, 255], dtype="uint8")  # upper limit of blue
    mask = cv2.inRange(hsv, lower_limit, upper_limit)  # filter for colour limits

    edges = cv2.Canny(mask, 50, 100)
    # cv2.imshow("edges", edges)

    return edges

def select_roi(edges):
    height, width = edges.shape
    mask = np.zeros_like(edges)  # empty matrix with same dimensions as frame

    # focus on lower half of screen
    # specify coordinates of 4 points(lower left, upper left, upper right, lower right)
    polygon = np.array([[
        (0, height),
        (0, height/2),
        (width, height/2),
        (width, height),
    ]], np.int32)

    cv2.fillPoly(mask, polygon, 255)  # fill polygon with blue
    cropped_edges = cv2.bitwise_and(edges, mask)
    # cv2.imshow("roi", cropped_edges)

    return cropped_edges

def detect_line_segments(cropped_edges):
    line_segments = cv2.HoughLinesP(cropped_edges, rho, theta, min_threshold,
                                   np.array([]), minLineLength, maxLineGap)

    return line_segments

def make_points(frame, line):
    height, width, _ = frame.shape
    slope, intercept = line
    y1 = height  # bottom of the frame
    y2 = int(y1 / 2)  # make points from middle of the frame down

    if slope == 0:
        slope = 0.1

    x1 = int((y1 - intercept) / slope)
    x2 = int((y2 - intercept) / slope)

    return [[x1, y1, x2, y2]]

def display_lines(frame, lines, line_color=(0, 255, 0), line_width=6):  # line color (B,G,R)
    line_image = np.zeros_like(frame)
    if lines is not None:
        for line in lines:
            for x1, y1, x2, y2 in line:
                cv2.line(line_image, (x1, y1), (x2, y2), line_color, line_width)
    line_image = cv2.addWeighted(frame, 0.8, line_image, 1, 1)

    return line_image

def average_slope_intercept(frame, line_segments):
    lane_lines = []
    if line_segments is None:
        print("no line segment detected")
        return lane_lines
    height, width,_ = frame.shape
    left_fit = []
    right_fit = []
    boundary = 1/3
    left_region_boundary = width * (1 - boundary)
    right_region_boundary = width * boundary
    for line_segment in line_segments:
        for x1, y1, x2, y2 in line_segment:
            if x1 == x2:
                print("skipping vertical lines (slope = infinity)")
                continue
            fit = np.polyfit((x1, x2), (y1, y2), 1)
            slope = (y2 - y1) / (x2 - x1)
            intercept = y1 - (slope * x1)
            if slope < 0:
                if x1 < left_region_boundary and x2 < left_region_boundary:
                    left_fit.append((slope, intercept))
            else:
                if x1 > right_region_boundary and x2 > right_region_boundary:
                    right_fit.append((slope, intercept))
    left_fit_average = np.average(left_fit, axis=0)
    if len(left_fit) > 0:
        lane_lines.append(make_points(frame, left_fit_average))
    right_fit_average = np.average(right_fit, axis=0)
    if len(right_fit) > 0:
        lane_lines.append(make_points(frame, right_fit_average))
    # lane_lines is a 2-D array consisting the coordinates of the right and left lane lines
    # for example: lane_lines = [[x1,y1,x2,y2],[x1,y1,x2,y2]]
    # where the left array is for left lane and the right array is for right lane
    # all coordinate points are in pixels
    return lane_lines

def get_steering_angle(frame, lane_lines):
    height, width, _ = frame.shape
    x_offset, y_offset = 0, 0

    if len(lane_lines) == 2: # if two lane lines are detected
        _, _, left_x2, _ = lane_lines[0][0] # extract left x2 from lane_lines array
        _, _, right_x2, _ = lane_lines[1][0] # extract right x2 from lane_lines array
        mid = int(width / 2)
        x_offset = (left_x2 + right_x2) / 2 - mid
        y_offset = int(height / 2)

    elif len(lane_lines) == 1: # if only one line is detected
        x1, _, x2, _ = lane_lines[0][0]
        x_offset = x2 - x1
        y_offset = int(height / 2)

    elif len(lane_lines) == 0: # if no line is detected
        x_offset = 0
        y_offset = int(height / 2)

    angle_to_mid_radian = math.atan(x_offset / y_offset)
    angle_to_mid_deg = int(angle_to_mid_radian * 180.0 / math.pi)
    steering_angle = angle_to_mid_deg + 90

    return steering_angle

def display_heading_line(frame, steering_angle, line_color=(0, 0, 255), line_width=5):
    heading_image = np.zeros_like(frame)
    height, width, _ = frame.shape
    steering_angle_radian = steering_angle / 180.0 * math.pi
    x1 = int(width / 2)
    y1 = height
    x2 = int(x1 - height / 2 / math.tan(steering_angle_radian))
    y2 = int(height / 2)
    cv2.line(heading_image, (x1, y1), (x2, y2), line_color, line_width)
    heading_image = cv2.addWeighted(frame, 0.8, heading_image, 1, 1)
    cv2.imshow("heading", heading_image)

    return heading_image

if __name__ == '__main__':
    # init
    GPIO.setup(motor_1_pin, GPIO.OUT)
    GPIO.setup(motor_2_pin, GPIO.OUT)
    GPIO.setup(motor_1_1, GPIO.OUT)
    GPIO.setup(motor_1_2, GPIO.OUT)
    GPIO.setup(motor_2_1, GPIO.OUT)
    GPIO.setup(motor_2_2, GPIO.OUT)

    GPIO.output(motor_1_1, True)
    GPIO.output(motor_1_2, False)
    GPIO.output(motor_2_1, False)
    GPIO.output(motor_2_2, True)

    motor_1 = GPIO.PWM(motor_1_pin, 1000)
    motor_1.stop()

    motor_2 = GPIO.PWM(motor_2_pin, 1000)
    motor_2.stop()

    while True:
        # Lane detection
        ret, frame = video.read()
        # frame = cv2.flip(frame,-1) # used to flip the image vertically
        hsv = convert_to_hsv(frame)  # convert to hsv
        edges = detect_edges(hsv)  # detect colour
        roi_edges = select_roi(edges)  # returns only region of interest
        line_segments = detect_line_segments(roi_edges)  # detects lines in roi
        lane_lines = average_slope_intercept(frame, line_segments)
        lane_lines_image = display_lines(frame, lane_lines)
        steering_angle = get_steering_angle(frame, lane_lines)
        heading_image = display_heading_line(lane_lines_image, steering_angle)
        # cv2.imshow('original', frame)

        print(steering_angle);
        # Motors
        motor_1.start(default_speed)
        motor_2.start(default_speed)
        #if steering_angle == 90:
        #    motor_1.start(default_speed)
        #    motor_2.start(default_speed)
        #elif steering_angle > 90:
        #    motor_1.start(steering_angle-90)
        #    motor_2.start(default_speed)
        #elif steering_angle < 90:
        #    motor_1.start(default_speed)
        #    motor_2.start(steering_angle)

        # Exit key
        key = cv2.waitKey(1)
        if key == 27:
            break

    video.release()
    cv2.destroyAllWindows()


"""
Pinger application.  Periodically poll hosts to see if they are up.  Log state in the
document database,  but report state changes to the message Q

"""
from asyncio import run, get_event_loop, create_task

from aio_pika import ExchangeType, connect_robust
from tentacruel.config import get_config
from tentacruel.pinger import ensure_proactor, Pinger
from tentacruel.pinger.hue_ping import hue_loop, protect

async def pika_connect(config):
    """
    Initialization that can only be completed in an async method
    """
    connection = await connect_robust(
        loop=get_event_loop(),
        **config["pika"]
    )

    channel = await connection.channel()
    exchange = await channel.declare_exchange("smartthings", ExchangeType.FANOUT)
    return exchange

async def amain():
    """
    Asynchronous main method.

    :return:
    """
    config = get_config()
    exchange = await pika_connect(config)
    pinger = Pinger(exchange)
    create_task(protect(lambda: hue_loop(exchange)))
    await pinger.ping_all()

if __name__ == "__main__":
    ensure_proactor()
    run(amain())


"""
Create a function getGuessedWord that takes in two parameters - a string,
secretWord, and a list of letters, lettersGuessed. This function will return a string
that is comprised of letters and underscores, based on what letters in
lettersGuessed are in secretWord. This shouldn't be too different from
isWordGuessed.
"""

# secretWord - a string
# lettersGuessed - a list of letters
# string - return, consists of letters and underscores

def getGuessedWord(secretWord, lettersGuessed):

    strng = " "

    for x in secretWord:
        if x in lettersGuessed:
            strng = strng + x
        else:
            strng = strng + "_"
    return strng


import os
import os.path as osp
import pandas as pd
import getpass
import matplotlib as mpl
import numpy as np
import cmasher as cmr
import matplotlib.pyplot as plt

from ..load_sim import LoadSim
from ..util.units import Units

from .hst import Hst
from .dust_pol import DustPol
from .profile_1d import Profile1D

class LoadSimFeedbackTest(LoadSim, Hst, DustPol, Profile1D):
    """LoadSim class for analyzing LoadSimFeedbackTest simulations.
    """

    def __init__(self, basedir, savdir=None, load_method='pyathena',
                 muH=1.4271, verbose=False):
        """The constructor for LoadSimFeedbackTest class

        Parameters
        ----------
        basedir : str
            Name of the directory where all data is stored
        savdir : str
            Name of the directory where pickled data and figures will be saved.
            Default value is basedir.
        load_method : str
            Load vtk using 'pyathena' or 'yt'. Default value is 'pyathena'.
            If None, savdir=basedir. Default value is None.
        verbose : bool or str or int
            Print verbose messages using logger. If True/False, set logger
            level to 'DEBUG'/'WARNING'. If string, it should be one of the string
            representation of python logging package:
            ('NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
            Numerical values from 0 ('NOTSET') to 50 ('CRITICAL') are also
            accepted.
        """

        super(LoadSimFeedbackTest,self).__init__(basedir, savdir=savdir,
                                                 load_method=load_method,
                                                 verbose=verbose)
        # Set unit and domain
        try:
            muH = self.par['problem']['muH']
        except KeyError:
            pass
        self.muH = muH
        self.u = Units(muH=muH)
        self.domain = self._get_domain_from_par(self.par)
        if self.test_newcool():
            self.test_newcool_params()

    def test_newcool(self):
        try:
            if self.par['configure']['new_cooling'] == 'ON':
                newcool = True
            else:
                newcool = False
        except KeyError:
            newcool = False
        return newcool

    def test_newcool_params(self):
        s = self
        try:
            s.iCoolH2colldiss = s.par['cooling']['iCoolH2colldiss']
        except KeyError:
            s.iCoolH2colldiss = 0

        try:
            s.iCoolH2rovib = s.par['cooling']['iCoolH2rovib']
        except KeyError:
            s.iCoolH2rovib = 0

        try:
            s.iCoolH2rovib = s.par['cooling']['iCoolH2rovib']
        except KeyError:
            s.iCoolH2rovib = 0

        try:
            s.ikgr_H2 = s.par['cooling']['ikgr_H2']
        except KeyError:
            s.ikgr_H2 = 0

        # s.config_time = pd.to_datetime(s.par['configure']['config_date'])
        # if 'PDT' in s.par['configure']['config_date']:
        #     s.config_time = s.config_time.tz_localize('US/Pacific')
        # if s.config_time < pd.to_datetime('2021-06-30 20:29:36 -04:00'):
        #     s.iCoolHIcollion = 0
        # else:
        #     s.iCoolHIcollion = 1

    def show_timeit(self):
        import matplotlib.pyplot as plt
        try:
            time = pd.read_csv(self.files['timeit'],delim_whitespace=True)

            tfields = [k.split('_')[0] for k in time.keys() if k.endswith('tot')]

            for tf in tfields:
                if tf == 'rayt': continue
                plt.plot(time['time'],time[tf].cumsum()/time['all'].cumsum(),label=tf)
            plt.legend()
        except KeyError:
            print("No timeit plot is available")

    def get_timeit_mean(self):
        try:
            time = pd.read_csv(self.files['timeit'],delim_whitespace=True)

            tfields = [k.split('_')[0] for k in time.keys() if k.endswith('tot')]

            return time[tfields].mean()
        except:
            print("No timeit file is available")

    def get_nums(self, t_Myr=None, rounding=True,
                 output='vtk'):
        """Function to determine output snapshot numbers
        from t_Myr

        Parameters
        ----------
        t_Myr : array-like or scalar
            Time of snapshots in Myr
        output : str
            Output type: 'vtk', 'starpar_vtk', 'vtk_2d', 'hst', 'rst'
        """

        u = self.u
        # Find time at which xx percent of SF has occurred
        if t_Myr is not None:
            t_Myr = np.atleast_1d(t_Myr)
            t_code = [t_Myr_/u.Myr for t_Myr_ in t_Myr]

        nums = []
        dt_output = self.get_dt_output()[output]
        for t in t_code:
            if rounding:
                num = int(round(t/dt_output))
            else:
                num = int(t/dt_output)

            nums.append(num)

        if len(nums) == 1:
            nums = nums[0]

        return nums

    def get_dt_output(self):

        r = dict()
        r['vtk'] = None
        r['hst'] = None
        r['vtk_sp'] = None
        r['rst'] = None
        r['vtk_2d'] = None

        for i in range(self.par['job']['maxout']):
            b = f'output{i+1}'
            try:
                if self.par[b]['out_fmt'] == 'vtk' and \
                   (self.par[b]['out'] == 'prim' or self.par[b]['out'] == 'cons'):
                    r['vtk'] = self.par[b]['dt']
                elif self.par[b]['out_fmt'] == 'hst':
                    r['hst'] = self.par[b]['dt']
                elif self.par[b]['out_fmt'] == 'starpar_vtk':
                    r['vtk_sp'] = self.par[b]['dt']
                elif self.par[b]['out_fmt'] == 'rst':
                    r['rst'] = self.par[b]['dt']
                elif self.par[b]['out_fmt'] == 'vtk':
                    r['vtk_2d'] = self.par[b]['dt']
            except KeyError:
                continue
        self.dt_output = r

        return r

    def get_summary_sn(self, as_dict=False):

        par = self.par
        df = dict()
        df['par'] = par

        h = self.read_hst(force_override=True)
        df['hst'] = h
        df['basedir'] = self.basedir
        df['domain'] = self.domain
        df['Nx'] = int(par['domain1']['Nx1'])

        # Input parameters
        df['mhd'] = par['configure']['gas'] == 'mhd'

        df['iWind'] = par['feedback']['iWind']
        df['iSN'] = par['feedback']['iSN']
        df['irayt'] = par['radps']['irayt']
        try:
            df['iPhot'] = par['radps']['iPhot']
        except:
            df['iPhot'] = par['radps']['iPhotIon']

        df['iRadp'] = par['radps']['apply_force']

        df['n0'] = par['problem']['n0']
        df['Z_gas'] = par['problem']['Z_gas']
        df['Z_dust'] = par['problem']['Z_dust']

        # Initial feedback radius (rinit in Kim & Ostriker 2015)
        df['dx'] = df['domain']['dx'][0]
        df['r_init'] = par['problem']['rblast_over_hdx']*(0.5*df['dx'])

        # Quantities at the time of shell formation
        # Shell formation time
        df['t_sf_M'] = float(h.loc[(h['Mi']+h['Mh']).max() ==
                                (h['Mi']+h['Mh']), 'time'])
        df['t_sf_E'] = h.where(h['Ethm']+h['Ekin']-h['Ethm_u']-h['Ekin_u'] > 0.7e51).time.max()
        df['t_sf']=df['t_sf_E']
        # Radius at the time of shell formation
        df['r_sf'] = float(h.loc[h['time'] == df['t_sf'], 'Rsh'])
        # Mass of ionized and hot gas (T > 2e4K)
        df['Mhi_sf'] = float(h.loc[h['time'] == df['t_sf'], 'Mhi'])
        # Shell mass
        df['Msh_sf'] = float(h.loc[h['time'] == df['t_sf'], 'Msh'])
        # SNR mass
        df['Msnr_sf'] = df['Mhi_sf']+df['Msh_sf']
        # Momentum
        df['pr_sf'] = float(h.loc[h['time'] == df['t_sf'], 'pr'])
        df['pok_bub_sf'] = float(h.loc[h['time'] == df['t_sf'], 'pok_bub'])
        df['Ethm_sf'] = float(h.loc[h['time'] == df['t_sf'], 'Ethm'])
        df['Ekin_sf'] = float(h.loc[h['time'] == df['t_sf'], 'Ekin'])
        df['vrsh_sf'] = float(h.loc[h['time'] == df['t_sf'], 'vrsh'])
        df['vrbub_sf'] = float(h.loc[h['time'] == df['t_sf'], 'vrbub'])

        df['dx_over_r_sf'] = df['dx']/df['r_sf']

        # Momentum at 10*t_sf
        idx = (h['time'] - 10.0*df['t_sf']).abs().argsort()[0]
        df['pr_10t_sf'] = h['pr'].iloc[idx]

        # Plot styles
        df['cmapZ'] = mpl.cm.plasma_r
        df['normZ'] = mpl.colors.LogNorm(0.003,3.0)
        df['linecolorZ'] = df['cmapZ'](df['normZ'](df['Z_gas']))

        df['cmapn'] = cmr.cosmic_r
        df['normn'] = mpl.colors.LogNorm(1e-2,1e2)
        df['linecolorn'] = df['cmapn'](df['normn'](df['n0']))

        if as_dict:
            return df
        else:
            return pd.Series(df, name=self.basename)

class LoadSimFeedbackTestAll(object):
    """Class to load multiple simulations"""
    def __init__(self, models=None):
        # self.models = list(models.keys())
        self.models = []
        self.basedirs = dict()

        for mdl, basedir in models.items():
            if not osp.exists(basedir):
                print('[LoadSimFeedbackTestAll]: Model {0:s} doesn\'t exist: {1:s}'.format(
                    mdl,basedir))
            else:
                self.models.append(mdl)
                self.basedirs[mdl] = basedir

    def set_model(self, model, savdir=None, load_method='pyathena', verbose=False):

        self.model = model
        self.sim = LoadSimFeedbackTest(self.basedirs[model], savdir=savdir,
                                       load_method=load_method, verbose=verbose)
        return self.sim

def load_all_feedback_test_sn(force_override=False):

    basedir = '/tigress/jk11/FEEDBACK-TEST/FEEDBACK-TEST'
    # basedir = '/scratch/gpfs/jk11/FEEDBACK-TEST/'
    models = dict(

        SN_n001_Z0001_N128=osp.join(basedir,'SN-n0.01-Z0.001-N128'),
        SN_n01_Z0001_N128=osp.join(basedir,'SN-n0.1-Z0.001-N128'),
        SN_n1_Z0001_N128=osp.join(basedir,'SN-n1-Z0.001-N128'),
        SN_n10_Z0001_N128=osp.join(basedir,'SN-n10-Z0.001-N128'),
        SN_n100_Z0001_N128=osp.join(basedir,'SN-n100-Z0.001-N128'),

        SN_n001_Z001_N128=osp.join(basedir,'SN-n0.01-Z0.01-N128'),
        SN_n01_Z001_N128=osp.join(basedir,'SN-n0.1-Z0.01-N128'),
        SN_n1_Z001_N128=osp.join(basedir,'SN-n1-Z0.01-N128'),
        SN_n10_Z001_N128=osp.join(basedir,'SN-n10-Z0.01-N128'),
        SN_n100_Z001_N128=osp.join(basedir,'SN-n100-Z0.01-N128'),

        SN_n001_Z01_N128=osp.join(basedir,'SN-n0.01-Z0.1-N128'),
        SN_n01_Z01_N128=osp.join(basedir,'SN-n0.1-Z0.1-N128'),
        SN_n1_Z01_N128=osp.join(basedir,'SN-n1-Z0.1-N128'),
        SN_n10_Z01_N128=osp.join(basedir,'SN-n10-Z0.1-N128'),
        SN_n100_Z01_N128=osp.join(basedir,'SN-n100-Z0.1-N128'),

        SN_n001_Z1_N128=osp.join(basedir,'SN-n0.01-Z1-N128'),
        SN_n01_Z1_N128=osp.join(basedir,'SN-n0.1-Z1-N128'),
        SN_n1_Z1_N128=osp.join(basedir,'SN-n1-Z1-N128'),
        SN_n10_Z1_N128=osp.join(basedir,'SN-n10-Z1-N128'),
        SN_n100_Z1_N128=osp.join(basedir,'SN-n100-Z1-N128'),

        # SN_n001_Z1_N256=osp.join(basedir,'SN-n0.01-Z1-N256'),
        # SN_n01_Z1_N256=osp.join(basedir,'SN-n0.1-Z1-N256'),
        # SN_n1_Z1_N256=osp.join(basedir,'SN-n1-Z1-N256'),
        # SN_n10_Z1_N256=osp.join(basedir,'SN-n10-Z1-N256'),
        # SN_n100_Z1_N256=osp.join(basedir,'SN-n100-Z1-N256'),

        SN_n001_Z3_N128=osp.join(basedir,'SN-n0.01-Z3-N128'),
        SN_n01_Z3_N128=osp.join(basedir,'SN-n0.1-Z3-N128'),
        SN_n1_Z3_N128=osp.join(basedir,'SN-n1-Z3-N128'),
        SN_n10_Z3_N128=osp.join(basedir,'SN-n10-Z3-N128'),
        SN_n100_Z3_N128=osp.join(basedir,'SN-n100-Z3-N128'),

    )

    sa = LoadSimFeedbackTestAll(models)

    # Check if pickle exists
    savdir = osp.join('/tigress', getpass.getuser(), 'FEEDBACK-TEST/pickles')
    if not osp.exists(savdir):
        os.makedirs(savdir)

    fpkl = osp.join(savdir, 'feedback-test-all.p')
    if not force_override and osp.isfile(fpkl):
        r = pd.read_pickle(fpkl)
        return sa, r

    df_list = []

    print(sa.models)
    # Save key results to a single dataframe
    for mdl in sa.models:
        print(mdl, end=' ')
        s = sa.set_model(mdl, verbose=False)
        df = s.get_summary_sn(as_dict=True)
        df_list.append(pd.DataFrame(pd.Series(df, name=mdl)).T)

    # print(df)
    df = pd.concat(df_list, sort=True).sort_index(ascending=False)
    df.to_pickle(fpkl)

    return sa, df

def plt_hst_sn_diff_Z(n0=1.0):
    """Plot history

    Parameters
    ----------
    n0 : float
         initial number density of hydrogen
    """

    sa, df = load_all_feedback_test_sn(force_override=False)
    fig, axes = plt.subplots(2,3,figsize=(16,8), sharex=True, constrained_layout=True)
    axes = axes.flatten()
    for mdl in sa.models:
        d = df.loc[mdl]
        if d['n0'] != n0:
            continue
        # print(mdl,d['n0'])

        h = d['hst']
        c = d['linecolorZ']
        x = h['time']
        axes[0].loglog(x, h['Rsh'], c=c, ls='-')
        axes[1].loglog(x, h['Mhi'], c=c, ls='-')
        axes[1].loglog(x, h['Msh'], c=c, ls='--')
        axes[2].loglog(x, h['pr'], c=c, ls='-', label=r'$Z=$' + '{0:g}'.format(d['Z_gas']))
        axes[3].loglog(x, h['Ethm']+h['Ekin'], c=c, ls='-')
        axes[3].loglog(x, h['Ethm'], c=c, ls='--')
        axes[3].loglog(x, h['Ekin'], c=c, ls=':')
        axes[4].loglog(x, h['pok_bub'], c=c, ls='-')
        #axes[5].loglog(x, h['dt'], c=c, ls='-')
        axes[5].semilogx(x,h['etash'], c=c, ls='-')

    plt.setp(axes[3:], xlabel=r'time [Myr]')#, xlim=(1e-3,1e0))
    plt.setp(axes[0], ylabel=r'$R_{\rm snr}\,[{\rm pc}]$')#, ylim=(10,50))
    plt.setp(axes[1], ylabel=r'${\rm mass}\;[M_{\odot}]$')#, ylim=(1e1,2e4))
    plt.setp(axes[2], ylabel=r'$p_{\rm snr}\;[M_{\odot}\,{\rm km}\,{\rm s}^{-1}]$')#, ylim=(1e4,5e5))
    plt.setp(axes[3], ylabel=r'$energy\;[{\rm erg}]$')
    plt.setp(axes[4], ylabel=r'$P_{\rm bub}/k_{\rm B}\;[{\rm K}\,{\rm cm}^{-3}]$') #, ylim=(1e3,1e10))
    plt.setp(axes[5], ylabel=r'$\eta = v_{\rm s,sh}t/R_{\rm sh}$', ylim=(0, 0.5))
    plt.suptitle('N={0:d}, '.format(d['Nx']) + r'$n_0=$' + '{0:g}'.format(n0) +\
                 r'$\;{\rm cm}^{-3}$')

    axes[2].legend(loc=4)
    plt.savefig('/tigress/jk11/figures/NEWCOOL/FEEDBACK-TEST-SN/SN-hst-n{0:g}.png'.format(n0))

    return fig

def plt_hst_sn_diff_n(Z=1.0):
    """Plot history

    Parameters
    ----------
    Z: float
        metallicty
    """

    sa, df = load_all_feedback_test_sn(force_override=False)

    fig, axes = plt.subplots(2,3,figsize=(16,8), sharex=True, constrained_layout=True)
    axes = axes.flatten()

    for mdl in sa.models:
        d = df.loc[mdl]

        if d['Z_gas'] != Z:
            continue
        # print(mdl,d['Z_gas'],d['r_sf'])

        h = d['hst']
        x = h['time']/d['t_sf']
        c = d['linecolorn']
        axes[0].loglog(x, h['Rsh']/d['r_sf'], c=c, ls='-')
        axes[1].loglog(x, h['Mhi']/d['Mhi_sf'], c=c, ls='-')
        axes[1].loglog(x, h['Msh']/d['Mhi_sf'], c=c, ls='--')
        axes[2].loglog(x, h['pr']/d['pr_sf'], c=c, ls='-',label=r'$n=$'+'{0:g}'.format(d['n0']))
        axes[3].loglog(x, (h['Ethm']+h['Ekin'])/(d['Ethm_sf'] + d['Ekin_sf']), c=c, ls='-')
        axes[3].loglog(x, h['Ethm']/(d['Ethm_sf'] + d['Ekin_sf']), c=c, ls=':')
        axes[3].loglog(x, h['Ekin']/(d['Ethm_sf'] + d['Ekin_sf']), c=c, ls='--')
        # axes[3].loglog(x, h['Ethm'], c=c, ls='--')
        # axes[3].loglog(x, h['Ekin'], c=c, ls=':')
        axes[4].loglog(x, h['pok_bub']/d['pok_bub_sf'], c=c, ls='-')
        #axes[5].loglog(x, h['dt'], c=c, ls='-')
        axes[5].semilogx(x,h['etash'], c=c, ls='-')

    plt.setp(axes[3:], xlabel=r'time [Myr]', xlim=(5e-2,2e1))
    plt.setp(axes[0], ylabel=r'$R_{\rm snr}/r_{\rm sf}$')#, ylim=(10,50))
    plt.setp(axes[1], ylabel=r'$mass/M_{\rm h,sf}$')#, ylim=(1e1,2e4))
    plt.setp(axes[2], ylabel=r'$p_{\rm r}/p_{\rm r,sf}$')#, ylim=(1e4,5e5))
    plt.setp(axes[3], ylabel=r'$E/(E_{\rm kin,sf}+E_{\rm thm,sf})$')#, ylim=(1e4,5e5))
    plt.setp(axes[4], ylabel=r'$P_{\rm bub}/k_{\rm B}\;[{\rm K}\,{\rm cm}^{-3}]$') #, ylim=(1e3,1e10))
    #plt.setp(axes[5], ylabel=r'$dt$')
    plt.setp(axes[5], ylabel=r'$\eta$', ylim=(0, 0.5))
    plt.suptitle('N={0:d}, '.format(d['Nx']) + r'$Z=$' + '{0:g}'.format(Z))

    for ax in axes:
        ax.axvline(1.0, linestyle='-', lw=0.5, color='grey')
    axes[2].legend(loc=4)
    plt.savefig('/tigress/jk11/figures/NEWCOOL/FEEDBACK-TEST-SN/SN-hst-Z{0:g}.png'.format(Z))

    return fig

class LoadSimFeedbackTestAll(object):
    """Class to load multiple simulations"""
    def __init__(self, models=None, muH=None):

        # Default models
        if models is None:
            models = dict()
        if muH is None:
            muH = dict()
            for mdl in models:
                muH[mdl] = 1.4271
        self.models = []
        self.basedirs = dict()
        self.muH = dict()
        self.simdict = dict()

        for mdl, basedir in models.items():
            if not osp.exists(basedir):
                print('[LoadSimFeedbackTestAll]: Model {0:s} doesn\'t exist: {1:s}'.format(
                    mdl,basedir))
            else:
                self.models.append(mdl)
                self.basedirs[mdl] = basedir
                if mdl in muH:
                    self.muH[mdl] = muH[mdl]
                else:
                    print('[LoadSimFeedbackTestAll]: muH for {0:s} has to be set'.format(
                          mdl))

    def set_model(self, model, savdir=None, load_method='pyathena', verbose=False):
        self.model = model
        try:
            self.sim = self.simdict[model]
        except KeyError:
            self.sim = LoadSimFeedbackTest(self.basedirs[model], savdir=savdir,
                                           muH=self.muH[model],
                                           load_method=load_method, verbose=verbose)
            self.simdict[model] = self.sim

        return self.sim

    # adding two objects
    def __add__(self, o):
        for mdl in o.models:
            if not (mdl in self.models):
                self.models += [mdl]
                self.basedirs[mdl] = o.basedirs[mdl]
                self.muH[mdl] = o.muH[mdl]
                if mdl in o.simdict: self.simdict[mdl] = o.simdict[mdl]

        return self

    # get self class with only one key
    def __getitem__(self, key):
        return self.set_model(key)

    def __setitem__(self, key, value):
        if (type(value) == LoadSimFeedbackTest):
            self.models.append(key)
            self.simdict[key] = value
            self.basedirs[key] = value.basedir
            self.muH[key] = value.muH
        else:
            print("Assigment only accepts LoadSimFeedbackTest")


"""
This code is for uploading the training data to AWS S3
"""
import boto3

bucket = 'sklearn-deploy-dataset'
region = 'us-east-1'
s3_session = boto3.Session().resource('s3')
s3_session.create_bucket(Bucket=bucket,
                         CreateBucketConfiguration=
                         {'LocationConstraint': region})
s3_session.Bucket(bucket).Object('train/train.csv').upload_file('data/train.csv')


import cPickle as pickle
from q4_code_questions import load_variable,save_variable
import matplotlib.pyplot as plt
import numpy as np

def plot_error_hist(exact_freq,error,n_hashes,n_buckets):
    plt.hist(np.log10(error),np.sqrt(len(error)),'b.')
    plt.title('n_hashes = ' + str(n_hashes) + ', n_buckets = 3*10^' + str(n_buckets))

def plot_error_evol(exact_freq,error,n_hashes,n_buckets):
    plt.plot(exact_freq,error,'b.')
    plt.xscale('log')
    plt.yscale('log')
    plt.title('n_hashes = ' + str(n_hashes) + ', n_buckets = 10^' + str(n_buckets))
    plt.xlabel('word frequency')
    plt.ylabel('Relative error between estimated and actual frequencies')

def load_frequencies(n_hashes,n_buckets):
    return load_variable('saved/approx_word_count_'+str(n_hashes)+'_'+str(n_buckets)+'.pck')

exact_word_counts = load_variable('saved/counts.pck');
total_words = sum(exact_word_counts.values())
exact_counts = [exact_word_counts[k]*1./total_words for k in exact_word_counts.keys()]
idx_subplot = 0
subplots_idx = [1,2,3,0]
for n_hash in [5]:
    for n_buckets in [4]:
        approx_counts = load_frequencies(n_hash,n_buckets)
        errors = [ abs(exact_word_counts[k] - approx_counts[k])*1./exact_word_counts[k] for k in exact_word_counts.keys() ]

        plot_error_evol(exact_counts,errors,n_hash,n_buckets)
        idx_subplot += 1

plt.show()