import numpy as np
import scipy.io as scio
from tqdm import tqdm
from .image_dataset import ImageDataset

class HierarchicalSynthDataset(ImageDataset):
    r'''Dataset reading from images.
    Args:
        Processes: A series of Callable object, which accept as parameter and return the data dict,
            typically inherrited the `DataProcess`(data/processes/data_process.py) class.
    '''

    def __init__(self, data_dir=None, data_list=None, cmd={}, **kwargs):
        self.load_all(**kwargs)
        self.data_dir = data_dir or self.data_dir
        self.data_list = data_list or self.data_list

        self.is_training = True

        self.debug = cmd.get('debug', False)

        self.image_paths = []
        self.targets = []

        self.parts = kwargs.get('parts', 3)

        print("Initializing SynthDataset")
        self.get_all_samples()

    def get_all_samples(self):
        for i in range(len(self.data_dir)):
            data = scio.loadmat(self.data_list[i])

            img_paths = list(data['imnames'][0])
            gts = list(data['wordBB'][0])
            texts = list(data['txt'][0])

            img_paths=[self.data_dir[i]+'/img/'+timg[0].strip() for timg in img_paths]
            self.image_paths += img_paths

            for gt, text in tqdm(zip(gts,texts)):
                bboxes, words = self.load_ann(gt, text)

                lines = []
                for k in range(bboxes.shape[0]):
                    polys = self._decode_polys(bboxes[k])
                    item = {
                        'seg_polys':polys,
                        'poly':bboxes[k].tolist(),
                        'text':words[k]
                    }
                    lines.append(item)
                self.targets.append(lines)
        self.num_samples = len(self.image_paths)

    def load_ann(self, gt, text):
        bboxes = np.array(gt)
        bboxes = np.reshape(bboxes, (bboxes.shape[0], bboxes.shape[1], -1))
        bboxes = bboxes.transpose(2, 1, 0)
        bboxes = np.round(bboxes)

        words = []
        for text_ in text:
            text_ = text_.replace('\n', ' ').replace('\r', ' ')
            words.extend([w for w in text_.split(' ') if len(w) > 0])

        return bboxes, words

    def _decode_polys(self, bbox):
        step = 1
#         bbox = np.array(bbox).reshape(-1)
#         assert bbox.shape[0]==8
        bbox = np.array(bbox).reshape(4,2)
        bbox = self.check_clockwise(bbox)
        bbox = self.check_bbox(bbox)
        bbox = np.array(bbox).reshape(-1)
        new_x_t = np.linspace(float(bbox[0]), float(bbox[2]), num=step*self.parts+1)
        new_y_t = np.linspace(float(bbox[1]), float(bbox[3]), num=step*self.parts+1)
        new_x_b = np.linspace(float(bbox[4]), float(bbox[6]), num=step*self.parts+1)
        new_y_b = np.linspace(float(bbox[5]), float(bbox[7]), num=step*self.parts+1)
        new_x = np.concatenate((new_x_t, new_x_b), axis=0)
        new_y = np.concatenate((new_y_t, new_y_b), axis=0)
        points = np.stack((new_x, new_y), axis=-1)

        polys = []
        u_ind = 0
        b_ind = points.shape[0]
        for i in range(self.parts):
            poly = np.concatenate((points[u_ind:u_ind+step+1],points[b_ind-step-1:b_ind]), axis=0)
            polys.append(poly.reshape((-1, 2)).tolist())
            u_ind = u_ind+step
            b_ind = b_ind-step
        return polys

    def check_bbox(self, bbox):
        xmin, xmax = min(bbox[:,0]), max(bbox[:,0])
        w = xmax-xmin
        ymin, ymax = min(bbox[:,1]), max(bbox[:,1])
        h = ymax-ymin
        d = np.sum((bbox-np.array([[xmin,ymin]]))**2,axis=1)
        start_ind = np.argmin(d)
        bbox = np.roll(bbox, -start_ind, axis=0)
        return bbox

    def check_clockwise(self, points):
        y = points[:, 1]
        idx = np.argmax(y)
        x1 = points[(idx - 1 + len(points)) % len(points)]
        x2 = points[idx]
        x3 = points[(idx + 1) % len(points)]
        x2_x1 = x2 - x1
        x3_x2 = x3 - x2
        judge_result = x2_x1[0] * x3_x2[1] - x2_x1[1] * x3_x2[0]
        if judge_result < 0:
            points = points[::-1]

        return points


#!/usr/bin/env python3.7

import iterm2

async def main(connection):
    app = await iterm2.async_get_app(connection)

    async def get_all_session():
        return [s for w in app.terminal_windows for t in w.tabs for s in t.sessions]

    async def get_session_by_tty(tty):
        sessions = [s for s in (await get_all_session()) if (await s.async_get_variable("tty")) == tty]
        if len(sessions) > 0:
            return sessions[0]
        else:
            return None

    async def exec_on_tab(tab, command):
        await tab.current_session.async_send_text(command+"\n")

    async def find_idle_tab():
        for tab in app.current_terminal_window.tabs:
            job_name = await tab.current_session.async_get_variable("jobName")
            if job_name == 'zsh' or job_name == 'bash' or job_name == 'sh' or job_name == 'fish':
                return tab
        return None

    async def find_tmux_tabs():
        return [t for t in enumerate(app.current_terminal_window.tabs) if (await t[1].current_session.async_get_variable("jobName")) == 'tmux']

    async def create_tab():
        window = app.current_terminal_window
        tmux_tabs = await find_tmux_tabs()
        if len(tmux_tabs) == 0:
            index = len(window.tabs)
        else:
            index, tab = tmux_tabs[0]
        return await window.async_create_tab(None, None, index)

    @iterm2.RPC
    async def activate_session_by_tty(tty):
        await app.async_activate()
        session = await get_session_by_tty(tty)
        if session != None:
            await session.async_activate()
    await activate_session_by_tty.async_register(connection)

    @iterm2.RPC
    async def exec_on_tab_at(tab_index, command):
        tab = [t for t in app.current_terminal_window.tabs][tab_index]
        await exec_on_tab(tab, command)
    await exec_on_tab_at.async_register(connection)

    @iterm2.RPC
    async def find_or_create_idle_tab():
       tab = await find_idle_tab()
       if tab != None:
           return tab
       else:
           tab = await create_tab()
           return tab
    await find_or_create_idle_tab.async_register(connection)

    @iterm2.RPC
    async def run_ssh(host):
       tab = await find_or_create_idle_tab()
       await app.async_activate()
       await tab.async_activate()
       await exec_on_tab(tab, "clear; SSH_INTERACTIVE=1 ssh " + host)
    await run_ssh.async_register(connection)

    @iterm2.RPC
    async def activate_tab_by_id(tab_id):
       await app.async_activate()
       tab = app.get_tab_by_id(tab_id)
       await tab.async_activate()
    await activate_tab_by_id.async_register(connection)

    @iterm2.RPC
    async def get_tab_commands():
        return [{"tab_id": t.tab_id, "command": await t.current_session.async_get_variable('commandLine'), "pid": await t.current_session.async_get_variable('jobPid')} for t in app.current_terminal_window.tabs]
    await get_tab_commands.async_register(connection)

iterm2.run_forever(main)


import copy
# import crawler.models as c_models
import ml.models as m_models
import fooreviews.article_data.article_formatter as article_formatter
import fooreviews.helper as helper
import fooreviews.models as f_models
import graphing.graphing_data as gdata
from urllib import quote_plus
import services.loggers as loggers
logger = loggers.Loggers(__name__).get_logger()

def get_graphing_data(request, frsku=None, article=None):
    slug = request.path.replace('/','')
    graph_data = {}
    try:
        graph_data = gdata.GraphingData(slug, frsku=frsku, article=article).get_data()
    except Exception as e:
        msg = '{}: {}'.format(type(e).__name__, e.args[0])
        logger.exception(msg)
    return graph_data

def get_share_string(article_obj):
    '''
    Return an encoded title string for embedding into social share_string sites.
    '''
    share_string = quote_plus(article_obj.article_title)
    return share_string

def get_related(article_obj):
    '''
    Get related articles for populating the 'Related Reviews' section.
    Initially, we're just getting the three most recent articles,
    excluding the one under which this section is found. We'll
    implement the retrieval of related articles with Elasticsearch's
    'more like this' feature once we have enough inventory of articles.

    NB: 'Recent Reviews' and list view use different templates because
    list view queries Elasticsearch, whereas recent reviews queries postgres.
    We can query Elasticsearch for everything, but it gets complicated and hard
    to justify the overhead.
    '''
    params = {'published': True, 'draft': False}
    related_set = f_models.Article.objects.filter(**params).order_by('-created_at')[:4]

    # exclude article_obj
    excluded_obj_list = []
    excluded_obj_list = [obj for obj in related_set if obj.id != article_obj.id]
    if len(excluded_obj_list) == 4:
        excluded_obj_list.pop(-1)
    if excluded_obj_list:
        return excluded_obj_list, True
    return excluded_obj_list, False

def get_image_url(article_obj):

    try:
        full_img_url = helper.full_img_url(article_obj.product.image.url)
    except Exception as e:
        msg = '{}: {}'.format(type(e).__name__, e.args[0])
        logger.exception(msg)
        full_img_url = 'IMAGE_NOT_FOUND'
    return full_img_url

def get_breadcrumb(article_obj):
    return helper.get_breadcrumb(article_obj)

def _get_raw_rev_count(data_obj):
    count = 0
    clf = data_obj.data.get('Classification')
    for clf_type, value in clf.items():
        # sum review count for Duplicate, Legitimate, and Spam
        count += value.get('review_count')
    return count

def _get_aspect_count(data_obj):
    ASPECTS_ANALYZED = 20 # this doesn't change
    count_dict = {
        'aspects_discovered': data_obj.data.get('Aspects Discovered'),
        'aspects_analyzed': ASPECTS_ANALYZED,
    }
    return count_dict

def get_intro_stats(article_obj):
    '''
    Return some analysis figures to display in the introductory section of
    the article.
    '''
    params = {
        'frsku': article_obj.product.frsku,
    }
    data_obj = m_models.Data.objects.filter(**params)[0]

    # comma-separate count if greater than 999
    schema_review_count = _get_raw_rev_count(data_obj) #int
    review_count = "{:,}".format(schema_review_count) # string
    kwargs = {
        'reviews_analyzed': review_count,
        'schema_review_count': schema_review_count,
    }
    kwargs.update(_get_aspect_count(data_obj))
    return kwargs

def _update_context(context, request, article_obj, frsku=None):
    '''
    Update the context dictionary with preformatted data.
    The functions and classes called here return the data in a dictionary format.
    We populate the template by accessing the key-value pairs directly so we want
    to maintain the relationships here.
    '''
    # get key specs, pricing, etc
    fa = article_formatter.FormatArticle(article_obj)
    fa.update_article()
    formatted_content = fa.get_formatted_content()
    context.update(formatted_content)

    # get graphing data
    graphing_data = get_graphing_data(request, frsku=frsku, article=article_obj)
    context.update(graphing_data)

    # get adjusted rating stars
    adjusted_star_rating = fa._get_star_rating(context.get('adjusted_rating'))
    context['adjusted_star_rating'] = adjusted_star_rating

    # get introductory stats
    context.update(get_intro_stats(article_obj))

    return context

def _update_article_obj(article_obj, context):
    '''
    Update article object fields for Elasticsearch aggregation and
    sorting based on the values computed thus far. Because we're updating
    the article object after we get a request from views.py, certain
    html attributes won't be populated until after we save article_obj and
    Elasticsearch index get updated (this automatically upon saving article_obj).
    '''

    article_obj.adjusted_rating = context.get('adjusted_rating')
    article_obj.adjusted_star_rating = context.get('adjusted_star_rating')
    article_obj.aspect_count = context.get('aspects_discovered')
    article_obj.review_count = context.get('schema_review_count')
    article_obj.save()

def _cache_context(context, article_obj):
    '''
    Caches article context dictionary to db.

    The caching doesn't seem to provide any benefit. The main bottleneck
    in the request cycle is related item lookup for 'Most Recent' reviews.
    This should not be an issue once we start using Elasticsearch directly
    instead of Postgres. In addition, site-wide caching once the site is
    in production should perform it's own serialization and eliminate the
    need for manual caching.
    '''
    entry = {
        'article': article_obj,
        'context': context
    }
    f_models.ArticleContextCache.objects.create(**entry)

def _get_cached_context(article_obj):
    lookup = {
        'article__product__frsku': article_obj.product.frsku
    }
    context_set = f_models.ArticleContextCache.objects.filter(**lookup)
    if context_set:
        context = context_set[0].context
        context['article'] = article_obj
        context['related_article_set'] = get_related(article_obj)
def get_article_obj(request):
    '''
    Return a models.Article object.
    '''
    url_tokens = [token for token in request.path.split('/') if token]
    slug = url_tokens[-1]
    params = {
        'slug': slug,
        'download_charts': True,

    }
    article_obj = f_models.Article.objects.filter(**params)[0]
    return article_obj, article_obj.product.frsku

def get_where_to_buy(article_obj):
    '''
    Return a dictionary of product-specific merchant URLs.
    '''
    params = {'article__product__frsku': article_obj.product.frsku}
    merchants = f_models.WhereToBuy.objects.filter(**params)
    where_to_buy = {}
    if merchants.count():
        for m in merchants:
            where_to_buy[m.merchant] = m.url
    return where_to_buy

def get_context(request, article_obj):
    '''
    Return a fully formatted context dictionary to be passed to the
    article DetailView template.
    '''

    context = {}
    load_highcharts_exporting_js = False
    data_page_enabled = False
    frsku = None
    try:
        if not article_obj:
            article_obj, frsku = get_article_obj(request)
            load_highcharts_exporting_js = True
        related_set, related_found = get_related(article_obj)
        if article_obj.download_charts:
            data_page_enabled = True
        context = {
             "article": article_obj,
             "related_article_set": related_set,
             "full_img_url": get_image_url(article_obj),
             'breadcrumb': get_breadcrumb(article_obj),
             "share_string": get_share_string(article_obj),
             'load_highcharts_js': True,
             'load_highcharts_exporting_js': load_highcharts_exporting_js,
             'related_found': related_found,
             'where_to_buy': get_where_to_buy(article_obj),
             'data_page_enabled': data_page_enabled,
        }
        context = _update_context(context, request, article_obj, frsku=frsku)
        if not article_obj.context_upto_date:
            _update_article_obj(article_obj, context)
    except Exception as e:
        msg = '{}: {}'.format(type(e).__name__, e.args[0])
        logger.exception(msg)
    return context


import math
import matplotlib.pyplot as plt
from .Generaldistribution import Distribution

class Uniform(Distribution):
    """ Uniform distribution class for calculating and
    visualizing a Uniform distribution.

    Attributes:
        mean (float) representing the mean value of the distribution
        stdev (float) representing the standard deviation of the distribution
        data_list (list of floats) extracted from the data file
        low (float) representing the smallest number in data_list
        high (float) representing the highest number in data_list
    """

    def __init__(self, low=0, high=10):
        if low == high:
            raise Exception('Invalid interval -  start and end of interval cannot be the same')
        self.low = low
        self.high = high
        Distribution.__init__(self, self.calculate_mean(),
                              self.calculate_stdev())

    def replace_stats_with_data(self):
        """Function to calculate low and high from the data set

                # Args:
                    None

                Returns:
                    float: the low value
                    float: the high value
                """
        self.low = min(self.data)
        self.high = max(self.data)
        if self.low == self.high:
            raise Exception('Invalid interval -  start and end of interval cannot be the same')
        self.calculate_mean()
        self.calculate_stdev()
        return self.low, self.high

    def calculate_mean(self, round_to=2):
        """Function to calculate the mean of the data set.

        Args:
             round_to (int): Round the mean value. [Default value: 2 floating point]

        Returns:
               float: mean of the data set
        """

        self.mean = (self.low + self.high) / 2

        return round(self.mean, round_to)

    def calculate_stdev(self, sample=True, round_to=2):
        """Function to calculate the standard deviation of the data set.

        Args:
             sample (bool): whether the data represents a sample or population
             round_to (int): Round the mean value. [Default value: 2 floating point]

        Returns:
            float: standard deviation of the data set
        """

        sqr_interval = (self.high - self.low) ** 2

        self.stdev = math.sqrt(sqr_interval / 12)

        return round(self.stdev, round_to)

    def calculate_cdf(self, x, round_to=2):
        """Cumulative distribution function calculator for the uniform distribution.

                Args:
                        x (float): point for calculating the
                                   cumulative distribution function

                Returns:
                        float: cumulative distribution function output
                """
        if x < self.low:
            self.cdf = 0
        elif self.low<=x<=self.high:
            self.cdf = (x - self.low)/(self.high-self.low)
        else:
            self.cdf = 1

        return round(self.cdf, round_to)

    def plot_histogram(self):
        """Function to output a histogram of the instance variable data using
                matplotlib pyplot library.

                Args:
                        None

                Returns:
                        None
                """
        plt.hist(self.data)
        plt.title("Histogram of Data")
        plt.xlabel("data")
        plt.ylabel("count")
        plt.show()

    def calculate_pdf(self, x, round_to=2):
        """Probability density function calculator for the uniform distribution.

                Args:
                        x (float): point for calculating the
                                   probability density function
                        round_to (int): Round the mean value. [Default value: 2 floating point]

                Returns:
                        float: probability density function output
        """
        self.pdf = 1/(self.high-self.low) if self.high >= x >= self.low else 0
        return round(self.pdf, round_to)

    def plot_bar_pdf(self):
        """Function to plot the pdf of the uniform distribution

        Args:
            None

        Returns:
            list: x values for the pdf plot
            list: y values for the pdf plot
        """

        x = []
        y = []

        # calculate the x values to visualize
        for i in range(int(self.low)-5, int(self.high)+5):
            x.append(i)
            self.calculate_pdf(i)
            y.append(self.pdf)

        # make the plots
        plt.bar(x, y)
        plt.title('Probability Density for Uniform Distribution')
        plt.ylabel('Probability')
        plt.xlabel('x')

        plt.show()

        return x, y

    def __repr__(self):
        """Function to output the characteristics of the Uniform instance

                Args:
                        None

                Returns:
                        string: characteristics of the Uniform distribution

                """

        return "mean {}, standard deviation {}".format(self.mean, self.stdev)


# -*- coding: utf-8 -*-
#!/usr/bin/env python3

import functions as f
import phrases as ph

# Initialization / Driver Code
if __name__ == "__main__":

    f.speak("Hello there. This is Marina, your digital assistant. How can I be of help? What is your name?")

    person = f.get_audio()
    if person == 0:
        person = 'there'

    f.speak("Hello " + person + '.')
    f.speak("What can i do for you?")
    myname = "Marina"
    wake = myname

while(1):

    data = f.get_audio()

    if data == 0:
        continue

    if "Marina" in str(data) or "hey Marina" in str(data) or data.count(wake) > 0:
        f.speak("I'm listening . . Go ahead " + person)

    if "exit" in str(data) or "Good bye" in str(data) or "sleep" in str(data) or "please stop" in str(data) :
        if (person == 'there'):
                f.speak("Ok. Bye for now")
        else:
            f.speak("Ok. Bye "+ person +'.')
        break

    ph.talkback(data,person)


import cv2
import gym
import gym.spaces
import numpy as np
import collections
import pdb

class ScaledFloatFrame(gym.ObservationWrapper):
    def observation(self, obs):

        res = np.array(obs).astype(np.float32) / 255.0
        #pdb.set_trace()
        return res

class MultiLives(gym.ObservationWrapper):
    def __init__(self, env):
        super(MultiLives, self).__init__(env)
        self.env = env
        self.lives = 0
        self.real_over = True
    def reset(self):
        if (self.real_over):
            state = self.env.reset()
            self.real_over = False
        else:
            state, _, _, _ = self.env.step(0)
        self.lives = self.env.unwrapped.ale.lives()
        return state
    def step(self, action):
        obs, reward, done, info = self.env.step(action)
        self.real_over = done
        # check current lives, make loss of life terminal,
        # then update lives to handle bonus lives
        lives = self.env.unwrapped.ale.lives()

        if lives < self.lives and lives > 0:
            # for Qbert sometimes we stay in lives == 0 condition for a few frames
            # so it's important to keep lives > 0, so that we only reset once
            # the environment advertises done.
            done = True
        self.lives = lives
        return obs, reward, done, info

class SkipFrame(gym.ObservationWrapper):
    def __init__(self, env, skip_number = 4):
        super(SkipFrame, self).__init__(env)
        self.SKIP = skip_number
    def reset(self):
        state = self.env.reset()
        self.COUNTER = self.SKIP
        return state
    def step(self, action):
        total_reward = 0
        states = []
        for i in range(self.SKIP):
            state, reward, terminal, _ = self.env.step(action)
            states.append(state)
            total_reward += reward
            if terminal:
                break
        return np.max(np.stack(states), axis = 0), total_reward, terminal, _

class ResizeGreyPic(gym.ObservationWrapper):
    def __init__(self, env, cut = [-1, -1], size = (84, 84)):
        super(ResizeGreyPic, self).__init__(env)
        self.SIZE = size
        self.observation_space = gym.spaces.Box(low = 0, high = 255, shape = (size[1], size[0], 1), dtype=np.float)
        self.cut = cut

    def observation(self, state):
        #print(state.shape)
        #pdb.set_trace()
        if len(state.shape) == 3:
            state = state.reshape(1, *state.shape)
        new_s_all = []
        for s in state:
            new_s = s[:,:,0] * 0.299 + s[:,:,1] * 0.587 + s[:,:,2] * 0.114
            if self.cut[0] != -1 or self.cut[1] != -1:
                new_s = new_s[self.cut[0]:self.cut[1], :]
                #new_s = new_s[34:194, :]#Pong
                #new_s = new_s[32:192, :]#Breakout
            new_s_all.append(new_s)
        #pdb.set_trace()
        new_s_all = np.stack(new_s_all).max(0)
        #print(new_s_all.shape)
        state = cv2.resize(new_s_all, self.SIZE, interpolation = cv2.INTER_AREA)
        state = state.reshape((*self.SIZE, 1)).astype(np.uint8)
        #plt.matshow(state)
        #delta = x_t - state
        #pdb.set_trace()
        #plt.show()
        return state# / 255.0

class ChangeAxis(gym.ObservationWrapper):
    def __init__(self, env):
        super(ChangeAxis, self).__init__(env)
        size = self.env.observation_space.shape
        self.observation_space = gym.spaces.Box(low = self.env.observation_space.low.reshape(-1)[0], high = self.env.observation_space.high.reshape(-1)[0], shape = (size[2], size[0], size[1]), dtype=self.env.observation_space.dtype)
    def observation(self, state):
        return state.transpose(2, 0, 1)

class CollectFrame(gym.ObservationWrapper):
    def __init__(self, env, collect_number = 4):
        super(CollectFrame, self).__init__(env)
        self.COLLECT = collect_number
        #pdb.set_trace()
        oldshape = self.env.observation_space.shape
        if len(oldshape) == 3:
            if oldshape[0] == 1: oldshape = (oldshape[1], oldshape[2])
            elif oldshape[2] == 1: oldshape = (oldshape[0], oldshape[1])
        #print(oldshape)
        self.observation_space = gym.spaces.Box(low=0, high=255, shape=(collect_number, *oldshape),
                                                dtype=self.env.observation_space.dtype)
        #pdb.set_trace()
    def reset(self):
        oldshape = self.env.observation_space.shape
        if len(oldshape) == 3:
            if oldshape[0] == 1: oldshape = (oldshape[1], oldshape[2])
            elif oldshape[2] == 1: oldshape = (oldshape[0], oldshape[1])
        self.states = np.zeros((self.COLLECT, *oldshape), dtype='float32')
        return self.observation(self.env.reset())
    def observation(self, state):
        if len(state.shape) == 3 and state.shape[0] == 1: state = state.reshape(state.shape[1], state.shape[2])
        if len(state.shape) == 3 and state.shape[2] == 1: state = state.reshape(state.shape[0], state.shape[1])
        self.states[:-1] = self.states[1:]
        self.states[-1] = state
        #pdb.set_trace()
        return self.states

def make_env(env_name):
    '''
    env = gym.make("PongNoFrameskip-v4")
    env = SkipFrame(env)
    env = ResizeGreyPic(env)
    #env = ChangeAxis(env)
    env = CollectFrame(env)
    return env
    '''
    cut = [-1, -1]
    if env_name[:4] == 'Pong':
        cut = [34, 194]
    elif env_name[:8] == 'Breakout':
        cut = [32, 192]
    env = gym.make(env_name)
    env = MultiLives(env)
    env = SkipFrame(env)
    env = ResizeGreyPic(env, cut)
    env = CollectFrame(env)
    env = ScaledFloatFrame(env)
    return env


from typing import List, Union, Any

from random import random
import numpy as np

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cf
import geopandas as gpd

from shapely.geometry import MultiPolygon, Polygon

def display_polygons(polygons_list: List[Union[Polygon, MultiPolygon]], fill=True, show=True) -> Any:
    """
    Display in different colours a set of polygons or multipolygons.

    Parameters
    ----------
    polygons_list: List[Union[Polygon, MultiPolygon]]
        List of shapely polygons or multipolygons
    fill: bool (default: True)
        Whether to fill in shapes or not
    show: bool (default: True)
        Whether to use plt.show or to return plotting axis
    """

    assert isinstance(polygons_list, list) or isinstance(polygons_list, np.ndarray) \
        or isinstance(polygons_list, gpd.array.GeometryArray), \
        f'The argument must be a list of polygons or multipolygons, got {type(polygons_list)}'

    land_50m = cf.NaturalEarthFeature('physical', 'land', '50m',
                                      edgecolor='darkgrey',
                                      facecolor=cf.COLORS['land_alt1'])

    fig = plt.figure(figsize=(13, 13))
    ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
    ax.add_feature(land_50m, linewidth=0.5, zorder=-1)
    ax.add_feature(cf.BORDERS.with_scale('50m'), edgecolor='darkgrey', linewidth=0.5, zorder=-1)

    from matplotlib import cm
    cmap = cm.get_cmap('viridis')

    norm = np.linspace(0.3, 1, len(polygons_list))
    for i, polygons in enumerate(polygons_list):
        c = cmap(norm[i])#(random(), random(), random())
        if isinstance(polygons, Polygon):
            polygons = [polygons]
        for poly in polygons:
            longitudes = [i[0] for i in poly.exterior.coords]
            latitudes = [i[1] for i in poly.exterior.coords]
            if not fill:
                ax.plot(longitudes, latitudes, transform=ccrs.PlateCarree(), color='k', zorder=0)
            else:
                ax.fill(longitudes, latitudes, transform=ccrs.PlateCarree(), color=c, zorder=0, alpha=0.5)
                # Remove interior
                if 0:
                    interior_polys = list(poly.interiors)
                    for i_poly in interior_polys:
                        longitudes = [i[0] for i in i_poly.coords]
                        latitudes = [i[1] for i in i_poly.coords]
                        ax.fill(longitudes, latitudes, transform=ccrs.PlateCarree(), color='white', zorder=0)

    if show:
        plt.show()
    else:
        return ax


from django.db import models
from django.urls import reverse
from django.template.defaultfilters import slugify

class ProductCategory(models.Model):
    category_name = models.CharField(max_length=30)
    is_navbar = models.BooleanField(default=True)
    parent_category = models.ForeignKey("ProductCategory", on_delete=models.CASCADE, related_name="category_parent", null=True, blank=True)

    def __str__(self):
        return self.category_name

    def save(self, *args, **kwargs):
        if self.parent_category == None:
            self.is_navbar = True
        else:
            self.is_navbar = False
        return super().save()

    class Meta:
        verbose_name = "Category"
        verbose_name_plural = "Categories"

class Size(models.Model):
    name = models.CharField(max_length = 30)

    def __str__(self):
        return f"{self.name} size"

class Color(models.Model):
    name = models.CharField(max_length = 30)

    def __str__(self):
        return self.name

class Products(models.Model):
    discounts = (
        (5, '5'),
        (10, '10'),
        (15, '15'),
        (20, '20'),
        (25, '25'),
        (30, '30'),
        (35, '35'),
        (40, '40'),
        (45, '45'),
        (50, '50')
    )
    cover_image = models.ImageField(upload_to="product_images")
    title = models.CharField(max_length = 50)
    slug = models.SlugField(null=True, blank=True)
    short_descriptions = models.TextField()
    long_descriptions = models.TextField()
    in_sale = models.BooleanField(default=False)
    price = models.FloatField()
    discount = models.IntegerField(choices=discounts, null=True, blank=True)
    new_price = models.FloatField(null=True, blank=True)
    category_id = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name='product_category')
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("product_detail", kwargs={"slug": self.slug})

    def save(self, *args, **kwargs):
        self.slug = slugify(f'{self.title}')
        if self.in_sale:
            self.new_price = self.price - self.price*(self.discount/100)
        return super().save(*args, **kwargs)

    class Meta:
        verbose_name = "Product"
        verbose_name_plural = "Products"

class ProductVersion(models.Model):
    in_stock = models.PositiveIntegerField(default=0)
    rate_avg = models.FloatField(default=0)
    review_count = models.PositiveIntegerField(default=0)
    read_count = models.PositiveIntegerField(default=0)
    size_id = models.ManyToManyField(Size, blank=True, related_name="product_size")
    color_id = models.ManyToManyField(Color, blank=True, related_name="product_color")
    product_id = models.ForeignKey(Products, on_delete=models.CASCADE, related_name="product_version")
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.product_id.title}'s version"

    class Meta:
        verbose_name = "Product Version"
        verbose_name_plural = "Product Versions"

class Image(models.Model):
    image = models.ImageField(upload_to="product_images")
    product_version_id = models.ForeignKey(ProductVersion, on_delete=models.CASCADE, related_name='product_version_image')

    def __str__(self):
        return f"{self.product_version_id.product_id.title}'s photo"

    class Meta:
        verbose_name = "Product Image"
        verbose_name_plural = "Product Images"

class ProductReview(models.Model):
    Rates = (
        (1, "20"),
        (2, "40"),
        (3, "60"),
        (4, "80"),
        (5, "100")
    )
    product_rate = models.IntegerField(choices=Rates)
    name = models.CharField(max_length=30)
    surname = models.CharField(max_length=30)
    review = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)
    product_version_id = models.ForeignKey(ProductVersion, on_delete = models.CASCADE, related_name="product_review")

    def __str__(self):
        return f"{self.name}'s reviews"

    class Meta:
        verbose_name = "Product Review"
        verbose_name_plural = "Product Reviews"


from functools import reduce
from itertools import groupby

from pypokerengine.engine.hand_evaluator import HandEvaluator
from pypokerengine.engine.pay_info import PayInfo

class GameEvaluator:

  @classmethod
  def judge(self, table):
    winners = self.__find_winners_from(table.get_community_card(), table.seats.players)
    hand_info = self.__gen_hand_info_if_needed(table.seats.players, table.get_community_card())
    prize_map = self.__calc_prize_distribution(table.get_community_card(), table.seats.players)
    return winners, hand_info, prize_map

  @classmethod
  def create_pot(self, players):
    side_pots = self.__get_side_pots(players)
    main_pot = self.__get_main_pot(players, side_pots)
    return side_pots + [main_pot]

  @classmethod
  def __calc_prize_distribution(self, community_card, players):
    prize_map = self.__create_prize_map(len(players))
    pots = self.create_pot(players)
    for pot in pots:
      winners = self.__find_winners_from(community_card, pot["eligibles"])
      prize = int(pot["amount"] / len(winners))
      for winner in winners:
        prize_map[players.index(winner)] += prize
    return prize_map

  @classmethod
  def __create_prize_map(self, player_num):
    def update(d, other): d.update(other); return d
    return reduce(update, [{i:0} for i in range(player_num)], {})

  @classmethod
  def __find_winners_from(self, community_card, players):
    score_player = lambda player: HandEvaluator.eval_hand(player.hole_card, community_card)

    active_players = [player for player in players if player.is_active()]
    scores = [score_player(player) for player in active_players]
    best_score = max(scores)
    score_with_players = [(score, player) for score, player in zip(scores, active_players)]
    winners = [s_p[1] for s_p in score_with_players if s_p[0] == best_score]
    return winners

  @classmethod
  def __gen_hand_info_if_needed(self, players, community):
    active_players = [player for player in players if player.is_active()]
    gen_hand_info = lambda player: { "uuid": player.uuid, "hand" : HandEvaluator.gen_hand_rank_info(player.hole_card, community) }
    return [] if len(active_players) == 1 else [gen_hand_info(player) for player in active_players]

  @classmethod
  def __get_main_pot(self, players, sidepots):
    max_pay = max([pay.amount for pay in self.__get_payinfo(players)])
    return {
        "amount": self.__get_players_pay_sum(players) - self.__get_sidepots_sum(sidepots),
        "eligibles": [player for player in players if player.pay_info.amount == max_pay]
    }

  @classmethod
  def __get_players_pay_sum(self, players):
    return sum([pay.amount for pay in self.__get_payinfo(players)])

  @classmethod
  def __get_side_pots(self, players):
    pay_amounts = [payinfo.amount for payinfo in self.__fetch_allin_payinfo(players)]
    gen_sidepots = lambda sidepots, allin_amount: sidepots + [self.__create_sidepot(players, sidepots, allin_amount)]
    return reduce(gen_sidepots, pay_amounts, [])

  @classmethod
  def __create_sidepot(self, players, smaller_side_pots, allin_amount):
    return {
        "amount": self.__calc_sidepot_size(players, smaller_side_pots, allin_amount),
        "eligibles" : self.__select_eligibles(players, allin_amount)
    }

  @classmethod
  def __calc_sidepot_size(self, players, smaller_side_pots, allin_amount):
    add_chip_for_pot = lambda pot, player: pot + min(allin_amount, player.pay_info.amount)
    target_pot_size = reduce(add_chip_for_pot, players, 0)
    return target_pot_size - self.__get_sidepots_sum(smaller_side_pots)

  @classmethod
  def __get_sidepots_sum(self, sidepots):
    return reduce(lambda sum_, sidepot: sum_ + sidepot["amount"], sidepots, 0)

  @classmethod
  def __select_eligibles(self, players, allin_amount):
    return [player for player in players if self.__is_eligible(player, allin_amount)]

  @classmethod
  def __is_eligible(self, player, allin_amount):
    return player.pay_info.amount >= allin_amount and \
        player.pay_info.status != PayInfo.FOLDED

  @classmethod
  def __fetch_allin_payinfo(self, players):
    payinfo = self.__get_payinfo(players)
    allin_info = [info for info in payinfo if info.status == PayInfo.ALLIN]
    return sorted(allin_info, key=lambda info: info.amount)

  @classmethod
  def __get_payinfo(self, players):
    return [player.pay_info for player in players]


from flask import request
from flask import current_app
import json
import requests

def main():
    eventType = request.headers["X-Kubernetes-Event-Type"]
    objType = request.headers["X-Kubernetes-Object-Type"]

    if objType != "Pod":
        return ""

    pod = request.get_json()
    if (pod == None) or ("status" not in pod) or ("containerStatuses" not in pod["status"]):
        current_app.logger.info("No container status information in event")
        return ""

    podName = pod["metadata"]["name"]

    states = summarizeContainerStates(pod)

    # Notify clients
    notification = { "containerStates": states, "podName": podName }
    r = requests.post("http://podvis.default:8001/", data=json.dumps(notification))
    current_app.logger.info("Pod vis event post status: %s" % r.status_code)

    return ""

def summarizeContainerStates(pod):
    containerStatuses = pod["status"]["containerStatuses"]
    states = {}
    for cs in containerStatuses:
        name = cs["name"]
        state = cs["state"]
        if "running" in state:
            states[name] = "running"
        elif "terminated" in state:
            states[name] = "terminated"
        else:
            s = "waiting"
            if ("waiting" in state) and ("reason" in state["waiting"]):
                s = state["waiting"]["reason"]
            states[name] = s

    current_app.logger.info("C states = %s" % states)
    return states