import os
import glob
import json
import datetime
from collections import defaultdict
import cv2
import numpy as np
import pandas as pd
from sklearn.neighbors import KDTree

from PySide6.QtCore import QObject, Signal

from ..utils.common import get_immediate_subdirectories, to_celsius
from ..utils.geojson import load_geojson, save_geojson, coords_wgs84_to_ltp

def load_modules(file):
    df = load_geojson(open(file, "r"))
    df = df.set_index("track_id")
    df_ltp = coords_wgs84_to_ltp(df)
    df_corners = df_ltp.loc[df_ltp["geometry_type"] == "Polygon"]
    df_centers = df_ltp.loc[df_ltp["geometry_type"] == "Point"]
    return df, df_corners, df_centers

def truncate_patch(patch, margin=0.05):
    """Truncates module edges by margin (percent of width) to remove module frame."""
    width = patch.shape[1]
    margin_px = int(margin*width)
    patch = patch[margin_px:-margin_px, margin_px:-margin_px]
    return patch

def remove_patches_with_sun_reflection(patch_files, sun_reflections):
    """Returns a copy of the patch_files list withput patches that contain a sun reflections as per 'sun_reflections'."""
    patch_files_filtered = []
    for patch_file in patch_files:
        patch_name = os.path.splitext(os.path.basename(patch_file))[0]
        if patch_name not in sun_reflections:
            patch_files_filtered.append(patch_file)
    return patch_files_filtered

def get_patch_temps(patch_files, margin, to_celsius_gain, to_celsius_offset):
    """Returns min, max, mean and median temperatures for each patch of a module."""
    temps = defaultdict(list)
    for patch_file in patch_files:
        patch = cv2.imread(patch_file, cv2.IMREAD_ANYDEPTH)
        if patch is not None:
            patch = truncate_patch(patch, margin)
            temps["min"].append(to_celsius(np.min(patch), to_celsius_gain, to_celsius_offset))
            temps["max"].append(to_celsius(np.max(patch), to_celsius_gain, to_celsius_offset))
            temps["mean"].append(to_celsius(np.mean(patch), to_celsius_gain, to_celsius_offset))
            temps["median"].append(to_celsius(np.median(patch), to_celsius_gain, to_celsius_offset))
    return temps

def mean_over_patches(dataframe, temps):
    """Compute the mean of the module temperatures over all patches of a module."""
    for patch_area_agg in ["min", "max", "mean", "median"]:
        dataframe["{}_temp".format(patch_area_agg)] = pd.Series({track_id: np.mean(t[patch_area_agg]) for track_id, t in temps.items()})

class AnalysisModuleTemperaturesWorker(QObject):
    finished = Signal()
    progress = Signal(float, bool, str)

    def __init__(self, dataset_dir, dataset_version, name, to_celsius_gain, to_celsius_offset,
            border_margin, neighbour_radius, ignore_sun_reflections, sun_reflections):
        super().__init__()
        self.is_cancelled = False
        self.timestamp = datetime.datetime.utcnow().isoformat()
        self.dataset_dir = dataset_dir
        self.dataset_version = dataset_version
        self.name = name
        self.to_celsius_gain = to_celsius_gain
        self.to_celsius_offset = to_celsius_offset
        self.border_margin = 0.01 * border_margin
        self.neighbour_radius = neighbour_radius
        self.ignore_sun_reflections = ignore_sun_reflections
        self.sun_reflections = sun_reflections
        self.progress_last_step = 0.0

    def get_neighbours_median_temp(self, df_centers, neighbour_radius=7, column="mean_of_max_temps"):
        """Returns a list of mean temperatures of the neighbours of each module in `df_centers`.
        The `neighbour_radius` defines the circle radius in which to look for neighbouring modules.
        The `column` specifies which temperature column to use."""
        centers = np.array([[d["coordinates"][0], d["coordinates"][1]] for d in df_centers["geometry"]])
        tree = KDTree(centers)
        neighbor_idxs = tree.query_radius(centers, r=neighbour_radius)

        # get mean temperature of neighbors
        neighbour_mean_temps = []
        for row_idx, neighbor_idx in enumerate(neighbor_idxs):
            progress = self.progress_last_step + (row_idx / len(neighbor_idxs)) / 5
            if self.is_cancelled:
                self.progress.emit(progress, True, "Cancelled")
                self.finished.emit()
                return

            neighbor_idx = np.delete(neighbor_idx, np.nonzero(neighbor_idx == row_idx))  # remove the current module from list of neighbors
            mean_temp = df_centers.iloc[neighbor_idx][column].median()
            neighbour_mean_temps.append(mean_temp)

            self.progress.emit(progress, False, "Computing corrected {}...".format(" ".join(column.split("_"))))
        self.progress_last_step = progress
        return neighbour_mean_temps

    def run(self):
        if self.dataset_version == "v1":
            patches_dir = os.path.join(self.dataset_dir, "patches_final", "radiometric")
        elif self.dataset_version == "v2":
            patches_dir = os.path.join(self.dataset_dir, "patches", "radiometric")

        file = os.path.join(self.dataset_dir, "mapping", "module_geolocations_refined.geojson")
        df, df_corners, df_centers = load_modules(file)

        temps = {}
        track_ids = sorted(get_immediate_subdirectories(patches_dir))
        for i, track_id in enumerate(track_ids):
            progress = (i / len(track_ids)) / 5
            if self.is_cancelled:
                self.progress.emit(progress, True, "Cancelled")
                self.finished.emit()
                return

            patch_files = sorted(glob.glob(os.path.join(patches_dir, track_id, "*")))
            if self.ignore_sun_reflections and self.sun_reflections is not None:
                patch_files = remove_patches_with_sun_reflection(patch_files, self.sun_reflections[track_id])
            temps[track_id] = get_patch_temps(patch_files, self.border_margin, self.to_celsius_gain, self.to_celsius_offset)

            self.progress.emit(progress, False, "Computing temperature distribution...")
        self.progress_last_step = progress

        mean_over_patches(df_corners, temps)
        mean_over_patches(df_centers, temps)

        for patch_area_agg in ["min", "max", "mean", "median"]:
            column = "{}_temp".format(patch_area_agg)
            neighbour_mean_temps = self.get_neighbours_median_temp(df_centers, neighbour_radius=self.neighbour_radius, column=column)
            if neighbour_mean_temps is None: # cancelled
                return

            df_corners["{}_corrected".format(column)] = df_corners.loc[:, column] - neighbour_mean_temps
            df_centers["{}_corrected".format(column)] = df_centers.loc[:, column] - neighbour_mean_temps

        # merge back into single geodataframe
        df_merged = df_corners.append(df_centers)

        # reuse WGS84 coordinates of original dataframe
        df_merged = df_merged.reset_index()
        df_merged = df_merged.set_index(["track_id", "geometry_type"])
        df = df.reset_index()
        df = df.set_index(["track_id", "geometry_type"])
        df_merged.update(df.loc[:, "geometry"])

        # write results to disk
        self.progress.emit(1, False, "Saving analysis results...")
        save_path = os.path.join(self.dataset_dir, "analyses", self.name)
        save_file = os.path.join(save_path, "results.geojson")
        print("Saving module temperature results in {}".format(save_file))
        os.makedirs(save_path, exist_ok=True)
        save_geojson(df_merged, open(save_file, "w"))

        print("Saving meta json in {}".format(os.path.join(save_path, "meta.json")))
        meta = {
            "type": "module_temperatures",
            "timestamp": self.timestamp,
            "dataset_dir": self.dataset_dir,
            "hyperparameters": {
                "border_margin": self.border_margin,
                "neighbour_radius": self.neighbour_radius,
                "ignore_sun_reflections": self.ignore_sun_reflections
            }
        }
        if self.ignore_sun_reflections and self.sun_reflections is not None:
            meta["hyperparameters"]["sun_reflections"] = self.sun_reflections
        json.dump(meta, open(os.path.join(save_path, "meta.json"), "w"))

        self.progress.emit(1, False, "Done")
        self.finished.emit()


from sys import stdin
from collections import defaultdict

stdin = open("input.txt", "r")

memory = defaultdict(int)

def apply_mask(val):
    return val | ones_mask

def set_bit(val, idx, bit):
    if bit == 1:
        val |= 2 ** idx
    else:
        val &= 2 ** 64 - 1 - 2 ** idx
    return val

def gen_floating(base_address, idx, cur_address, floating):
    if len(floating) == 0 or idx > floating[-1]:
        yield cur_address
    else:
        if idx in floating:
            yield from gen_floating(base_address, idx + 1, set_bit(cur_address, idx, 0), floating)
            yield from gen_floating(base_address, idx + 1, set_bit(cur_address, idx, 1), floating)
        else:
            yield from gen_floating(base_address, idx + 1, cur_address, floating)

for line in stdin:
    command, value = line.split(' = ')
    if command[1] == 'e':
        address = int(command.split('[')[1][:-1])
        value = int(value)
        address = apply_mask(address)
        for cur_address in gen_floating(address, 0, address, floating):
            memory[cur_address] = value
    else:
        ones_mask = 0
        floating = []
        for idx, ch in enumerate(value[::-1].strip()):
            if ch == 'X':
                floating.append(idx)
            elif ch == '1':
                ones_mask |= 2 ** idx

print(sum(memory.values()))


from itertools import permutations

def isPrime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def solution(numbers):
    result = []

    # numbers에서 나올 수 있는 숫자 리스트 도출
    arr = []
    numbers = [n for n in numbers]
    for i in range(1, len(numbers)+1):
        arr += list(permutations(numbers, i))
    arr = [int(("").join(i)) for i in arr]

    # arr에서 소수인 것만 리스트에 추가 후 중복 제거
    result = len(set([i for i in arr if isPrime(i)]))

    return result

프로그래머스/lv2/42839. 소수 찾기/소수 찾기.py


import tkinter as tk
from PIL import ImageTk, Image
import io

class PlantWidget(tk.Frame):  # tk.Frame
    def delete_widget_and_data(self):
        self.my_plant_service.delete_user_plant(self.values["_id"])
        self.destroy()

    def add_plant_to_pot(self):
        self.my_plant_service.handle_user_plant(self.values["_id"], True)
        self.load_planted_plants()
        self.destroy()

    def __init__(
        self, parent, plant, my_plant_service, load_planted_plants, update_pie_chart
    ):
        super().__init__(parent, borderwidth=2, relief="groove")

        self.values = plant
        self.my_plant_service = my_plant_service
        self.load_planted_plants = load_planted_plants
        self.update_pie_chart = update_pie_chart
        self.grid_columnconfigure(0, weight=1)
        # retrieve image data from MongoDB
        image_data = self.values.get("image_data")
        if image_data is not None:
            # convert image data to PIL Image object
            image = Image.open(io.BytesIO(image_data))

            # create PhotoImage from PIL Image object
            self.image_photo = ImageTk.PhotoImage(image.resize((150, 170)))

            # create label with image
            image_label = tk.Label(self, image=self.image_photo, height=170, width=150)

        name = tk.Label(
            self,
            justify="left",
            text="Name: \t\t{}".format(self.values["name"]),
        )
        type = tk.Label(
            self,
            justify="left",
            text="Type: \t\t{}".format(self.values["type"]),
        )
        watering = tk.Label(
            self,
            justify="left",
            text="Watering: \t{}".format(self.values["watering"]),
        )
        description = tk.Label(
            self,
            justify="left",
            text="Description: \t{}".format(self.values["desc"]),
            wraplength=720,
        )

        self.delete_button = tk.Button(
            self,
            text="Delete",
            command=lambda: (self.delete_widget_and_data(), self.update_pie_chart()),
            width=20,
            padx=5,
            pady=5,
        )
        self.add_to_pot_button = tk.Button(
            self,
            text="Add to Pot",
            command=lambda: (self.add_plant_to_pot(), self.update_pie_chart()),
            width=20,
            padx=5,
            pady=5,
        )

        self.add_to_pot_button.grid(row=0, column=1, padx=5, pady=5, sticky="nw")
        name.grid(row=0, column=0, padx=10, pady=10, sticky="nw")
        type.grid(row=1, column=0, padx=10, pady=10, sticky="nw")
        watering.grid(row=2, column=0, padx=10, pady=10, sticky="nw")
        description.grid(row=3, column=0, padx=10, pady=10, sticky="nw")
        self.delete_button.grid(row=3, column=1, padx=5, pady=5, sticky="nw")
        image_label.grid(row=0, column=1, padx=5, pady=5, sticky="nw", rowspan=3)


"""
http://stackoverflow.com/questions/3612094/better-way-to-zip-files-in-python-zip-a-whole-directory-with-a-single-command?lq=1
http://stackoverflow.com/questions/10060069/safely-extract-zip-or-tar-using-python
"""

import os
import string
import zipfile

from .log import warning

def zipdir(target_dir, dest_file, compression=zipfile.ZIP_DEFLATED):
    z = zipfile.ZipFile(dest_file, "w", compression=compression)
    rootlen = len(target_dir) + 1
    for base, _, filenames in os.walk(target_dir):
        for n in filenames:
            p = os.path.join(base, n)
            z.write(p, p[rootlen:])

def unzipdir(zip_name, dest, overwrite=False):
    if not overwrite and os.path.exists(dest):
        raise Exception("%s already exists!" % dest)
    zfile = zipfile.ZipFile(zip_name)
    for name in zfile.namelist():
        # This precaution is necessary with Python < 2.7.4 .
        if (
            ".." not in name
            and ":" not in name
            and name[0] in string.ascii_letters + "_"
        ):
            zfile.extract(name, dest)
        else:
            warning("unzipdir: didn't extract %s", name)

if __name__ == "__main__":
    import shutil

    zf = "tmp_zipdirtest.zip"
    if os.path.exists(zf):
        os.remove(zf)
    zipdir("../tests", zf)
    dest = "tmp_tests"
    if os.path.exists(dest):
        shutil.rmtree(dest)
    unzipdir(zf, dest)
    input(f"Check {zf} and {dest}, then press Enter to continue the test.")
    try:
        unzipdir(zf, dest)
    except:
        pass
    else:
        assert False, "An exception should be raised when the destination exists!"
    unzipdir("tmp_zipdirtest.zip", dest, overwrite=True)
    os.remove(zf)
    shutil.rmtree(dest)


# coding: utf-8
import os
import random
import typing as t
from PIL.Image import new as createImage, Image, QUAD, BILINEAR
from PIL.ImageDraw import Draw, ImageDraw
from PIL.ImageFilter import SMOOTH
from PIL.ImageFont import FreeTypeFont, truetype
from io import BytesIO
import time

ColorTuple = t.Union[t.Tuple[int, int, int], t.Tuple[int, int, int, int]]

DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data')
DEFAULT_FONTS = [os.path.join(DATA_DIR, 'DroidSansMono.ttf')]

class Captcha:
    lookup_table: t.List[int] = [int(i * 1.97) for i in range(256)]

    def __init__(self, width: int = 160, height: int = 60, key: int = None, length: int = 4,
                 fonts: t.Optional[t.List[str]] = None, font_sizes: t.Optional[t.Tuple[int]] = None):
        self._width = width
        self._height = height
        self._length = length
        self._key = (key or int(time.time())) + random.randint(1,100)
        self._fonts = fonts or DEFAULT_FONTS
        self._font_sizes = font_sizes or (42, 50, 56)
        self._truefonts: t.List[FreeTypeFont] = []
        random.seed(self._key)

    @property
    def truefonts(self) -> t.List[FreeTypeFont]:
        if self._truefonts:
            return self._truefonts
        self._truefonts = [
            truetype(n, s)
            for n in self._fonts
            for s in self._font_sizes
        ]
        return self._truefonts

    @staticmethod
    def create_noise_curve(image: Image, color: ColorTuple) -> Image:
        w, h = image.size
        x1 = random.randint(0, int(w / 5))
        x2 = random.randint(w - int(w / 5), w)
        y1 = random.randint(int(h / 5), h - int(h / 5))
        y2 = random.randint(y1, h - int(h / 5))
        points = [x1, y1, x2, y2]
        end = random.randint(160, 200)
        start = random.randint(0, 20)
        Draw(image).arc(points, start, end, fill=color)
        return image

    @staticmethod
    def create_noise_dots(image: Image, color: ColorTuple, width: int = 3, number: int = 30) -> Image:
        draw = Draw(image)
        w, h = image.size
        while number:
            x1 = random.randint(0, w)
            y1 = random.randint(0, h)
            draw.line(((x1, y1), (x1 - 1, y1 - 1)), fill=color, width=width)
            number -= 1
        return image

    def _draw_character(self, c: str, draw: ImageDraw, color: ColorTuple) -> Image:
        font = random.choice(self.truefonts)

        left, top, right, bottom = draw.textbbox((0, 0), c, font=font)
        w = int((right - left)*1.7) or 1
        h = int((bottom - top)*1.7) or 1

        dx1 = random.randint(0, 4)
        dy1 = random.randint(0, 6)
        im = createImage('RGBA', (w + dx1, h + dy1))
        Draw(im).text((dx1, dy1), c, font=font, fill=color)

        # rotate
        im = im.crop(im.getbbox())
        im = im.rotate(random.uniform(-30, 30), BILINEAR, expand=True)

        # warp
        dx2 = w * random.uniform(0.1, 0.3)
        dy2 = h * random.uniform(0.2, 0.3)
        x1 = int(random.uniform(-dx2, dx2))
        y1 = int(random.uniform(-dy2, dy2))
        x2 = int(random.uniform(-dx2, dx2))
        y2 = int(random.uniform(-dy2, dy2))
        w2 = w + abs(x1) + abs(x2)
        h2 = h + abs(y1) + abs(y2)
        data = (
            x1, y1,
            -x1, h2 - y2,
            w2 + x2, h2 + y2,
            w2 - x2, -y1,
        )
        im = im.resize((w2, h2))
        im = im.transform((w, h), QUAD, data)
        return im

    def create_captcha_image(self, chars: str, color: ColorTuple, background: ColorTuple) -> Image:
        image = createImage('RGB', (self._width, self._height), background)
        draw = Draw(image)

        images: t.List[Image] = []
        for c in chars:
            if random.random() > 0.5:
                images.append(self._draw_character(" ", draw, color))
            images.append(self._draw_character(c, draw, color))

        text_width = sum([im.size[0] for im in images])

        width = max(text_width, self._width)
        image = image.resize((width, self._height))

        average = int(text_width / len(chars))
        rand = int(0.25 * average)
        offset = int(average * 0.1)

        for im in images:
            w, h = im.size
            mask = im.convert('L').point(self.lookup_table)
            image.paste(im, (offset, int((self._height - h) / 2)), mask)
            offset = offset + w + random.randint(-rand, 0)

        if width > self._width:
            image = image.resize((self._width, self._height))

        return image

    def generate_image(self, chars: str) -> Image:
        background = random_color(238, 255)
        color = random_color(10, 200, random.randint(220, 255))
        im = self.create_captcha_image(chars, color, background)
        self.create_noise_dots(im, color)
        self.create_noise_curve(im, color)
        im = im.filter(SMOOTH)
        return im

    def generate(self, format: str = 'png') -> (BytesIO,str):
        code = generate_code(self._length)
        im = self.generate_image(code)
        out = BytesIO()
        im.save(out, format=format)
        out.seek(0)
        return out, code

    def write(self, output: str, format: str = 'png') -> (Image, str):
        code = generate_code(self._length)
        im = self.generate_image(code)
        im.save(output, format=format)
        return im, code

def random_color(start: int, end: int, opacity: t.Optional[int] = None) -> ColorTuple:
    red = random.randint(start, end)
    green = random.randint(start, end)
    blue = random.randint(start, end)
    if opacity is None:
        return (red, green, blue)
    return (red, green, blue, opacity)

def generate_code(length: int = 4):
    characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    return ''.join(random.choice(characters) for _ in range(length))


#!/usr/bin/env python3

import json
import sys
import http.client as http
import subprocess
import re
import os

###
# COLOR CONSTANTS
###
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

###
# CONFIGURABLE
###
JIRA_HOST = 'jira.atlassian.com'
JIRA_API_VERSION = '2'
GITLAB_HOST = 'gitlab.com'
GITLAB_API_VERSION = 'v4'
TARGET_BRANCH = 'develop' #TODO replace with repo default branch
TASK_NAME_REGEX='TCRM-[0-9]*'
SQUASH = True
REMOVE_SOURCE_BRANCH = True

if 'JIRA_TOKEN' not in os.environ:
    print(f'{RED}There is no "JIRA_TOKEN" in ENV variables! Exiting...{NC}')
    exit(1)
JIRA_TOKEN = os.environ['JIRA_TOKEN']

if 'GITLAB_TOKEN' not in os.environ:
    print(f'{RED}There is no "GITLAB_TOKEN" in ENV variables! Exiting...{NC}')
    exit(1)
GITLAB_TOKEN = os.environ['GITLAB_TOKEN']

def get_current_branch():
    return subprocess.run(['git', 'branch', '--show-current'], capture_output=True, text=True).stdout.strip('\n')

def send_https_request(method, host, path, headers, body = '', expected_status=200):
    connection = http.HTTPSConnection(host)
    try:
        connection.request(method, path, body, headers = headers)
        response = connection.getresponse()
        if(response.status != expected_status):
            raise Exception(
                f'{RED}Request failed{NC}\n' \
                f'request: {method} https://{host}{path}\n{body}\n\n' \
                f'response status: {str(response.status)}\n' \
                f'response body: {response.read().decode("utf-8")}'
            )
        return json.load(response)

    finally:
        connection.close()

def get_project_id():
    remote_url = subprocess.run(['git', 'remote', 'get-url', 'origin'], capture_output=True, text=True).stdout.strip('\n')
    project_name = re.search('/([a-zA-Z0-9\-]*)\.git', remote_url).group(1)
    min_access_level = '30' # Developer
    max_per_page = '100' # Gitlab API max. FIXME: because of max size per page there is some chance of not finding the project in large repos
    response = send_https_request(
        'GET',
        GITLAB_HOST,
        f'/api/{GITLAB_API_VERSION}/projects?' \
            'archived=false' \
            f'&min_access_level={min_access_level}' \
            '&simple=true' \
            '&with_merge_requests_enabled=true' \
            '&pagination=keyset' \
            f'&per_page={max_per_page}' \
            '&sort=desc' \
            '&order_by=id' \
            f'&search={project_name}',
        headers = {'PRIVATE-TOKEN': GITLAB_TOKEN},
    )

    for project in response:
        if remote_url in [ project['ssh_url_to_repo'], project['http_url_to_repo'] ]:
            return project['id']

    raise Exception(f'{RED}Cannot find project whith url:{NC} {remote_url}')

def get_user_id():
    return send_https_request(
        'GET',
        GITLAB_HOST,
        f'/api/{GITLAB_API_VERSION}/user',
        headers = {'PRIVATE-TOKEN': GITLAB_TOKEN}
    )['id']

def get_already_exist_mr_link(project_id, current_branch, target_branch):
    response = send_https_request(
        'GET',
        GITLAB_HOST,
        f'/api/{GITLAB_API_VERSION}/projects/{project_id}/merge_requests?' \
            'state=opened'\
            f'&source_branch={current_branch}' \
            f'&target_branch={target_branch}',
        headers = {'PRIVATE-TOKEN': GITLAB_TOKEN, 'Content-Type': 'application/json'}
    )
    if len(response) > 0:
        return response[0]['web_url']
    else:
        return None

def create_mr_request_body(title, current_branch, target_branch, user_id, squash=True, remove_source_branch=True):
    return json.dumps({
        'title': title,
        'source_branch': current_branch,
        'target_branch': target_branch,
        'assignee_id': user_id,
        'squash': squash,
        'remove_source_branch': remove_source_branch
    })

def create_mr(project_id, title, current_branch, target_branch, user_id, squash=True, remove_source_branch=True):
    return send_https_request(
        'POST',
        GITLAB_HOST,
        f'/api/{GITLAB_API_VERSION}/projects/{project_id}/merge_requests',
        body = create_mr_request_body(title, current_branch, target_branch, user_id, squash, remove_source_branch),
        headers = {'PRIVATE-TOKEN': GITLAB_TOKEN, 'Content-Type': 'application/json'},
        expected_status = 201
    )['web_url']

def get_task_title(task):
    return send_https_request(
        'GET',
        JIRA_HOST,
        f'/rest/api/{JIRA_API_VERSION}/issue/{task}',
        headers = {'Authorization': f'Bearer {JIRA_TOKEN}'}
    )['fields']['summary']

def get_service_name_from(mr_link):
    return re.search('\/([a-zA-Z\-]*)\/-\/merge_requests', mr_link).group(1)

def is_issue_link_already_exist(task, mr_link):
    response = send_https_request(
        'GET',
        JIRA_HOST,
        f'/rest/api/{JIRA_API_VERSION}/issue/{task}/remotelink',
        headers = {'Authorization': f'Bearer {JIRA_TOKEN}'}
    )

    for issue in response:
        if issue['object']['url'] == mr_link:
            return True
    return False

def create_jira_issue_link_request_body(mr_link):
    return json.dumps({
        'object': {
            'url': mr_link,
            'title': get_service_name_from(mr_link),
            'icon': {
                'url16x16': f'https://{GITLAB_HOST}/favicon.ico'
            },
        }
    })

def add_jira_issue_link(task, mr_link):
    return send_https_request(
        'POST',
        JIRA_HOST,
        f'/rest/api/{JIRA_API_VERSION}/issue/{task}/remotelink',
        body = create_jira_issue_link_request_body(mr_link),
        headers = {'Authorization': f'Bearer {JIRA_TOKEN}', 'Content-Type': 'application/json'},
        expected_status = 201
    )

def main():
    print('>>> Geting current branch name...')
    current_branch = get_current_branch()
    print(current_branch)

    if current_branch == TARGET_BRANCH:
        print(f'{RED}Cannot open MR:{NC} current branch is target ({TARGET_BRANCH})')
        exit(1)

    print('>>> Extracting task name from branch name...')
    match = re.match(TASK_NAME_REGEX, current_branch) # Validate branch name format
    if not match:
        print(f"{RED}Cannot open MR:{NC} current branch name ({TARGET_BRANCH}) doesn't start with 'TCRM-[0-9]*'")
        exit(1)
    task = match.group()
    print(task)

    print('>>> Getting GitLab project id...')
    project_id = get_project_id()
    print(project_id)

    print('>>> Check if MR is already oppened...')
    mr_link = get_already_exist_mr_link(project_id, current_branch, TARGET_BRANCH)

    if mr_link is not None:
        print(f'MR already opened:\n{BLUE}{mr_link}{NC}')
    else:
        print('No open MR was found. A new one will be created')

        print('>>> Getting task title...')
        title = task + get_task_title(task)
        print(title)

        print('>>> Creating MR...')
        mr_link = create_mr(
            project_id,
            title,
            current_branch,
            TARGET_BRANCH,
            get_user_id(),
            SQUASH,
            REMOVE_SOURCE_BRANCH
        )
        print(f'{GREEN}MR opened:{NC} {current_branch}')
        print(f'{BLUE}{mr_link}{NC}')

    print('>>> Check if MR link in task is already exist...')
    if is_issue_link_already_exist(task, mr_link):
        print('MR link already added to task')
    else:
        print('MR link not found')
        print('>>> Adding MR link to task...')
        add_jira_issue_link(task, mr_link)
        print(f'{GREEN}MR link added to task{NC}')

    print(f'{BLUE}https://{JIRA_HOST}/browse/{task}{NC}')

if __name__ == "__main__":
    main()


from q2generator import *
import math
import numpy as np
from collections import Counter
import matplotlib.pyplot as plt
import pdb
import random

def make_deepcopy( Switches, Graph_sz ):
	""" Creates a deep copy of all switch settiings within a graph"""
	return [[Switches[y][x] for x in range(Graph_sz)] for y in range(Graph_sz)]

def mh_w_gibbs(o, G, num_iter, error_prob=0.1):
	s = [] # store samples for the start positions
	X = [] # store switch states
	accepted=0
	proposals=0

	X.append(sample_switch_states(G.lattice_size)) # generate initial switch state
	s.append(sample_start_pos(G)) # set the initial start position as the one at G[0][0]

	# Gibbs for the startposition
	for n in range(num_iter):
		log_likelihoods=[]
		for r in range(G.lattice_size):
			for c in range(G.lattice_size):
				switches_prev_it = X[-1]
				logL = cond_L(o, G, G.get_node(r,c), switches_prev_it ,error_prob) #+math.log(1/n_pos)
				log_likelihoods.append( logL )

		# 'Unlogging'the likelihoods
		likelihoods = np.exp(log_likelihoods - np.max(log_likelihoods))
		# Normalization
		probabilities = likelihoods / np.sum( likelihoods)

		#Categorical sampling of new start position
		s_new = np.argmax(np.random.multinomial(1,probabilities))

		# we extract a node for the index for the sampled start position and add it to our list of smaples
		s.append( extract_start_pos(len(X[0]), s_new, G))

		# Metropolis hastings for the switchsettings

		# First we make a deepcopy of the switchsettinsg smapled from the previous iteration
		last_sampled_switch=X[-1]
		X_previous = make_deepcopy(last_sampled_switch, G.lattice_size )

		#We create a vector to store our new accepted switches
		x_new = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

		#We make a copy of the last accepted swittchsettings so that we can condition
		#on the rest of the nodes
		X_proposal = make_deepcopy(last_sampled_switch, G.lattice_size )

		for row in range(G.lattice_size):
			for col in range(G.lattice_size):
				s_last_it=s[-1] # we want to condition on s1 from the last iteration

				#For every node we sample a new switchsetting once from uniform distribution.
				x_proposal =np.random.randint(1,4)
				X_proposal[row][col] = x_proposal

				x_previous = X_previous[row][col] # we keep the previous value in case the proposal is rejected

				# Compute the loglikelihoods for the proposal and the previous iteration
				logL_previous = cond_L(o, G, s_last_it, X_previous, error_prob)
				logL_proposal = cond_L(o, G, s_last_it, X_proposal, error_prob)

				u = np.random.rand()

				#We compute the acceptence prob
				acceptance_prob = min( math.exp(logL_proposal - logL_previous) , 1)

				if u < acceptance_prob:
				# If the acceptence probability is larger than an uniform sample we accpt the proposal
					accepted+=1
					proposals+=1
					X_previous = make_deepcopy(X_proposal, G.lattice_size )
					x_new[row][col] = x_proposal

				else:
					X_proposal = make_deepcopy( X_previous , G.lattice_size )
					x_new[row][col] = x_previous
					proposals+=1

		X.append(x_new)

	print('Acceptance rate ',accepted / proposals)
	return s, X

def extract_start_pos(sz,new_s1,G):
	""" Extraxt a new sampled startposition  node from the graph"""
	c=np.mod(new_s1,sz)
	r=(int)(new_s1/sz)
	return G.get_node(r,c)

def gibbs(o, G, num_iter, prob_err=0.1):
	"""A gibbs sampler"""
	acceptancerate=0
	proposals=0

	s = [] # Start position samples
	X = [] # Switch setting samples
	graph_sz=len(G.G)
	X.append(sample_switch_states(G.lattice_size)) # generate initial switch state
	s.append(sample_start_pos(G)) # set the initial start position as the one at G[ 0 , 0 ]

	for n in range(num_iter):
		log_likelihoods=[]
		#For the gibbs sampler we want to go thorugh every possible start position
		#for s1 in the graph.
		for row in range(graph_sz):
			for col in range(graph_sz):
				#We compute the loged likelihood for every startposition
				start_pos=G.get_node(row,col)
				last_switchsetting=X[-1]
				log_likelihoods.append(cond_L(o, G,start_pos,last_switchsetting,prob_err))

		Likelihoods=np.exp(log_likelihoods-np.max(log_likelihoods)) #"unlogging" the logged likelihoods
		probabilities=Likelihoods/np.sum(Likelihoods) #Normalization

		#Sampling from a Categorical
		samples=np.random.multinomial(1,probabilities)
		new_s1 = np.argmax(samples)

		s.append(extract_start_pos(len(X[0]),new_s1,G)) #Extracting the node for the sampled start position
		X_last_sample= make_deepcopy(X[-1], G.lattice_size) #Making a deepcopy of the last sampled X... python is not nice sometimes

		temp=[]

		for row in range(graph_sz):
			for col in range(graph_sz):
				log_likelihoods=[]

				for switch_setting in range(1,4):
				#For every node in the current Graoh we want to go through every
				#possible switch setting, i.e. 1,2 and 3
					X_last_sample[row][col]=switch_setting
					last_start_pos=s[-1]
					namnare_temp= cond_L( o, G,last_start_pos, X_last_sample, prob_err) + math.log(1 / 3) # the prior is given in the assignment as 1/3
					log_likelihoods.append( namnare_temp )

				unlogedL=np.exp(log_likelihoods-np.max(log_likelihoods)) #unlogging
				normL=unlogedL/np.sum(unlogedL) #normalized liklihoods

				#categorical sampling of new switch setting
				samples=np.random.multinomial(1,normL)
				temp.append(np.max(samples))
				x_val_new = np.argmax(samples)
				X_last_sample[row][col] = 1 + x_val_new

		X.append(X_last_sample)
	return s, X

def convert_array_to_matrix(array_list):
	"""Converts an array of size 9 into a 3x3 matrix
	"""
	return [array_list[0:3], array_list[3:6], array_list[6:9]]

def convert_matrix_to_array( matrix ):
	"""converts a matrix into an array
	"""
	return [val for row in matrix for val in row]

def block_gibbs(o, G, num_iter, error_prob=0.1):
	s = [] # store samples for the start positions
	X = [] # store switch states
	X.append(sample_switch_states(G.lattice_size)) # generate initial switch state
	s.append(sample_start_pos(G)) # set the initial start position as the one at G[0][0]

	for n in range(num_iter):
		log_likelihoods=[]

		for row in range( G.lattice_size ):
			for col in range( G.lattice_size ):
				last_switch_setting = X[-1]

				# We go thorugh every possible value the start pos
				s1=G.get_node( row, col )

				logL = cond_L( o, G, s1, last_switch_setting ,error_prob)
				log_likelihoods.append( logL )

		Likleihood = np.exp( log_likelihoods - np.max( log_likelihoods ))
		#normalize the likelihoods
		probs = Likleihood / np.sum(Likleihood)
		# categorical sampling of new startposition
		s_new = np.argmax(np.random.multinomial(1,probs))
		# we extract a node for the index for the sampled start position and add it to our list of smaples
		s.append( extract_start_pos( len(X[0]), s_new, G))

		# A list with the indicies of the 3 blocks as suggested by Seyong during
		#the help session

		block_1 = [0,2,4]
		block_2 = [3,5,7]
		block_3 = [1,6,8]

		block_indicies = [block_1, block_2, block_3]
		#block_indicies = [[0,2,4], [3,5,7], [3,5,7]]

		# We create an array of the switches in the last samplex switch setting(s) X
		last_switches=X[-1]
		X_tmp=make_deepcopy( last_switches, G.lattice_size )
		X_array = convert_matrix_to_array(X_tmp )

		for block in block_indicies:
			log_likelihoods = []
			# So for the three blocks we want to go though all possible swithc setting for each position
			# Also note that we need three nested loops since we want to compute all possible cobintaions of switch settiings
			#for the three blocks
			for sw_b1 in range(1, 4):
				for sw_b2 in range(1,4):
					for sw_b3 in range(1,4):

						ind_b1 = block[0]
						X_array[ind_b1] = sw_b1

						ind_b2 = block[1]
						X_array[ind_b2] = sw_b2

						ind_b3 = block[2]
						X_array[ind_b3] = sw_b3

						# We convert the array into a matrix again so taht we can send it into cond_L()
						X_array =convert_array_to_matrix( X_array )

						prev_start_pos = s[-1]
						logL = cond_L( o, G, prev_start_pos, X_array, error_prob)+math.log(1/3)
						log_likelihoods.append(logL)
						X_array = convert_matrix_to_array(X_array)

			unLogL =np.exp(log_likelihoods-np.max(log_likelihoods)) # unlogging
			probs = unLogL / np.sum(unLogL) #normalization
			# Categorical resmapling
			x_new = np.argmax(np.random.multinomial( 1, probs ))

			# We extract the new indicies for the new sampled x SEBBE??
			X_NEW=extracter(x_new,sw_b2,sw_b3,X_array,block)

		X_new =convert_array_to_matrix( X_NEW )
		X.append(X_new)

	return s, X
def extracter(x,sw_b2,sw_b3,X,b):

	X[b[2]] = np.mod(x, sw_b3 ) + 1
	X[b[1]] = np.mod(np.floor_divide(x , sw_b3), sw_b2) + 1
	X[b[0]] =  np.floor_divide(x, (sw_b2*sw_b3)) + 1
	return X

def cond_L(o,G,start,X,p):
	"""Computes the conditional likelihood
	"""
	logL=0
	O_len=len(o)

	#Initialisation: We start by getting the next node/state from the starting node
	st_new=G.get_next_node(start,0, X)[0]
	curr_node=G.get_node(start.row,start.col)
	prev_dir=G.get_entry_direction(curr_node,st_new)

	for t in range (1,O_len):
		if prev_dir!=0:
			# if the previous direction was not 0, then we know that the next correct
			#observation should be zero
			if o[t]==0:
				#Therefore we add log(1-p) i.e 0.9 if the next observation is 0. Since
				#we did not enter through zero and now have to exit through another switch
				prob=1-p
				logP=math.log(prob)
				logL+=logP
			else:
				#if the observations is not zero it is incorrect. Therefore we add log(p)
				prob=p
				logP=math.log(prob)
				logL+=logP
		else:
			#We know that the previous observation was zero. Therefore we if the switch setting is correct
			#we should exit through the switch setting
			true_switch_setting=X[st_new.row][st_new.col]
			if o[t]==true_switch_setting:
				prob=1-p
				logP=math.log(prob)
				logL+=logP
			else:
				prob=p
				logP=math.log(prob)
				logL+=logP
		start, st_new, prev_dir= next_state(st_new,G,start,X)
	return logL

def next_state(st_n,G,start,X):
	st_hold=st_n
	entry_dir=G.get_entry_direction(start,st_n)
	st_n=G.get_next_node(st_n,entry_dir,X)
	start=st_hold
	st_n=st_n[0]
	node_prev=G.get_node(start.row,start.col)
	node_new=G.get_node(st_n.row,st_n.col)
	prev_dir=G.get_entry_direction(node_prev,node_new)
	return start, st_n, prev_dir

def calc_acc(burn_in,lag,s,s_truth):
	"""Calculates the accuracy of a start position sequence
	"""
	s_b=s[burn_in:-1]
	s_lag=s_b[0::lag]
	s_str=convert_node_to_string(s_lag)

	cnt = Counter(s_str)
	tmp1=cnt.most_common(9)
	occurances_most_common=tmp1[0][0][1]

	s1_truth_str_rep=str(convert_node_to_string([s_truth])[0])

	print('The most common sample was correct! It was: ',s1_truth_str_rep)
	print('Accuracy: ',int(occurances_most_common)/len(s_lag))
	print()

def convert_node_to_string(sequence):
	""" Converts a node to astring representation"""
	str_list=[]
	for s in sequence:
		str_list.append(str(s.row)+' '+str(s.col))
	return str_list

def convergence_histogram_plotter(burn_in,lag,s,s_truth):
	""""Computes histograms for 3 chains for all algorithms for s1 """
	print()
	true_s1_str=str(convert_node_to_string([s_truth])[0])

	s_mhg=s[0]
	sg=s[1]
	sbg=s[2]

	s_mhg=s_mhg[burn_in:-1]
	s_mhg=s_mhg[0::lag]

	sg=sg[burn_in:-1]
	sg=sg[0::lag]

	sbg=sbg[burn_in:-1]
	sbg=sbg[0::lag]
	#
	sbg_str_rep=convert_node_to_string(sbg)
	sg_str_rep=convert_node_to_string(sg)
	s_mhg_str_rep=convert_node_to_string(s_mhg)

	height0 = [sbg_str_rep.count('0 0'), sbg_str_rep.count('0 1'), sbg_str_rep.count('0 2'), sbg_str_rep.count('1 0'),sbg_str_rep.count('1 1'),sbg_str_rep.count('1 2'), sbg_str_rep.count('2 0'), sbg_str_rep.count('2 1'), sbg_str_rep.count('2 2')]
	bars0 = ['(0,0)','(0,1)', '(0,2)', '(1,0)', '(1,1)', '(1,2)','(2,0)','(2,1)','(2,2)']
	y0_pos = np.arange(len(bars0))
	plt.bar(y0_pos, height0, color = 'r')
	title_str='Start Positions - Blocked Gibbs - last half of samples - True s1='+str(true_s1_str)
	plt.title(title_str)
	plt.xlabel('Start positions')
	plt.ylabel('Occurances')
	plt.xticks(y0_pos, bars0)
	plt.show()

	height0 = [sg_str_rep.count('0 0'), sg_str_rep.count('0 1'), sg_str_rep.count('0 2'), sg_str_rep.count('1 0'),sg_str_rep.count('1 1'),sg_str_rep.count('1 2'), sg_str_rep.count('2 0'), sg_str_rep.count('2 1'), sg_str_rep.count('2 2')]
	bars0 = ['(0,0)','(0,1)', '(0,2)', '(1,0)', '(1,1)', '(1,2)','(2,0)','(2,1)','(2,2)']
	y0_pos = np.arange(len(bars0))
	plt.bar(y0_pos, height0, color = 'r')
	title_str='Start Positions - Gibbs - last half of samples - True s1='+str(true_s1_str)
	plt.title(title_str)
	plt.xlabel('Start positions')
	plt.ylabel('Occurances')
	plt.xticks(y0_pos, bars0)
	plt.show()

	height0 = [s_mhg_str_rep.count('0 0'), s_mhg_str_rep.count('0 1'), s_mhg_str_rep.count('0 2'), s_mhg_str_rep.count('1 0'),s_mhg_str_rep.count('1 1'),s_mhg_str_rep.count('1 2'), s_mhg_str_rep.count('2 0'), s_mhg_str_rep.count('2 1'), s_mhg_str_rep.count('2 2')]
	bars0 = ['(0,0)','(0,1)', '(0,2)', '(1,0)', '(1,1)', '(1,2)','(2,0)','(2,1)','(2,2)']
	y0_pos = np.arange(len(bars0))
	plt.bar(y0_pos, height0, color = 'r')
	title_str='Start Positions - MH within Gibbs - last half of samples - True s1='+str(true_s1_str)
	plt.title(title_str)
	plt.xlabel('Start positions')
	plt.ylabel('Occurances')
	plt.xticks(y0_pos, bars0)
	plt.show()

def main():

	data_seed = 9 #A seed to generate data
	n_lattice = 3
	T = 100
	p = 0.1
	G, X_truth, s_truth, o = generate_data(data_seed, n_lattice, T, p)
	num_iter = 1000

	seeds_chains=[225]#,11,2222] # Different seeds used to run different chains
	for seed in seeds_chains:
		np.random.seed(seed)

		#Running the samplers
		sbg, X=block_gibbs(o, G, num_iter)
		smhg, X = mh_w_gibbs(o, G, num_iter, p)
		sg, X = gibbs(o, G, num_iter, p)

		s_list=[smhg,sg,sbg]
		s_list_check_convergence=[smhg[0:200],sg[0:200],sbg[0:200]]
		s_list_check_convergence_second_half=[smhg[500:-1],sg[500:-1],sbg[500:-1]]

		burn_in=100
		lag=5

		# Computing accuracy for the algorithms
		# for s_s in s_list:
		#      calc_acc(burn_in,lag,s_s,s_truth[0])

		# Creating histograms for the startpositions
		#convergence_histogram_plotter(burn_in,lag,s_list,s_truth[0])

		#convergence_histogram_plotter(burn_in,lag,s_list_check_convergence,s_truth[0])
		convergence_histogram_plotter(0,lag,s_list_check_convergence_second_half,s_truth[0])

if __name__ == '__main__':
	main()


from enum import IntEnum
import numpy as np
from libcbm.storage.series import Series

import numba

class SpinupState(IntEnum):
    """The possible spinup states for stands during spinup"""

    AnnualProcesses = 1
    HistoricalEvent = 2
    LastPassEvent = 3
    GrowToFinalAge = 4
    Delay = 5
    End = 6

@numba.njit()
def _small_slow_diff(
    last_rotation_slow: np.ndarray, this_rotation_slow: np.ndarray
) -> np.ndarray:
    return (
        abs(
            (last_rotation_slow - this_rotation_slow)
            / (last_rotation_slow + this_rotation_slow)
            / 2.0
        )
        < 0.001
    )

def advance_spinup_state(
    spinup_state: Series,
    age: Series,
    delay_step: Series,
    final_age: Series,
    delay: Series,
    return_interval: Series,
    rotation_num: Series,
    min_rotations: Series,
    max_rotations: Series,
    last_rotation_slow: Series,
    this_rotation_slow: Series,
    enabled: Series,
) -> np.ndarray:
    """Run the vectorized spinup finite state machine.  Passed values are not
    modified by this function.

    Args:
        spinup_state (Series): The current spinup state value. See
            :py:class:`SpinupState`
        age (Series): The age.
        delay_step (Series): The spinup delay step, when in the Delay state
        final_age (Series): The final age at the end of spinup, which is also
            known as the inventory age in CBM
        delay (Series): The number of delay steps to perform in spinup.
        return_interval (Series): The number of years between historical
            disturbance rotations.
        rotation_num (Series): The number of rotations already performed
        min_rotations (Series): The minimum number of rotations to perform
        max_rotations (Series): The maximum number of rotations to perform
        last_rotation_slow (Series): The sum of Slow C pools at the end of the
            last rotation, prior to historical disturbance.
        this_rotation_slow (Series): The current sum of Slow C pools.
        enabled (Series): A boolean flag indicating the corresponding stands
            are finished spinup (when 0), or spinup is ongoing (when 1)

    Returns:
        np.ndarray: The array of updated SpinupState.
    """
    out_state = spinup_state.copy().to_numpy()
    _advance_spinup_state(
        age.length,
        spinup_state.to_numpy(),
        age.to_numpy(),
        delay_step.to_numpy(),
        final_age.to_numpy(),
        delay.to_numpy(),
        return_interval.to_numpy(),
        rotation_num.to_numpy(),
        min_rotations.to_numpy(),
        max_rotations.to_numpy(),
        last_rotation_slow.to_numpy(),
        this_rotation_slow.to_numpy(),
        enabled.to_numpy(),
        out_state,
    )
    return out_state

@numba.njit()
def _advance_spinup_state(
    n_stands: int,
    spinup_state: np.ndarray,
    age: np.ndarray,
    delay_step: np.ndarray,
    final_age: np.ndarray,
    delay: np.ndarray,
    return_interval: np.ndarray,
    rotation_num: np.ndarray,
    min_rotations: np.ndarray,
    max_rotations: np.ndarray,
    last_rotation_slow: np.ndarray,
    this_rotation_slow: np.ndarray,
    enabled: np.ndarray,
    out_state: np.ndarray,
) -> np.ndarray:
    for i in range(0, n_stands):
        state = spinup_state[i]
        if not enabled[i]:
            out_state[i] = SpinupState.End
            continue
        if state == SpinupState.AnnualProcesses:
            if age[i] >= (return_interval[i]):
                small_slow_diff = (
                    _small_slow_diff(
                        last_rotation_slow[i], this_rotation_slow[i]
                    )
                    if (last_rotation_slow[i] > 0)
                    | (this_rotation_slow[i] > 0)
                    else False
                )
                if ((rotation_num[i] > min_rotations[i]) & small_slow_diff) | (
                    rotation_num[i] >= max_rotations[i]
                ):
                    out_state[i] = SpinupState.LastPassEvent
                else:
                    out_state[i] = SpinupState.HistoricalEvent
            else:
                out_state[i] = SpinupState.AnnualProcesses
        elif state == SpinupState.HistoricalEvent:
            out_state[i] = SpinupState.AnnualProcesses
        elif state == SpinupState.LastPassEvent:
            if age[i] < final_age[i]:
                out_state[i] = SpinupState.GrowToFinalAge
            elif age[i] >= final_age[i]:
                if delay[i] > 0:
                    out_state[i] = SpinupState.Delay
                else:
                    out_state[i] = SpinupState.End
        elif state == SpinupState.Delay:
            if delay_step[i] < delay[i]:
                out_state[i] = SpinupState.Delay
            else:
                out_state[i] = SpinupState.End
        elif state == SpinupState.GrowToFinalAge:
            if age[i] < final_age[i]:
                out_state[i] = SpinupState.GrowToFinalAge
            else:
                if delay[i] > 0:
                    out_state[i] = SpinupState.Delay
                else:
                    out_state[i] = SpinupState.End
    return out_state


# -*- coding: utf-8 -*-
import numpy as np
from glob import glob
import math
import matplotlib.pyplot as plt
from collections import defaultdict
from collections import OrderedDict
from collections import namedtuple
import pickle
import re
import os.path
from scipy import stats

# used for label sorting
#import operator

# Configuration:
#-----------------

# PATHS to look for .jtl files

PATHS_API =   ["/Users/chrig/benchmark-results/v3_api/"]
PATHS_BURST = ["/Users/chrig/benchmark-results/v3_api_burst/"]
PATHS_NON_BURST = ["/Users/chrig/benchmark-results/v3_api_non_burst/"]
PATHS_GOOGLE = ["/Users/chrig/benchmark-results/v3_api_google/"]

PATHS = PATHS_API+PATHS_BURST+PATHS_NON_BURST+PATHS_GOOGLE
#PATHS = PATHS_API+PATHS_BURST+PATHS_GOOGLE
#PATHS = PATHS_API+PATHS_BURST+PATHS_NON_BURST
#PATHS = PATHS_GOOGLE
#PATHS = PATHS_NON_BURST
#PATHS = PATHS_BURST

PATHS =   ["/Users/chrig/benchmark-results/predict_burst_aws/"]

# Keys for grouping or selecting single instances
KEYS_GOOGLE = ["g1_g1", "s1_s1","s2_s1","s4_s1", "h2_s1", "h2_h2", "h4_s1"]
KEYS_AWS = ["c4l_s","c4l_m3m","m4l_s","m4l_m3m","c4xl_s","c4xl_m","m4xl_s"]
KEYS_BURST = ["mi_mi_burst", "s_mi_burst", "s_s_burst", "m_mi_burst" ,"m_s_burst"]
KEYS_NON_BURST = ["mi_mi", "s_mi","m_mi"]
KEYS_CONFIG_BURST = ["s_mi_burst", "s_s_burst"]+["m_mi_burst", "m_s_burst"]
KEYS_CONFIG_NON_BIRST = ["c4l_s","c4l_m3m","m4l_s","m4l_m3m"]
KEYS_BURST_NON_BURST = ["mi_mi_burst", "mi_mi", "s_mi_burst","s_mi", "m_mi_burst","m_mi"]

KEYS = KEYS_BURST+KEYS_NON_BURST+KEYS_AWS+KEYS_GOOGLE

#KEYS = KEYS_CONFIG_NON_BIRST
#KEYS = KEYS_CONFIG_BURST
#KEYS = KEYS_BURST_NON_BURST
#KEYS = KEYS_GOOGLE
#KEYS = KEYS_BURST
#KEYS = KEYS_BURST+KEYS_AWS+KEYS_GOOGLE

#KEYS = KEYS_NON_BURST
#KEYS= ["m_mi"]

#KEYS= ["322"]

FIG_NAME = "predict_burst_aws"
FIG_WIDTH = 10
FIG_HEIGHT = 7.5
ERROR_EVERY = 20
#enable data_file_overwrite
OVERWRITE_DATA_FILE = False
#Print results
PLOT_LINES = True
PLOT_SINGLE = True
PLOT_AVERAGE = False

AVERGAE_INDICATOR = False
SINGLE_INDICATOR = False

PLOT_BOXPLOT = True
ENABLE_FILE_INFO = False

SAMPLE_LENGTH = 120
PADDING = 0
GAP_LENGTH = 30

WILCOXONU = False
DROP_TIME = False

PRINT_LENGTH = 300
PRINT_START = 100

# What to compute: 1 = started reqeuest, 2 = finished, else = open requests
MODE = 2

class BenchmarkInstance(object):
    def __init__(self,filename,rps,metrics):
        self.filename = filename
        self.rps = rps
        self.metrics = metrics

class AverageInstance(object):
    def __init__(self, itk,averages,list_with_means, list_with_medians,mean_mean,mean_mean_std,mean_median,mean_median_std,median_req95,median_req99, count,metrics,mean_rsd):
        self.itk = itk
        self.averages = averages
        self.list_with_means = list_with_means
        self.list_with_medians = list_with_medians
        self.mean_mean = mean_mean
        self.mean_mean_std = mean_mean_std
        self.mean_median = mean_median
        self.mean_median_std = mean_median_std
        self.median_req95=median_req95
        self.median_req99=median_req99
        self.count = count
        self.metrics = metrics
        self.mean_rsd = mean_rsd

class Filename:
    def __init__(self, filename):
        self.filename = filename
        self.splits = self.filename.split("-")
        self.j_exid = self.splits[0][2:]
        self.benchmark = '-'.join(self.splits[1].split("_")[1:-1])
        self.types_short = self.splits[3]
        self.types_long = self.splits[4]
        self.number_of_slaves = self.get_flag('-j')
        self.execution_time = self.get_flag('-dur')
        self.ramp_up_time = self.get_flag('-rt')
        self.number_of_threads = self.get_flag('-thr')
        self.iteration_count = self.get_flag('-iter')
        self.timestamp = self.get_flag('-tst')
        self.os = self.get_flag('-os')

    def get_flag(self,flag):
        pattern = '(?<={})[a-zA-Z0-9]*'.format(flag)
        tmp =  re.search(pattern, self.filename)
        if tmp:
            return tmp.group(0)

        else:
            return "pattern not found"
#-------Methods----------

def save_object(obj, filename):
    with open(filename, 'wb') as output:
        pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

def load_object(filename):
    with open(filename, 'rb') as input:
        return pickle.load(input)

def compute_started_requests_per_second(data):
    rps = defaultdict(list)
    for request in data:
        if request["success"] == True:
            s = math.ceil(request["timeStamp"])
            rps[s].append(1)
    for key, value in rps.items():
        rps[key] = len(value)
    return OrderedDict(sorted(rps.items()))

def compute_finished_requests_per_second(data):
    rps = defaultdict(list)
    for request in data:
        if request["success"] == True:
            s = math.ceil(request["timeStamp"]+(request["elapsed"]/1000))
            rps[s].append(request)
    for key, value in rps.items():
        rps[key] = len(value)
    return OrderedDict(sorted(rps.items()))

def compute_open_requests_per_second(data):
    started_requests = defaultdict(list)
    finished_requests = defaultdict(list)

    for request in data:
        if(request["success"]) == True:
            s = math.ceil(request["timeStamp"])
            started_requests[s].append(request)
            f = math.ceil(request["timeStamp"]+(request["elapsed"]/1000))
            finished_requests[f].append(request)

    for skey, svalue in started_requests.items():
        started_requests[skey] = len(svalue)

    for fkey, fvalue in finished_requests.items():
        finished_requests[fkey] = len(fvalue)

    open_requests_per_second =  defaultdict(list)
    last_count = 0
    for j in range(sorted(list(finished_requests.keys()))[-1]):
        current_count = last_count+(started_requests.get(j,0) - finished_requests.get(j,0))
        open_requests_per_second[j] = current_count
        last_count = current_count

    return OrderedDict(sorted(open_requests_per_second.items()))

def remove_path_from_file(file):
    removed_path = file
    for p in PATHS:
        tmp = file.replace(p[:-1], "")
        if tmp != file:
            removed_path = tmp[1:]
    return removed_path

def get_name(file):
#    j_exid435-morphia_distributed_api_NoSlaves_debian-s1_s1-n1_standard_1_n1_standard_1-j0-t2500-s600-rt0_0_2016-07-25_07-21-34
    n = Filename(remove_path_from_file(file))
    return "{} {} {}".format(n.j_exid, n.benchmark ,n.types_short)

def get_uid(filename):
    try:
        found = re.search('j_(.+?)-', filename).group(1)
        return found
    except AttributeError:
        print("No id found for: %s"%(filename))

def compute_sample_metrics(data_dict):

    sample_length = SAMPLE_LENGTH-PADDING
    drop_time = 0
    last_request = 0
    nongap_duration = 0
    sample_size_reached = False
    drops = []

    for time, count in data_dict.items():
        if time == last_request+1:
            nongap_duration = nongap_duration+1
            last_request = time
            if nongap_duration >= sample_length:
                sample_size_reached = True
        else:
            #after a gap
            if sample_size_reached:
                drop_time = last_request
                drops.append(last_request)
            if last_request + GAP_LENGTH <= time:
                nongap_duration = 0
                sample_size_reached = False
            last_request = time

    if sample_size_reached == True:
        drop_time = last_request

    if drop_time == 0:
        drop_time = last_request
#        print("Attention: includes gaps! Set to end:{}".format(last_request))
        raise Exception("No GAP-free part found!")

    print(drops)
    values = list(data_dict.values())
    dict_keys = list(data_dict.keys())

    #adjusting
    if PADDING and drop_time-PADDING in dict_keys:
        drop_time_index = dict_keys.index(drop_time-PADDING)
        drop_time = drop_time-PADDING
    else:
        print("droptime: {}".format(drop_time))
        drop_time_index =  dict_keys.index(drop_time)
        while dict_keys[drop_time_index] >= drop_time-PADDING:
            drop_time_index =  drop_time_index-1
            print("finding next smaller:{}>{}".format(dict_keys[drop_time_index],drop_time-PADDING))
        drop_time = dict_keys[drop_time_index]

    if DROP_TIME and DROP_TIME<dict_keys[-1]:
        drop_time = DROP_TIME
        drop_time_index = dict_keys.index(drop_time)

    mean = np.mean(values[drop_time_index-sample_length:drop_time_index])
    median = np.median(values[drop_time_index-sample_length:drop_time_index])
    std = np.std(values[drop_time_index-sample_length:drop_time_index])
    rsd = 100*(std/mean)

    return sample_metric(mean, median, std, drop_time, sample_length,rsd)

def print_sample_indicator(requests_dict, metric,drop_time,sample_length):
    keys = list(requests_dict.keys())
    index_drop_time = keys.index(drop_time)
    ax.plot(keys[index_drop_time-sample_length:index_drop_time], [metric for i in range(sample_length)],'k.',markersize=6)

def print_sample(filename, requests_dict):
    label = get_label_for_key(filename.split(" ")[2])

    ax.plot(list(requests_dict.keys())[PRINT_START:], list(requests_dict.values())[PRINT_START:],'-',markersize=6,label=label)

def print_averages(itk, averages):
    label = "mean: {}".format(itk)
    keys = list(averages.keys())
    values = [i[0] for i in list(averages.values())]
    ax.plot(keys, values,'-',markersize=6,label=label)

def print_line_with_error_indicators(itk, averages):
    label = "mean: {}".format(get_label_for_key(itk))
    errorevery = ERROR_EVERY
    keys = list(averages.keys())
    values = [i[0] for i in list(averages.values())]
    lowers = [i[1] for i in list(averages.values())]
    uppers = [i[2] for i in list(averages.values())]
    ax.errorbar(keys, values, yerr=[lowers, uppers],errorevery=errorevery, ecolor='k',capthick=2, label=label)

def print_stats_single(benchmark_instance):
    print("name: %s"%(benchmark_instance.filename))
    print("mean: %f"%(benchmark_instance.mean))
    print("median: %f"%(benchmark_instance.median))
    print("standard-deviation: %f"%(benchmark_instance.std))
    print("------------------")

def get_instance_type_key(file):
    filename = remove_path_from_file(file)
    tmp_itk = None
    for itk in KEYS:
        if filename.find(itk) > -1:
            tmp_itk = itk
            break
    return tmp_itk

def get_data_file_path_name(file):
    uid = get_uid(file)
    return ".data/{}.pkl".format(uid)
    # for more complex datafile names
    #    instance_type_key = get_instance_type_key(file)
    #    return ".data/{}_{}.pkl".format(uid, instance_type_key)

def get_label_for_key(key):
    if key == "mi_mi":
        return "A_nb1m"
    elif key == "mi_mi_burst":
        return "A_b1m"
    elif key == "s_mi":
        return "A_nb1s"
    elif key == "s_mi_burst":
        return "A_b1s_1"
    elif key == "s_s_burst":
        return "A_b1s_2"
    elif key == "m_mi":
        return "A_nb2"
    elif key == "m_mi_burst":
        return "A_b2_1"
    elif key == "m_s_burst":
        return "A_b2_2"
    elif key == "m4l_s":
        return "A_gp2_1"
    elif key == "m4l_m3m":
        return "A_gp2_2"
    elif key == "m4xl_s":
        return "A_gp4"
    elif key == "c4l_s":
        return "A_co2_1"
    elif key == "c4l_m3m":
        return "A_co2_2"
    elif key == "c4xl_s":
        return "A_co4"
#google
    elif key == "g1_g1":
        return "G_b1"
    elif key == "s1_s1":
        return "G_gp1"
    elif key == "s2_s1":
        return "G_gp2"
    elif key == "s4_s1":
        return "G_gp4"
#    elif key == "h2_s1":
#        return "G_co2_1"
    elif key == "h2_h2":
        return "G_co2"
    elif key == "h4_s1":
        return "G_co4"
    else:
        print ("no label found for key:{}".format(key))
        return key

sample_metric = namedtuple('SampleMetric','mean, median, std, drop_time, sample_length, rsd')
config = namedtuple('SampleMetric','KEYS SAMPLE_LENGTH PADDING GAP_LENGTH WILCOXONU DROP_TIME PRINT_LENGTH FIG_NAME')
#---init script -----------------------------------------------------------------------------------------------------
#NON_BURSTING_CONFIG = config(["c4l_s","c4l_m3m","m4l_s","m4l_m3m"],240, 0,100,True,False,360,"deployment_options_non_bursting")
#BURSTING_CONFIG = config(["s_mi_burst", "s_s_burst", "m_mi_burst", "m_s_burst"],150,0,100,True, False,250,"deployment_options_bursting")
#
#config = BURSTING_CONFIG
#KEYS=config.KEYS
#SAMPLE_LENGTH = config.SAMPLE_LENGTH
#PADDING = config.PADDING
#GAP_LENGTH = config.GAP_LENGTH
#WILCOXONU = config.WILCOXONU
#DROP_TIME = config.DROP_TIME
#PRINT_LENGTH = config.PRINT_LENGTH
#FIG_NAME = config.FIG_NAME

#---init script -----------------------------------------------------------------------------------------------------
files = []
for path in PATHS:
        files.extend(glob(path+'*.jtl'))

for idx,file in enumerate(files):

    # check if there is a key in the filename
    instance_type_key = get_instance_type_key(file)
    if instance_type_key == None:
        print("No KEY found in filename and therefore ignored: %s"%(file))
        continue

    filename = get_name(file)
    if os.path.exists(get_data_file_path_name(file)) and not OVERWRITE_DATA_FILE:
        continue
    print("%s of %s: %s"%(idx+1, len(files),filename))
    # read the raw file
    data = np.genfromtxt(file, delimiter=',', skip_header=0, names=True, usecols=("timeStamp", "elapsed", "success"), dtype=[("timeStamp", float), ( "elapsed", float), ("success", bool)])

    #transform times from ms to s based on numpy array operations
    time_in_ms_from_start = data['timeStamp']-data['timeStamp'][0]
    time_in_s_from_start = time_in_ms_from_start/(1000)
    data['timeStamp'] = time_in_s_from_start

    if MODE == 1:
        rps = compute_started_requests_per_second(data)
    elif MODE == 2:
        rps= compute_finished_requests_per_second(data)
    else:
        rps = compute_open_requests_per_second(data)

    benchmark_instance = BenchmarkInstance(filename,rps, None)

    save_object(benchmark_instance,get_data_file_path_name(file))
    del(benchmark_instance)

benchmarks_per_type = defaultdict(list)
for idx,file in enumerate(files):
    if ENABLE_FILE_INFO:
        print("Check File %s of %s"%(idx+1, len(files)))
    instance_type_key = get_instance_type_key(file)
    if instance_type_key in KEYS:
        benchmark_instance = load_object(get_data_file_path_name(file))
        print("Load File %s"%(benchmark_instance.filename))

        if PRINT_LENGTH and max(list(benchmark_instance.rps.keys()))>PRINT_LENGTH:
            benchmark_instance.rps = {k:v for k,v in benchmark_instance.rps.items() if k < PRINT_LENGTH}

        if DROP_TIME and DROP_TIME not in benchmark_instance.rps.keys():
            print("{} exluded!".format(benchmark_instance.filename))
            continue

        sample_metrics = compute_sample_metrics(benchmark_instance.rps)
        benchmark_instance.metrics = sample_metrics
        benchmark_instance.mean = sample_metrics.mean
        benchmark_instance.median = sample_metrics.median
        benchmark_instance.std = sample_metrics.std
        benchmark_instance.drop_time = sample_metrics.drop_time
        benchmark_instance.sample_length =  sample_metrics.sample_length

        benchmarks_per_type[instance_type_key].append(benchmark_instance)

averages_per_type = {}
for itk, benchmarks in benchmarks_per_type.items():
#    print(itk)
    aggregated_counts = defaultdict(list)
    #collect for every second the count form each individual benchmark

    list_with_means = []
    list_with_medians = []
    list_with_rsds = []
    for benchmark in benchmarks:
        list_with_means.append(benchmark.mean)
        list_with_medians.append(benchmark.median)
        list_with_rsds.append(benchmark.metrics.rsd)
        for timestamp, count in benchmark.rps.items():
            aggregated_counts[timestamp].append(count)

    averages = defaultdict(list)
    for i in range(max(list(aggregated_counts.keys()))+1):#we read the biggest number and take it as index, therefore +1
        if len(aggregated_counts[i]) > 0:
            item_mean = np.mean(aggregated_counts[i])
            item_lower_error= item_mean-np.percentile(aggregated_counts[i],25)
            item_upper_error = np.percentile(aggregated_counts[i],75)-item_mean
            averages[i] = [item_mean,item_lower_error,item_upper_error]

    only_averages = defaultdict(int)
    for key, value in averages.items():
        only_averages[key] = value[0]

    mean_rsd = np.mean(list_with_rsds)
#    mean_mean = np.mean(list_with_means)
#    mean_mean_std =  np.std(list_with_means)
#    mean_median = np.mean(list_with_medians)
#    mean_median_std = np.std(list_with_medians, ddof=1)

    metrics = compute_sample_metrics(only_averages)
    median = metrics.median
    median_std = metrics.std
    mean = metrics.mean
    mean_std = metrics.std

    median_req95 = ((1.96*mean_std)/(mean*0.05))**2
    median_req99 = ((2.576*mean_std)/(mean*0.05))**2
    count = len(benchmarks)

    #round tp next int
    median_req95 = math.ceil(median_req95)
    median_req99 = math.ceil(median_req99)

    averages_per_type[itk] = AverageInstance(itk,averages,list_with_means, list_with_medians,mean,mean_std,median,median_std,median_req95,median_req99, count,metrics, mean_rsd)

print("=== SINGLE ===")
for itk, benchmarks in  sorted(benchmarks_per_type.items(), key=lambda tup: tup[0]):
    for benchmark in benchmarks:
        print_stats_single(benchmark)

print("=== TYPES ===")
file_stats = open("{}_stats.csv".format(FIG_NAME), "w")
file_stats.write("sample length: {}, padding: {},gap: {},nett lenngth: {}\n".format(SAMPLE_LENGTH, PADDING, GAP_LENGTH, SAMPLE_LENGTH-PADDING))
file_stats.write("Instance Type,Count,95%CL (5%MOE),99%CL (5%MOE),Mean,Median,Mean Std,RSD,Mean RSD\n")
for key in KEYS:
    if key in averages_per_type.keys():
        average_instance = averages_per_type[key]
        file_stats.write("{},{},{},{},{},{},{},{},{}\n".format(get_label_for_key(average_instance.itk),average_instance.count,average_instance.median_req95,average_instance.median_req99,average_instance.mean_mean,average_instance.mean_median,average_instance.mean_median_std,average_instance.metrics.rsd, average_instance.mean_rsd))
        print("---- {}----\ncount: {}\n95%CL (5%MOE):{}\n99%CL (5%MOE): {}\nmedian: {}\nstd: {}".format(average_instance.itk,average_instance.count,average_instance.median_req95,average_instance.median_req99,average_instance.mean_median,average_instance.mean_median_std))
file_stats.close()

if WILCOXONU:
    file_wilcoxon = open("{}_wilcoxon.csv".format(FIG_NAME), "w")
    file_wilcoxon.write("c1,count_c1,mean1,std1 ,c2, count_c2,mean_c2,std_c2,p-Value, U-value\n ")
    print("Webapp-DB, Webapp-DB, p-Value, U-value")
    temp = KEYS.copy()
    for key in KEYS:
        temp.remove(key)
        for temp_key in temp:
            wa1 = key.split("_")[0]
            db1 = key.split("_")[1]
            wa2 = temp_key.split("_")[0]
            db2 = temp_key.split("_")[1]

            key_mean = averages_per_type[key].mean_mean
            key_std = averages_per_type[key].mean_mean_std
            temp_mean = averages_per_type[temp_key].mean_mean
            temp_std = averages_per_type[temp_key].mean_mean_std

            wil = stats.mannwhitneyu(averages_per_type[key].list_with_means,averages_per_type[temp_key].list_with_means,alternative='two-sided')
            print("{}-{}, {}-{}, {}, {}".format(wa1, db1, wa2, db2,wil.pvalue, wil.statistic))
#            file_wilcoxon.write("{}, {}, {}, {}, {}\n".format(wa1, db1, wa2, db2,wil.pvalue))
            file_wilcoxon.write("{}, {},{},{},{},{},{}, {},{},{}\n".format(get_label_for_key(key),len(averages_per_type[key].list_with_means),key_mean,key_std, get_label_for_key(temp_key), len(averages_per_type[temp_key].list_with_means),temp_mean,temp_std,wil.pvalue, wil.statistic))
    file_wilcoxon.close()

if PLOT_LINES:
    fig = plt.figure(figsize=(FIG_WIDTH, FIG_HEIGHT))
#    fig.suptitle('Request Count Evolution', fontsize=12, fontweight='bold')

    ax = fig.add_subplot(111)
    ax.set_xlabel('duration in seconds')
    ax.set_ylabel('successfull requests per second')

    if PLOT_SINGLE:
        for itk, benchmarks in  sorted(benchmarks_per_type.items(), key=lambda tup: tup[0]):
            for benchmark in benchmarks:
                print_sample(benchmark.filename, benchmark.rps)
                if SINGLE_INDICATOR:
                    print_sample_indicator(benchmark.rps, benchmark.mean,benchmark.drop_time,benchmark.sample_length)

    if PLOT_AVERAGE:
        for itk, average_instance in sorted(averages_per_type.items(), key=lambda tup: tup[0]):
#            print_averages(itk, average_instance.averages)
            print_line_with_error_indicators(itk, average_instance.averages)
            metrics = average_instance.metrics
            if AVERGAE_INDICATOR:
                print_sample_indicator(average_instance.averages, metrics.mean,metrics.drop_time,metrics.sample_length)

    #Handles sorting for legend
    #handles, labels = ax.get_legend_handles_labels()
    #hl = sorted(zip(handles, labels),key=operator.itemgetter(1))
    #h, l = zip(*hl)
    #lgd = ax.legend(h, l, loc='upper center', bbox_to_anchor=(0.5,-0.1), ncol=2, markerscale=2, title="Files")
    handles, labels = ax.get_legend_handles_labels()
    lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1), ncol=2, markerscale=2)#, title="Files")
    plt.grid()
    plt.savefig('{}_lines.eps'.format(FIG_NAME), bbox_extra_artists=(lgd,), bbox_inches='tight')

if PLOT_BOXPLOT:
    fig = plt.figure(figsize=(FIG_WIDTH, FIG_HEIGHT))
#    fig.suptitle('Request Rate Boxplot', fontsize=12, fontweight='bold')
    ax = fig.add_subplot(111)
    ax.set_xlabel('configuration')
    ax.set_ylabel('successfull requests per second')
    boxplot_data = []
    boxplot_labels = []

    for key in KEYS:
        if key in averages_per_type.keys():
            boxplot_data.append(averages_per_type[key].list_with_medians)
            label = get_label_for_key(key)+"\n{}".format(len(averages_per_type[key].list_with_medians))
            boxplot_labels.append(label)

    plt.boxplot(boxplot_data, labels=boxplot_labels)
    plt.savefig('{}_boxplot.eps'.format(FIG_NAME),bbox_inches='tight')

if PLOT_LINES or PLOT_BOXPLOT:
    plt.show


from django.urls import path
from .views import blog, blogDetail, TagView, CategoryDetailView , CreateBlog ,Categorylist, UpdateBlog , DeleteBlog, Privacy

urlpatterns = [
    path('', blog, name="blog"),
    path('detail/<slug:slug_name>', blogDetail, name="detail"),
    path('tags/<slug:slug_tag>',TagView, name = 'tags'),
    path('category/<slug:slug>', CategoryDetailView.as_view(), name='category_detail'),
    path('CreateBlog/',CreateBlog.as_view(),name='create'),
    path('Category/',Categorylist.as_view(), name = 'Category'),
    path('Update/<slug:slug>',UpdateBlog.as_view(),name = 'update'),
    path('delete/<slug:slug>',DeleteBlog.as_view(),name = 'delete'),
    path('Xavfsizlik',Privacy,name = 'privacy'),
]


from django.urls import reverse
from django.utils.html import format_html
from wagtail.contrib.modeladmin.helpers import PageAdminURLHelper, PageButtonHelper
from wagtail.contrib.modeladmin.mixins import ThumbnailMixin
from wagtail.contrib.modeladmin.options import (
    ModelAdmin,
    ModelAdminGroup,
    modeladmin_register,
)

from .models import ArchiveIssue, MagazineDepartment, MagazineIssue

class MagazineIssueAdminURLHelper(PageAdminURLHelper):
    def get_action_url(
        self,
        action,
        *args,
        **kwargs,
    ):  # pragma: no cover
        if action == "add-child":
            url_name = "wagtailadmin_pages:add_subpage"
            target_url = reverse(url_name, args=args, kwargs=kwargs)

            return target_url

        # for every other case - just call the parent method
        return super().get_action_url(action, *args, **kwargs)

class MagazineIssueButtonHelperClass(PageButtonHelper):
    add_child_button_classnames = ["add-child"]

    def add_child_button(
        self,
        pk,
        classnames_add=None,
        classnames_exclude=None,
    ):  # pragma: no cover
        if classnames_add is None:
            classnames_add = []
        if classnames_exclude is None:
            classnames_exclude = []

        classnames = self.add_child_button_classnames + classnames_add
        final_classnames = self.finalise_classname(classnames, classnames_exclude)

        return {
            "url": self.url_helper.get_action_url("add-child", pk),
            "label": "Add Article",
            "classname": final_classnames,
            "title": f"Add article under this {self.verbose_name}",
        }

    def get_buttons_for_obj(
        self,
        obj,
        exclude=None,
        classnames_add=None,
        classnames_exclude=None,
    ):  # pragma: no cover
        if exclude is None:
            exclude = ([],)
        if classnames_add is None:
            classnames_add = ([],)
        if classnames_exclude is None:
            classnames_exclude = []

        # call the parent class method to get the default set of buttons
        buttons = super().get_buttons_for_obj(
            obj,
            exclude,
            classnames_add,
            classnames_exclude,
        )

        # set up some variables to do user checks and also get the primary key (id)
        permission_helper = self.permission_helper
        user = self.request.user
        pk = getattr(obj, self.opts.pk.attname)

        # many existing permission helpers are already available
        # - see wagtail/contrib/modeladmin/helpers/permission.py
        if "add-child" not in exclude and permission_helper.user_can_create(user):
            add_child_button = self.add_child_button(
                pk,
                classnames_add,
                classnames_exclude,
            )
            buttons.append(add_child_button)

        return buttons

class MagazineIssueModelAdmin(ThumbnailMixin, ModelAdmin):
    model = MagazineIssue
    menu_icon = "doc-full-inverse"
    menu_label = "Issues"
    list_per_page = 10
    ordering = [
        "-publication_date",
    ]
    list_display = (
        "admin_thumb",
        "title",
        "publication_date",
        "live",
        "view_articles",
        "add_article",
    )
    list_display_add_buttons = "title"
    thumb_image_field_name = "cover_image"
    thumb_image_filter_spec = "height-333"
    thumb_col_header_text = "Cover"
    thumb_default = "https://lorempixel.com/100/100"
    list_filter = ("publication_date",)
    empty_value_display = "-"
    search_fields = ("title",)

    button_helper_class = (
        MagazineIssueButtonHelperClass  # added to enable custom button generation
    )
    url_helper_class = (
        MagazineIssueAdminURLHelper  # added to enable custom url generation
    )

    def add_article(
        self,
        obj,
    ):  # pragma: no cover
        url_name = "wagtailadmin_pages:add_subpage"
        url = reverse(url_name, args=[obj.id])

        return format_html(
            f'Add Article',  # noqa: E501
        )

    def view_articles(
        self,
        obj,
    ):  # pragma: no cover
        url_name = "wagtailadmin_explore"
        url = reverse(url_name, args=[obj.id])

        return format_html(
            f'View Articles',  # noqa: E501
        )

class ArchiveIssueModelAdmin(ModelAdmin):
    model = ArchiveIssue
    menu_icon = "doc-full"
    menu_label = "Archive Issues"
    list_per_page = 10
    ordering = [
        "publication_date",
    ]
    list_display = (
        "title",
        "publication_date",
        "internet_archive_identifier",
    )
    empty_value_display = "-"
    search_fields = (
        "title",
        "internet_archive_identifier",
    )

class MagazineDepartmentModelAdmin(ModelAdmin):
    model = MagazineDepartment
    menu_icon = "tag"
    menu_label = "Departments"
    menu_order = 200
    add_to_settings_menu = False
    exclude_from_explorer = False
    list_per_page = 10
    list_display = ("title",)
    search_fields = ("title",)

class MagazineGroup(ModelAdminGroup):
    menu_label = "Magazine"
    menu_icon = "tablet-alt"
    menu_order = 100
    items = (
        MagazineIssueModelAdmin,
        ArchiveIssueModelAdmin,
        MagazineDepartmentModelAdmin,
    )

modeladmin_register(MagazineGroup)


# Append Dictionary Keys and Values ( In order ) in dictionary

# Input : test_dict = {“Gfg” : 1, “is” : 2, “Best” : 3}
# Output : [‘Gfg’, ‘is’, ‘Best’, 1, 2, 3]
# Explanation : All the keys before all the values in list.

test_dict = {"Gfg" : 1, "is" :  3, "Best" : 2}

print("======= 1) Naive Method ======")
lst = []
for key in test_dict.keys():
    lst.append(key)

for value in test_dict.values():
    lst.append(value)

print(lst)

print('\n====== 2) concanating two list =====')
concat_list = list(test_dict.keys()) + list(test_dict.values())
print(concat_list)

print('\n===== 3) chain + keys + values ======')
from itertools import chain
chain_list = list(chain(test_dict.keys(), test_dict.values()))
print(chain_list)


# Функция file_date создает новый файл в текущем рабочем каталоге, проверяет дату изменения файла и возвращает только дату временной метки в формате гггг-мм-дд. Заполните пробелы, чтобы создать файл с именем «newfile.txt», и проверьте дату его изменения.
import os
import datetime

def file_date(filename):
    # Create the file in the current directory
    file = open(filename, 'w')
    timestamp = os.path.getmtime(filename)
    # Convert the timestamp into a readable format, then into a string
    time = datetime.datetime.fromtimestamp(timestamp)
    # Return just the date portion
    # Hint: how many characters are in “yyyy-mm-dd”?
    return ("{}".format(time.strftime("%Y-%m-%d")))

print(file_date("newfile.txt"))
# Should be today's date in the format of yyyy-mm-dd


from fastapi import FastAPI, Depends

from app.routers import security, users, images, services

# < Development:
from fastapi.middleware.cors import CORSMiddleware
# >

from app.data.database import database
from app.data.io_files import create_folders
from app.security.methods import (
    create_admin, create_sample_user, get_current_active_user
)

app = FastAPI()

# < Development:
origins = [
    "http://localhost",
    "http://localhost:3000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
# >

app.include_router(security.router)
app.include_router(
    users.router,
    prefix='/users',
    dependencies=[Depends(get_current_active_user)]
)
app.include_router(
    images.router,
    prefix='/images',
    dependencies=[Depends(get_current_active_user)]
)
app.include_router(
    services.router,
    prefix='/services',
    dependencies=[Depends(get_current_active_user)]
)

@app.on_event("startup")
async def startup():
    await database.connect()
    await create_admin()
    await create_sample_user()
    await create_folders()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()

@app.get("/ping")
async def ping():
    return {"ping": True}


"""
Pagination helpers.
"""
import itertools
import math

def paginate_iterable(iterable, page, per_page):
    """
    Pagination for custom iterables.
    returns an iterator.
    """
    start = (page -1) * per_page
    end = start + per_page
    return itertools.islice(iterable, start, end)

def mongo_paginate_to_dict(pages, items_key, convert_items=True, **nestedkwargs):
    """
    Converts a page from mongoengine paginate to a dict for marshalling.
    optionally converts items to dict.
    pages: mongoengine page.
    items_key: key to insert items in dict.
    """
    dic = {
       'page': pages.page,
       'pages': pages.pages
    }
    if convert_items:
        dic[items_key] = [item.to_dict(**nestedkwargs) for item in pages.items]
    else:
        dic[items_key] = pages.items
    return dic

def custom_paginate_to_dict(iterable, items_key, page, total ,per_page, convert_items, **nestedkwargs):
    """
    Converts a page from paginate_iterable to a dict for marshalling.
    optionally converts items to dict.
    """
    pages = int(math.ceil(total / float(per_page)))
    if convert_items:
        items = [item.to_dict(**nestedkwargs) for item in iterable]
    else:
        items = list(iterable)
    dic = {
        "page": page,
        "pages": pages,
    }
    dic[items_key] = items
    return dic


import json

filename = "File-System/json/favourite_num.json"

# number = input("What's your favourite number? ")

# with open(filename, "w") as f_object:
#     json.dump(number, f_object)
#     print("Thanks! I'll remember that.")

with open(filename, "r") as f:
    content = json.load(f)
print("I know your favourite number is " + content)


# Released under the MIT License. See LICENSE for details.
#
"""Implements a flag used for marking bases, capture-the-flag games, etc."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from bascenev1lib.gameutils import SharedObjects
import bascenev1 as bs

if TYPE_CHECKING:
    from typing import Any, Sequence

class FlagFactory:
    """Wraps up media and other resources used by `Flag`s.

    Category: **Gameplay Classes**

    A single instance of this is shared between all flags
    and can be retrieved via FlagFactory.get().
    """

    flagmaterial: bs.Material
    """The bs.Material applied to all `Flag`s."""

    impact_sound: bs.Sound
    """The bs.Sound used when a `Flag` hits the ground."""

    skid_sound: bs.Sound
    """The bs.Sound used when a `Flag` skids along the ground."""

    no_hit_material: bs.Material
    """A bs.Material that prevents contact with most objects;
       applied to 'non-touchable' flags."""

    flag_texture: bs.Texture
    """The bs.Texture for flags."""

    _STORENAME = bs.storagename()

    def __init__(self) -> None:
        """Instantiate a `FlagFactory`.

        You shouldn't need to do this; call FlagFactory.get() to
        get a shared instance.
        """
        shared = SharedObjects.get()
        self.flagmaterial = bs.Material()
        self.flagmaterial.add_actions(
            conditions=(
                ('we_are_younger_than', 100),
                'and',
                ('they_have_material', shared.object_material),
            ),
            actions=('modify_node_collision', 'collide', False),
        )

        self.flagmaterial.add_actions(
            conditions=(
                'they_have_material',
                shared.footing_material,
            ),
            actions=(
                ('message', 'our_node', 'at_connect', 'footing', 1),
                ('message', 'our_node', 'at_disconnect', 'footing', -1),
            ),
        )

        self.impact_sound = bs.getsound('metalHit')
        self.skid_sound = bs.getsound('metalSkid')
        self.flagmaterial.add_actions(
            conditions=(
                'they_have_material',
                shared.footing_material,
            ),
            actions=(
                ('impact_sound', self.impact_sound, 2, 5),
                ('skid_sound', self.skid_sound, 2, 5),
            ),
        )

        self.no_hit_material = bs.Material()
        self.no_hit_material.add_actions(
            conditions=(
                ('they_have_material', shared.pickup_material),
                'or',
                ('they_have_material', shared.attack_material),
            ),
            actions=('modify_part_collision', 'collide', False),
        )

        # We also don't want anything moving it.
        self.no_hit_material.add_actions(
            conditions=(
                ('they_have_material', shared.object_material),
                'or',
                ('they_dont_have_material', shared.footing_material),
            ),
            actions=(
                ('modify_part_collision', 'collide', False),
                ('modify_part_collision', 'physical', False),
            ),
        )

        self.flag_texture = bs.gettexture('flagColor')

    @classmethod
    def get(cls) -> FlagFactory:
        """Get/create a shared `FlagFactory` instance."""
        activity = bs.getactivity()
        factory = activity.customdata.get(cls._STORENAME)
        if factory is None:
            factory = FlagFactory()
            activity.customdata[cls._STORENAME] = factory
        assert isinstance(factory, FlagFactory)
        return factory

@dataclass
class FlagPickedUpMessage:
    """A message saying a `Flag` has been picked up.

    Category: **Message Classes**
    """

    flag: Flag
    """The `Flag` that has been picked up."""

    node: bs.Node
    """The bs.Node doing the picking up."""

@dataclass
class FlagDiedMessage:
    """A message saying a `Flag` has died.

    Category: **Message Classes**
    """

    flag: Flag
    """The `Flag` that died."""

@dataclass
class FlagDroppedMessage:
    """A message saying a `Flag` has been dropped.

    Category: **Message Classes**
    """

    flag: Flag
    """The `Flag` that was dropped."""

    node: bs.Node
    """The bs.Node that was holding it."""

class Flag(bs.Actor):
    """A flag; used in games such as capture-the-flag or king-of-the-hill.

    Category: **Gameplay Classes**

    Can be stationary or carry-able by players.
    """

    def __init__(
        self,
        position: Sequence[float] = (0.0, 1.0, 0.0),
        color: Sequence[float] = (1.0, 1.0, 1.0),
        materials: Sequence[bs.Material] | None = None,
        touchable: bool = True,
        dropped_timeout: int | None = None,
    ):
        """Instantiate a flag.

        If 'touchable' is False, the flag will only touch terrain;
        useful for things like king-of-the-hill where players should
        not be moving the flag around.

        'materials can be a list of extra `bs.Material`s to apply to the flag.

        If 'dropped_timeout' is provided (in seconds), the flag will die
        after remaining untouched for that long once it has been moved
        from its initial position.
        """

        super().__init__()

        self._initial_position: Sequence[float] | None = None
        self._has_moved = False
        shared = SharedObjects.get()
        factory = FlagFactory.get()

        if materials is None:
            materials = []
        elif not isinstance(materials, list):
            # In case they passed a tuple or whatnot.
            materials = list(materials)
        if not touchable:
            materials = [factory.no_hit_material] + materials

        finalmaterials = [
            shared.object_material,
            factory.flagmaterial,
        ] + materials
        self.node = bs.newnode(
            'flag',
            attrs={
                'position': (position[0], position[1] + 0.75, position[2]),
                'color_texture': factory.flag_texture,
                'color': color,
                'materials': finalmaterials,
            },
            delegate=self,
        )

        if dropped_timeout is not None:
            dropped_timeout = int(dropped_timeout)
        self._dropped_timeout = dropped_timeout
        self._counter: bs.Node | None
        if self._dropped_timeout is not None:
            self._count = self._dropped_timeout
            self._tick_timer = bs.Timer(
                1.0, call=bs.WeakCall(self._tick), repeat=True
            )
            self._counter = bs.newnode(
                'text',
                owner=self.node,
                attrs={
                    'in_world': True,
                    'color': (1, 1, 1, 0.7),
                    'scale': 0.015,
                    'shadow': 0.5,
                    'flatness': 1.0,
                    'h_align': 'center',
                },
            )
        else:
            self._counter = None

        self._held_count = 0
        self._score_text: bs.Node | None = None
        self._score_text_hide_timer: bs.Timer | None = None

    def _tick(self) -> None:
        if self.node:
            # Grab our initial position after one tick (in case we fall).
            if self._initial_position is None:
                self._initial_position = self.node.position

                # Keep track of when we first move; we don't count down
                # until then.
            if not self._has_moved:
                nodepos = self.node.position
                if (
                    max(
                        abs(nodepos[i] - self._initial_position[i])
                        for i in list(range(3))
                    )
                    > 1.0
                ):
                    self._has_moved = True

            if self._held_count > 0 or not self._has_moved:
                assert self._dropped_timeout is not None
                assert self._counter
                self._count = self._dropped_timeout
                self._counter.text = ''
            else:
                self._count -= 1
                if self._count <= 10:
                    nodepos = self.node.position
                    assert self._counter
                    self._counter.position = (
                        nodepos[0],
                        nodepos[1] + 1.3,
                        nodepos[2],
                    )
                    self._counter.text = str(self._count)
                    if self._count < 1:
                        self.handlemessage(bs.DieMessage())
                else:
                    assert self._counter
                    self._counter.text = ''

    def _hide_score_text(self) -> None:
        assert self._score_text is not None
        assert isinstance(self._score_text.scale, float)
        bs.animate(
            self._score_text, 'scale', {0: self._score_text.scale, 0.2: 0}
        )

    def set_score_text(self, text: str) -> None:
        """Show a message over the flag; handy for scores."""
        if not self.node:
            return
        if not self._score_text:
            start_scale = 0.0
            math = bs.newnode(
                'math',
                owner=self.node,
                attrs={'input1': (0, 1.4, 0), 'operation': 'add'},
            )
            self.node.connectattr('position', math, 'input2')
            self._score_text = bs.newnode(
                'text',
                owner=self.node,
                attrs={
                    'text': text,
                    'in_world': True,
                    'scale': 0.02,
                    'shadow': 0.5,
                    'flatness': 1.0,
                    'h_align': 'center',
                },
            )
            math.connectattr('output', self._score_text, 'position')
        else:
            assert isinstance(self._score_text.scale, float)
            start_scale = self._score_text.scale
            self._score_text.text = text
        self._score_text.color = bs.safecolor(self.node.color)
        bs.animate(self._score_text, 'scale', {0: start_scale, 0.2: 0.02})
        self._score_text_hide_timer = bs.Timer(
            1.0, bs.WeakCall(self._hide_score_text)
        )

    def handlemessage(self, msg: Any) -> Any:
        assert not self.expired
        if isinstance(msg, bs.DieMessage):
            if self.node:
                self.node.delete()
                if not msg.immediate:
                    self.activity.handlemessage(FlagDiedMessage(self))
        elif isinstance(msg, bs.HitMessage):
            assert self.node
            assert msg.force_direction is not None
            self.node.handlemessage(
                'impulse',
                msg.pos[0],
                msg.pos[1],
                msg.pos[2],
                msg.velocity[0],
                msg.velocity[1],
                msg.velocity[2],
                msg.magnitude,
                msg.velocity_magnitude,
                msg.radius,
                0,
                msg.force_direction[0],
                msg.force_direction[1],
                msg.force_direction[2],
            )
        elif isinstance(msg, bs.PickedUpMessage):
            self._held_count += 1
            if self._held_count == 1 and self._counter is not None:
                self._counter.text = ''
            self.activity.handlemessage(FlagPickedUpMessage(self, msg.node))
        elif isinstance(msg, bs.DroppedMessage):
            self._held_count -= 1
            if self._held_count < 0:
                print('Flag held count < 0.')
                self._held_count = 0
            self.activity.handlemessage(FlagDroppedMessage(self, msg.node))
        else:
            super().handlemessage(msg)

    @staticmethod
    def project_stand(pos: Sequence[float]) -> None:
        """Project a flag-stand onto the ground at the given position.

        Useful for games such as capture-the-flag to show where a
        movable flag originated from.
        """
        assert len(pos) == 3
        bs.emitfx(position=pos, emit_type='flag_stand')


import boto3
from datetime import datetime
import time

TABLE_NAME = 'VisitorData'
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(TABLE_NAME)

def get_unix_time():
    """Returns current unix timestamp"""
    d = datetime.utcnow()
    return str(time.mktime(d.timetuple()))

def add_visit(user_agent):
    visit_id = get_unix_time()
    item = {
        'id': visit_id,
        'user_agent': user_agent
    }
    table.put_item(Item=item)

def get_total_count():
    return table.scan()['Count']

def lambda_handler(event, context):
    add_visit(event['requestContext']['identity'].get('userAgent'))
    total_count = get_total_count()
    return {
        'statusCode': 200,
        'headers': {
            'content-type': 'text/html'
        },
        'body': f'
Visited {total_count} times
'
    }


from splinter import Browser
from bs4 import BeautifulSoup as bs
import pandas as pd
from selenium import webdriver

def init_browser():
    executable_path = {'executable_path': 'chromedriver.exe'}
    options = webdriver.ChromeOptions()
    options.add_argument("--start-maximized")
    options.add_argument("--disable-notifications")
    return Browser('chrome', **executable_path, headless=False, options = options)

def scrape():
    browser = init_browser()

    # Visit webpage and get NASA Mars News
    url = 'https://mars.nasa.gov/news/?page=0&per_page=40&order=publish_date+desc%2Ccreated_at+desc&search=&category=19%2C165%2C184%2C204&blank_scope=Latest'
    browser.visit(url)

    #Scrape the NASA Mars News Site and collect the latest News Title and Paragraph Text. Assign the text to variables that you can reference later.
    news_title = browser.find_by_css('div[class="content_title"] a', wait_time=2).text
    news_p = browser.find_by_css('div[class="article_teaser_body"]').text

    # Visit JPL Space Images
    url = 'https://www.jpl.nasa.gov/spaceimages/?search=&category=Mars'
    browser.visit(url)

    # Click image to maximize
    browser.find_by_id('full_image').click()

    # Get featured image url
    featured_image_url = browser.find_by_id('fancybox-lock').first.find_by_tag('img')['src']

    #Visit Twitter for Mars Weather
    url = 'https://twitter.com/marswxreport?lang=en'
    browser.visit(url)

    # Get Mars Weather Data
    mars_weather = browser.find_by_tag('article').find_by_css('span[class="css-901oao css-16my406 r-1qd0xha r-ad9z0x r-bcqeeo r-qvutc0"]')[4].text

    # Visit Space Facts and read table facts to html table
    url = "https://space-facts.com/mars/"
    tables = pd.read_html(url)
    df = tables[0]
    df.columns = ['Description', 'Value']
    df.set_index('Description', inplace=True)
    html_table_df = df.to_html()
    html_table = html_table_df.replace('\n', '')

    # # Vist webpage to get images for Mars Hemispheres
    url = 'https://astrogeology.usgs.gov/search/results?q=hemisphere+enhanced&k1=target&v1=Mars'
    browser.visit(url)

    # # Find Hemisphere title containing the hemisphere name.
    title1 = browser.find_by_css('div[class="item"]')[0].find_by_tag('h3').text
    title2 = browser.find_by_css('div[class="item"]')[1].find_by_tag('h3').text
    title3 = browser.find_by_css('div[class="item"]')[2].find_by_tag('h3').text
    title4 = browser.find_by_css('div[class="item"]')[3].find_by_tag('h3').text

    # # Browse through webpage to get image urls
    link1 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/section/div/div[2]/div[1]/div/a/h3').click()
    open1 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/a').click()
    img1 = browser.find_by_css('div[class="downloads"]').find_by_tag('a')['href']

    back1 = browser.back()

    link2 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/section/div/div[2]/div[2]/div/a/h3').click()
    open2 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/a').click()
    img2 = browser.find_by_css('div[class="downloads"]').find_by_tag('a')['href']

    back2 = browser.back()

    link3 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/section/div/div[2]/div[3]/div/a/h3').click()
    open3 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/a').click()
    img3 = browser.find_by_css('div[class="downloads"]').find_by_tag('a')['href']

    back3 = browser.back()

    link4 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/section/div/div[2]/div[4]/div/a/h3').click()
    open4 = browser.find_by_xpath('/html/body/div[1]/div[1]/div[2]/a').click()
    img4 = browser.find_by_css('div[class="downloads"]').find_by_tag('a')['href']

    # Set titles and images to a list of dictionaries

    hemisphere_image_urls = [
    {"title": title1, "img_url": img1},
    {"title": title2, "img_url": img2},
    {"title": title3, "img_url": img3},
    {"title": title4, "img_url": img4}
     ]

    mars_data = {
        "news_title": news_title,
        "news_text": news_p,
        "featured_img": featured_image_url,
        "mars_weather": mars_weather,
        "mars_facts" : html_table,
        "hemisphere_images": hemisphere_image_urls
    }

    browser.quit()

    return mars_data