import pandas as pd
import os
from functools import lru_cache
from typing import List, Tuple
import numpy as np

import envelope

def get_filename(hc: str, region: int, mode: str, nvi: int, j: int, k: int, tsi: int, h_exc: bool) -> str:
    """負荷ファイル名の取得
    :param hc: heating or cooling.  'H' is heating. 'C' is cooling.
    :param region: region 1-8
    :param mode: 'い','ろ','は','全館連続','居室連続','居室間歇'
    :param nvi: 通風レベル 1-3 (int)
    :param j: 断熱水準 1-4 (int)
    :param k: 日射遮蔽レベル 1-3 (int)
    :param tsi: 蓄熱の利用の程度の区分 1-2 (int)
    :param h_exc: 熱交換換気の有無 (boolean)
    :return: ファイル名 (str)
    """

    # 3.運転モード
    if mode == 'い' or mode == '全館連続' or mode == '全館連続':
        mode_s = 11
    elif mode == 'は' or mode == '居室間歇':
        mode_s = 12
    elif mode == 'ろ' or mode == '居室連続':
        mode_s = 13
    else:
        raise ValueError(mode)

    # 4.断熱水準・熱交換換気の有無
    if j == 1:
        j_s = 2
    elif j == 2:
        j_s = 3 if not h_exc else 4
    elif j == 3:
        j_s = 5 if not h_exc else 7
    elif j == 4:
        j_s = 9 if not h_exc else 11
    else:
        raise ValueError(j)

    # 5.日射遮蔽
    if k == 1:
        k_s = 3
    elif k == 2:
        k_s = 2
    elif k == 3:
        k_s = 1
    else:
        raise ValueError(k)

    # 6.通風設定
    if nvi == 1:
        nvi_s = 1
    elif nvi == 2:
        nvi_s = 3
    elif nvi == 3:
        nvi_s = 5
    else:
        raise ValueError(nvi)

    filename = "I{hc}{region}x_{mode:02d}_{j:02d}_{k}{nvi}2_{tsi}SS.csv" \
        .format(hc=hc, region=region, mode=mode_s, nvi=nvi_s, j=j_s, k=k_s, tsi=tsi)

    return filename

@lru_cache()
def read_csv(hc, filename, debug=False):
    """read load file
    cache the file
    :param hc: 'H' or 'C'
        'H' is heating.
        'C' is cooling.
    :param filename: load file name
    :param debug: In case True, the file name is printed.
    :return:pandas file
    """
    path = os.path.join('data', filename)
    if debug:
        print(path)
    if hc == 'H':
        df = pd.read_csv(path, skiprows=4, nrows=24 * 365, names=(
            'date', 'hour', 'holiday', 'temp', 'humid', '1_HS', '1_HL', '2_HS', '2_HL', '3_HS', '3_HL', '4_HS', '4_HL',
            '5_HS', '5_HL', '6_HS', '6_HL', '7_HS', '7_HL', '8_HS', '8_HL', '9_HS', '9_HL', '10_HS', '10_HL', '11_HS',
            '11_HL', '12_HS', '12_HL'), encoding='shift_jis')
    elif hc == 'C':
        df = pd.read_csv(path, skiprows=4, nrows=24 * 365, names=(
            'date', 'hour', 'holiday', 'temp', 'humid', '1_CS', '1_CL', '2_CS', '2_CL', '3_CS', '3_CL', '4_CS', '4_CL',
            '5_CS', '5_CL', '6_CS', '6_CL', '7_CS', '7_CL', '8_CS', '8_CL', '9_CS', '9_CL', '10_CS', '10_CL', '11_CS',
            '11_CL', '12_CS', '12_CL'), encoding='shift_jis')
    else:
        raise NotImplementedError()
    return df

def get_L_dash_H_R_TSl_Qj_muH_j_k_d_t_i(region: int, mode: str, l: int, j: int, k: int, i: int, debug: bool) \
        -> List[float]:
    """按分しない暖房負荷を取得
    Args:
        region: 地域の区分(1-8)
        mode: 運転モード
            全館連続
            居室間歇
            居室連続
        l: 蓄熱の利用の程度の区分 (1-2)
            1: 蓄熱なし
            2: 蓄熱あり
        j: 断熱水準
            1: S55
            2: H4
            3: H11
            4: H11超
        k: 日射遮蔽レベル
            1: 大
            2: 中
            3: 小
        i: room number
        debug: debug mode
    Returns:
        暖房負荷(MJ/h) 8760時間分
    """

    filename = get_filename('H', region, mode, 1, j, k, l, False)

    df = read_csv('H', filename, debug)

    return df['%d_HS' % i].values / 1000

def get_L_dash_CS_R_NVl_Qj_muH_j_k_d_t_i(region: int, mode: str, l: int, j: int, k: int, i: int, debug: bool) \
        -> List[float]:
    """按分しない冷房顕熱負荷を取得
    Args:
        region: 地域の区分(1-8)
        mode: 運転モード
            全館連続
            居室間歇
            居室連続
        l: 通風レベル
            1: 通風なし
            2: 通風あり（換気回数5回/h）
            3: 通風あり（換気回数20回/h）
        j: 断熱水準
            1: S55
            2: H4
            3: H11
            4: H11超
        k: 日射遮蔽レベル
            1: 大
            2: 中
            3: 小
        i : room number
        debug : debug mode
    Returns:
        冷房顕熱負荷(MJ/h) 8760時間分
    """
    filename = get_filename('C', region, mode, l, j, k, 1, False)
    df = read_csv('C', filename, debug)
    return df['%d_CS' % i].values * -1.0 / 1000

def get_L_dash_CL_R_NVl_Qj_muH_j_k_d_t_i(region: int, mode: int, l: int, j: int, k: int, i: int, debug: bool) \
        -> List[float]:
    """按分しない冷房潜熱負荷を取得
    Args:
        region: 地域の区分(1-8)
        mode: 運転モード
            全館連続
            居室間歇
            居室連続
        l: 通風レベル
            1: 通風なし
            2: 通風あり（換気回数5回/h）
            3: 通風あり（換気回数20回/h）
        j: 断熱水準
            1: S55
            2: H4
            3: H11
            4: H11超
        k: 日射遮蔽レベル
            1: 大
            2: 中
            3: 小
        i: room number
        debug: debug mode
    Returns:
        冷房潜熱負荷(MJ/h) 8760時間分
    """

    filename = get_filename('C', region, mode, l, j, k, 1, False)

    df = read_csv('C', filename, debug)

    return df['%d_CL' % i].values * -1.0 / 1000

def get_envelope_number(insulation: str, solar_gain: str) -> (float, float):
    """
    断熱水準・日射遮蔽レベルから該当する番号j,kを取得する
        Args:
            insulation: 断熱水準
                s55: S55年基準相当レベル
                h4: H4年基準相当レベル
                h11: H11年基準相当レベル
                h11more: H11年基準超相当レベル
            solar_gain: 日射遮蔽レベル
                small: 小
                middle: 中
                large: 大
        Returns:
            断熱水準を表す番号, 日射遮蔽レベルを表す暗号
    """
    j = {
        's55': 1,
        'h4': 2,
        'h11': 3,
        'h11more': 4,
    }[insulation]
    k = {
        'small': 3,
        'middle': 2,
        'large': 1,
    }[solar_gain]
    return j, k

def get_size_factor(floor_area: envelope.FloorArea) -> np.ndarray:
    """
    get size factor
    Args:
        floor_area: floor area
    Returns:
        size factor (float, 12 rooms * 1 value)
    """

    # floor areas of the referenced house, m2 (12 rooms)
    a_r = envelope.get_referenced_floor_area()

    # floor area, m2 (12 rooms)
    a = envelope.get_hc_floor_areas(floor_area=floor_area)

    # size factor, (12 rooms)
    r = a / a_r

    # change dimension to 1 * 12
    return r.reshape(1, 12).T

def get_heating_load(region: int, envelope_spec: envelope.Spec, floor_area: envelope.FloorArea,
                     debug: bool = False) -> np.ndarray:
    """ get hourly heating load
    Args:
        region: region(1-8)
        envelope_spec: envelop spec
            insulation: insulation level. specify the level as string following below:
                's55': Showa 55 era level
                'h4': Heisei 4 era level
                'h11': Heisei 11 era level
                'h11more' : more than Heisei 11 era level
            solar_gain: solar gain level. specify the level as string following below.
                'small': small level
                'middle': middle level
                'large': large level
        floor_area: floor_area
        debug: in case of True, the file name of heat load is printed.
    Returns:
        heating load, MJ/h (12 rooms * 8760 times)
    """

    if region == 8:
        # Heating load is not defined in region 8.
        # In case of region 8, the array(5 rooms * 8760 times) of value 0.0 as heating load is returned.
        referenced = np.full((12, 8760), 0.0)
    else:
        j, k = get_envelope_number(envelope_spec.insulation, envelope_spec.solar_gain)
        referenced = np.array([get_L_dash_H_R_TSl_Qj_muH_j_k_d_t_i(
            region=region, mode='全館連続', l=1, j=j, k=k, i=i, debug=debug) for i in np.arange(1, 13)])

    r = get_size_factor(floor_area)

    return referenced * r

def get_sensible_cooling_load(region: int, envelope_spec: envelope.Spec, floor_area: envelope.FloorArea,
                              debug: bool = False) -> np.ndarray:
    """ get hourly sensible cooling load
    Args:
        region: region(1-8)
        envelope_spec: envelop spec
            insulation: insulation level. specify the level as string following below:
                's55': Showa 55 era level
                'h4': Heisei 4 era level
                'h11': Heisei 11 era level
                'h11more' : more than Heisei 11 era level
            solar_gain: solar gain level. specify the level as string following below.
                'small': small level
                'middle': middle level
                'large': large level
        floor_area: floor_area
        debug: in case of True, the file name of heat load is printed.
    Returns:
        sensible cooling load, MJ/h (12 rooms * 8760 times)
    """
    j, k = get_envelope_number(envelope_spec.insulation, envelope_spec.solar_gain)
    referenced = np.array([get_L_dash_CS_R_NVl_Qj_muH_j_k_d_t_i(
        region=region, mode='全館連続', l=1, j=j, k=k, i=i, debug=debug) for i in np.arange(1, 13)])
    r = get_size_factor(floor_area)

    return referenced * r

def get_latent_cooling_load(region: int, envelope_spec: envelope.Spec, floor_area: envelope.FloorArea,
                            debug: bool = False) -> np.ndarray:
    """ get hourly latent cooling load
    Args:
        region: region(1-8)
        envelope_spec: envelop spec
            insulation: insulation level. specify the level as string following below:
                's55': Showa 55 era level
                'h4': Heisei 4 era level
                'h11': Heisei 11 era level
                'h11more' : more than Heisei 11 era level
            solar_gain: solar gain level. specify the level as string following below.
                'small': small level
                'middle': middle level
                'large': large level
        floor_area: floor_area
        debug: in case of True, the file name of heat load is printed.
    Returns:
        latent cooling load, MJ/h (12 rooms * 8760 times)
    """
    j, k = get_envelope_number(envelope_spec.insulation, envelope_spec.solar_gain)
    referenced = np.array([get_L_dash_CL_R_NVl_Qj_muH_j_k_d_t_i(
        region=region, mode='全館連続', l=1, j=j, k=k, i=i, debug=debug) for i in np.arange(1, 13)])
    r = get_size_factor(floor_area)

    return referenced * r


import argparse
import datetime
import imutils
import time
import cv2
from imutils.video import VideoStream
from pyzbar import pyzbar
from code import Code
from client import SocketClient

class Camera():
    video_stream = None
    frame = None

    @staticmethod
    def start_camera():
        Camera.video_stream = VideoStream(src=0).start()
        time.sleep(2.0)

    @staticmethod
    def scan_code():
        code = None
        Camera.grab_frame()
        # find the barcodes in the frame and decode each of the barcodes
        barcodes = pyzbar.decode(Camera.frame)
        # loop over the detected barcodes
        for barcode in barcodes:
            Camera.draw_bounding_box(barcode)
            # retrieve and decode content
            code = Code(barcode)
        # cv2.imshow("Barcode Scanner", Camera.frame)
        SocketClient.emit_image(Camera.frame)
        return code

    @staticmethod
    def grab_frame():
        # grab the frame from the threaded video stream and resize it to
        # have a maximum width of 400 pixels
        Camera.frame = imutils.resize(Camera.video_stream.read(), width=400)

    @staticmethod
    def draw_bounding_box(barcode):
        # draw a bounding box
        (x, y, w, h) = barcode.rect
        cv2.rectangle(Camera.frame, (x, y), (x + w, y + h), (0, 0, 255), 1)

    @staticmethod
    def stop_camera():
        cv2.destroyAllWindows()
        Camera.video_stream.stop()


"""SCRU64: Sortable, Clock-based, Realm-specifically Unique identifier"""

from __future__ import annotations

__all__ = [
    "new",
    "new_string",
    "new_sync",
    "new_string_sync",
    "Scru64Id",
    "Scru64Generator",
    "GlobalGenerator",
    "NodeSpec",
    "counter_mode",
]

import asyncio
import datetime
import os
import re
import threading
import time
import typing

from .counter_mode import CounterMode, RenewContext, DefaultCounterMode

# The maximum valid value (i.e., `zzzzzzzzzzzz`).
MAX_SCRU64_INT = 36**12 - 1

# The total size in bits of the `node_id` and `counter` fields.
NODE_CTR_SIZE = 24

# The maximum valid value of the `timestamp` field.
MAX_TIMESTAMP = MAX_SCRU64_INT >> NODE_CTR_SIZE

# The maximum valid value of the combined `node_ctr` field.
MAX_NODE_CTR = (1 << NODE_CTR_SIZE) - 1

# Digit characters used in the Base36 notation.
DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"

class Scru64Id:
    """Represents a SCRU64 ID."""

    __slots__ = "_value"

    def __init__(self, int_value: int) -> None:
        """
        Creates an object from a 64-bit integer.

        Raises:
            `ValueError` if the argument is negative or larger than `36^12 - 1`.
        """
        self._value = int_value
        if not (0 <= int_value <= MAX_SCRU64_INT):
            raise ValueError("out of valid integer range")

    def __int__(self) -> int:
        """Returns the integer representation."""
        return self._value

    @classmethod
    def from_str(cls, str_value: str) -> Scru64Id:
        """
        Creates an object from a 12-digit string representation.

        Raises:
            `ValueError` if the argument is not a valid string representation.
        """
        if re.fullmatch(r"[0-9A-Za-z]{12}", str_value, flags=re.ASCII) is None:
            raise ValueError("invalid string representation")
        return cls(int(str_value, 36))

    def __str__(self) -> str:
        """Returns the 12-digit canonical string representation."""
        buffer = ["0"] * 12
        n = self._value
        for i in range(12):
            (n, rem) = divmod(n, 36)
            buffer[11 - i] = DIGITS[rem]
        return "".join(buffer)

    @classmethod
    def from_parts(cls, timestamp: int, node_ctr: int) -> Scru64Id:
        """
        Creates a value from the `timestamp` and the combined `node_ctr` field value.

        Raises:
            `ValueError` if any argument is negative or larger than their respective
            maximum value (`36^12 / 2^24 - 1` and `2^24 - 1`, respectively).
        """
        if timestamp < 0 or timestamp > MAX_TIMESTAMP:
            raise ValueError("`timestamp` out of range")
        if node_ctr < 0 or node_ctr > MAX_NODE_CTR:
            raise ValueError("`node_ctr` out of range")
        return cls(timestamp << NODE_CTR_SIZE | node_ctr)

    @property
    def timestamp(self) -> int:
        """Returns the `timestamp` field value."""
        return self._value >> NODE_CTR_SIZE

    @property
    def node_ctr(self) -> int:
        """
        Returns the `node_id` and `counter` field values combined as a single 24-bit
        integer.
        """
        return self._value & MAX_NODE_CTR

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(0x{self._value:016X})"

    def __eq__(self, value: object) -> bool:
        if not isinstance(value, self.__class__):
            return NotImplemented
        return self._value == value._value

    def __hash__(self) -> int:
        return hash(self._value)

    def __lt__(self, value: object) -> bool:
        if not isinstance(value, self.__class__):
            return NotImplemented
        return self._value < value._value

    def __le__(self, value: object) -> bool:
        if not isinstance(value, self.__class__):
            return NotImplemented
        return self._value <= value._value

    def __gt__(self, value: object) -> bool:
        if not isinstance(value, self.__class__):
            return NotImplemented
        return self._value > value._value

    def __ge__(self, value: object) -> bool:
        if not isinstance(value, self.__class__):
            return NotImplemented
        return self._value >= value._value

class Scru64Generator:
    """
    Represents a SCRU64 ID generator.

    The generator comes with several different methods that generate a SCRU64 ID:

    | Flavor                 | Timestamp | Thread- | On big clock rewind |
    | ---------------------- | --------- | ------- | ------------------- |
    | generate               | Now       | Safe    | Returns `None`      |
    | generate_or_reset      | Now       | Safe    | Resets generator    |
    | generate_or_sleep      | Now       | Safe    | Sleeps (blocking)   |
    | generate_or_await      | Now       | Safe    | Sleeps (async)      |
    | generate_or_abort_core | Argument  | Unsafe  | Returns `None`      |
    | generate_or_reset_core | Argument  | Unsafe  | Resets generator    |

    All of these methods return a monotonically increasing ID by reusing the previous
    `timestamp` even if the one provided is smaller than the immediately preceding ID's,
    unless such a clock rollback is considered significant (by default, approx. 10
    seconds). A clock rollback may also be detected when a generator has generated too
    many IDs within a certain unit of time, because this implementation increments the
    previous `timestamp` when `counter` reaches the limit to continue instant monotonic
    generation. When a significant clock rollback is detected:

    1.  `generate` (or_abort) methods abort and return `None` immediately.
    2.  `or_reset` variants reset the generator and return a new ID based on the given
        `timestamp`, breaking the increasing order of IDs.
    3.  `or_sleep` and `or_await` methods sleep and wait for the next timestamp tick.

    The `core` functions offer low-level thread-unsafe primitives to customize the
    behavior.
    """

    def __init__(
        self,
        node_spec: typing.Union[str, NodeSpec],
        *,
        counter_mode: typing.Optional[CounterMode] = None,
    ) -> None:
        """
        Creates a new generator with the given node configuration and counter
        initialization mode.

        The `node_spec` may be passed as a string or as a `NodeSpec` instance. See
        `NodeSpec` for the node spec string format.

        Raises:
            `ValueError` if the `node_spec` is given in a string and could not be parsed
            as a well-formed node spec string.
        """
        if isinstance(node_spec, str):
            node_spec = NodeSpec.parse(node_spec)

        self._prev = node_spec._node_prev
        self._counter_size = NODE_CTR_SIZE - node_spec.node_id_size()

        if counter_mode is not None:
            self._counter_mode = counter_mode
        else:
            # reserve one overflow guard bit if `counter_size` is very small
            if self._counter_size <= 4:
                self._counter_mode = DefaultCounterMode(1)
            else:
                self._counter_mode = DefaultCounterMode(0)

        self._lock = threading.Lock()

    def node_id(self) -> int:
        """Returns the `node_id` of the generator."""
        return self._prev.node_ctr >> self._counter_size

    def node_id_size(self) -> int:
        """Returns the size in bits of the `node_id` adopted by the generator."""
        return NODE_CTR_SIZE - self._counter_size

    def node_spec(self) -> NodeSpec:
        """Returns the node configuration specifier describing the generator state."""
        return NodeSpec(self._prev, self.node_id_size())

    def _renew_node_ctr(self, timestamp: int) -> int:
        """
        Calculates the combined `node_ctr` field value for the next `timestamp` tick.
        """
        node_id = self.node_id()
        context = RenewContext(timestamp=timestamp, node_id=node_id)
        counter = self._counter_mode.renew(self._counter_size, context)
        if counter >= (1 << self._counter_size):
            raise AssertionError("illegal `CounterMode` implementation")

        return (node_id << self._counter_size) | counter

    def generate(self) -> typing.Optional[Scru64Id]:
        """
        Generates a new SCRU64 ID object from the current `timestamp`, or returns `None`
        upon significant timestamp rollback.

        See the `Scru64Generator` class documentation for the description.
        """
        with self._lock:
            timestamp = datetime.datetime.now().timestamp()
            return self.generate_or_abort_core(int(timestamp * 1_000), 10_000)

    def generate_or_reset(self) -> Scru64Id:
        """
        Generates a new SCRU64 ID object from the current `timestamp`, or resets the
        generator upon significant timestamp rollback.

        See the `Scru64Generator` class documentation for the description.

        Note that this mode of generation is not recommended because rewinding
        `timestamp` without changing `node_id` considerably increases the risk of
        duplicate results.
        """
        with self._lock:
            timestamp = datetime.datetime.now().timestamp()
            return self.generate_or_reset_core(int(timestamp * 1_000), 10_000)

    def generate_or_sleep(self) -> Scru64Id:
        """
        Returns a new SCRU64 ID object, or synchronously sleeps and waits for one if not
        immediately available.

        See the `Scru64Generator` class documentation for the description.
        """
        DELAY = 64.0 / 1000.0
        while True:
            value = self.generate()
            if value is not None:
                return value
            else:
                time.sleep(DELAY)

    async def generate_or_await(self) -> Scru64Id:
        """
        Returns a new SCRU64 ID object, or asynchronously sleeps and waits for one if
        not immediately available.

        See the `Scru64Generator` class documentation for the description.
        """
        DELAY = 64.0 / 1000.0
        while True:
            value = self.generate()
            if value is not None:
                return value
            else:
                await asyncio.sleep(DELAY)

    def generate_or_reset_core(
        self, unix_ts_ms: int, rollback_allowance: int
    ) -> Scru64Id:
        """
        Generates a new SCRU64 ID object from a Unix timestamp in milliseconds, or
        resets the generator upon significant timestamp rollback.

        See the `Scru64Generator` class documentation for the description.

        Note that this mode of generation is not recommended because rewinding
        `timestamp` without changing `node_id` considerably increases the risk of
        duplicate results.

        The `rollback_allowance` parameter specifies the amount of `unix_ts_ms` rollback
        that is considered significant. A suggested value is `10_000` (milliseconds).

        Unlike `generate_or_reset()`, this method is NOT thread-safe. The generator
        object should be protected from concurrent accesses using a mutex or other
        synchronization mechanism to avoid race conditions.
        """
        value = self.generate_or_abort_core(unix_ts_ms, rollback_allowance)
        if value is not None:
            return value
        else:
            # reset state and resume
            timestamp = unix_ts_ms >> 8
            self._prev = Scru64Id.from_parts(timestamp, self._renew_node_ctr(timestamp))
            return self._prev

    def generate_or_abort_core(
        self, unix_ts_ms: int, rollback_allowance: int
    ) -> typing.Optional[Scru64Id]:
        """
        Generates a new SCRU64 ID object from a Unix timestamp in milliseconds, or
        returns `None` upon significant timestamp rollback.

        See the `Scru64Generator` class documentation for the description.

        The `rollback_allowance` parameter specifies the amount of `unix_ts_ms` rollback
        that is considered significant. A suggested value is `10_000` (milliseconds).

        Unlike `generate()`, this method is NOT thread-safe. The generator object should
        be protected from concurrent accesses using a mutex or other synchronization
        mechanism to avoid race conditions.
        """
        timestamp = unix_ts_ms >> 8
        allowance = rollback_allowance >> 8
        if timestamp <= 0:
            raise ValueError("`timestamp` out of range")
        elif allowance < 0 or allowance >= (1 << 40):
            raise ValueError("`rollback_allowance` out of reasonable range")

        prev_timestamp = self._prev.timestamp
        if timestamp > prev_timestamp:
            self._prev = Scru64Id.from_parts(timestamp, self._renew_node_ctr(timestamp))
        elif timestamp + allowance >= prev_timestamp:
            # go on with previous timestamp if new one is not much smaller
            prev_node_ctr = self._prev.node_ctr
            counter_mask = (1 << self._counter_size) - 1
            if (prev_node_ctr & counter_mask) < counter_mask:
                self._prev = Scru64Id.from_parts(prev_timestamp, prev_node_ctr + 1)
            else:
                # increment timestamp at counter overflow
                self._prev = Scru64Id.from_parts(
                    prev_timestamp + 1, self._renew_node_ctr(prev_timestamp + 1)
                )
        else:
            # abort if clock went backwards to unbearable extent
            return None
        return self._prev

class NodeSpec:
    """
    Represents a node configuration specifier used to build a `Scru64Generator`.

    A `NodeSpec` is usually expressed as a node spec string, which starts with a decimal
    `node_id`, a hexadecimal `node_id` prefixed by "0x", or a 12-digit `node_prev`
    SCRU64 ID value, followed by a slash and a decimal `node_id_size` value ranging from
    1 to 23 (e.g., "42/8", "0xb00/12", "0u2r85hm2pt3/16"). The first and second forms
    create a fresh new generator with the given `node_id`, while the third form
    constructs one that generates subsequent SCRU64 IDs to the `node_prev`. See also the
    usage notes in the SCRU64 spec for tips and techniques to design node configurations:
    https://github.com/scru64/spec#informative-usage-notes
    """

    def __init__(
        self, node_id_or_prev: typing.Union[int, Scru64Id], node_id_size: int
    ) -> None:
        """
        Creates an instance with `node_id` or `node_prev` and `node_id_size` values.

        Raises:
            `ValueError` if the `node_id_size` is less than 1 or greater than 23 or if
            the `node_id` does not fit in `node_id_size` bits.
        """
        if node_id_size <= 0 or node_id_size >= NODE_CTR_SIZE:
            raise ValueError(f"`node_id_size` ({node_id_size}) must range from 1 to 23")
        if not isinstance(node_id_or_prev, Scru64Id):
            if node_id_or_prev < 0 or node_id_or_prev >= (1 << node_id_size):
                raise ValueError(
                    f"`node_id` ({node_id_or_prev}) must fit in `node_id_size` ({node_id_size}) bits"
                )
            counter_size = NODE_CTR_SIZE - node_id_size
            node_id_or_prev = Scru64Id.from_parts(0, node_id_or_prev << counter_size)
        self._node_prev: Scru64Id = node_id_or_prev
        self._node_id_size: int = node_id_size

    def node_id_size(self) -> int:
        """Returns the `node_id_size` value."""
        return self._node_id_size

    def node_id(self) -> int:
        """
        Returns the `node_id` value given at instance creation or encoded in the
        `node_prev` value.
        """
        counter_size = NODE_CTR_SIZE - self._node_id_size
        return self._node_prev.node_ctr >> counter_size

    def node_prev(self) -> typing.Optional[Scru64Id]:
        """
        Returns the `node_prev` value if `self` is constructed with one or `None`
        otherwise.
        """
        if self._node_prev.timestamp > 0:
            return self._node_prev
        else:
            return None

    @classmethod
    def parse(cls, node_spec: str) -> NodeSpec:
        """
        Creates an instance from a node spec string.

        Raises:
            `ValueError` if an invalid `node_spec` string is passed.
        """
        m = re.fullmatch(
            r"(?:([0-9a-z]{12})|([0-9]{1,8})|0x([0-9a-f]{1,6}))\/([0-9]{1,3})",
            node_spec,
            flags=re.ASCII | re.IGNORECASE,
        )
        if m is None:
            raise ValueError(
                'could not parse string as node spec (expected: e.g., "42/8", "0xb00/12", "0u2r85hm2pt3/16")'
            )
        node_id_size = int(m[4], 10)
        if m[1] is not None:
            return cls(Scru64Id.from_str(m[1]), node_id_size)
        elif m[2] is not None:
            return cls(int(m[2], 10), node_id_size)
        elif m[3] is not None:
            return cls(int(m[3], 16), node_id_size)
        else:
            raise AssertionError("unreachable")

    def __str__(self) -> str:
        node_prev = self.node_prev()
        if node_prev is not None:
            return f"{node_prev}/{self._node_id_size}"
        else:
            return f"{self.node_id()}/{self._node_id_size}"

class GlobalGenerator:
    """
    The gateway class that forwards supported method calls to the process-wide global
    generator.

    By default, the global generator reads the node configuration from the
    `SCRU64_NODE_SPEC` environment variable when a generator method is first called, and
    it raises an error if it fails to do so. The node configuration is encoded in a node
    spec string consisting of `node_id` and `node_id_size` integers separated by a slash
    (e.g., "42/8", "0xb00/12"; see `NodeSpec` for details). You can configure the global
    generator differently by calling `GlobalGenerator.initialize` before the default
    initializer is triggered.
    """

    _instance: typing.Optional[Scru64Generator] = None
    _lock: threading.Lock = threading.Lock()

    @classmethod
    def _get(cls) -> Scru64Generator:
        # double-checked locking pattern
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    node_spec = os.environ.get("SCRU64_NODE_SPEC")
                    if node_spec is None:
                        raise KeyError(
                            "scru64: could not read config from SCRU64_NODE_SPEC env var"
                        )
                    cls._instance = Scru64Generator(node_spec)
        return cls._instance

    @classmethod
    def initialize(cls, node_spec: typing.Union[str, NodeSpec]) -> bool:
        """
        Initializes the global generator, if not initialized, with the node spec passed.

        This method tries to configure the global generator with the argument only when
        the global generator is not yet initialized. Otherwise, it preserves the
        existing configuration.

        Raises:
            `ValueError` if the `node_spec` is given in a string and could not be parsed
            as a well-formed node spec string.
        Returns:
            `True` if this method configures the global generator or `False` if it
            preserves the existing configuration.
        """
        # double-checked locking pattern
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = Scru64Generator(node_spec)
                    return True
        return False

    @classmethod
    def generate(cls) -> typing.Optional[Scru64Id]:
        """Calls `Scru64Generator.generate` of the global generator."""
        return cls._get().generate()

    @classmethod
    def generate_or_sleep(cls) -> Scru64Id:
        """Calls `Scru64Generator.generate_or_sleep` of the global generator."""
        return cls._get().generate_or_sleep()

    @classmethod
    async def generate_or_await(cls) -> Scru64Id:
        """Calls `Scru64Generator.generate_or_await` of the global generator."""
        return await cls._get().generate_or_await()

    @classmethod
    def node_id(cls) -> int:
        """Calls `Scru64Generator.node_id` of the global generator."""
        return cls._get().node_id()

    @classmethod
    def node_id_size(cls) -> int:
        """Calls `Scru64Generator.node_id_size` of the global generator."""
        return cls._get().node_id_size()

    @classmethod
    def node_spec(cls) -> NodeSpec:
        """Calls `Scru64Generator.node_spec` of the global generator."""
        return cls._get().node_spec()

def new_sync() -> Scru64Id:
    """
    Generates a new SCRU64 ID object using the global generator.

    By default, the global generator reads the node configuration from the
    `SCRU64_NODE_SPEC` environment variable when a generator method is first called, and
    it raises an error if it fails to do so. The node configuration is encoded in a node
    spec string consisting of `node_id` and `node_id_size` integers separated by a slash
    (e.g., "42/8", "0xb00/12"; see `NodeSpec` for details). You can configure the global
    generator differently by calling `GlobalGenerator.initialize` before the default
    initializer is triggered.

    This function usually returns a value immediately, but if not possible, it sleeps
    and waits for the next timestamp tick. It employs blocking sleep to wait; see
    `new` for the non-blocking equivalent.

    This function is thread-safe; multiple threads can call it concurrently.

    Raises:
        An error if the global generator is not properly configured.
    """
    return GlobalGenerator.generate_or_sleep()

def new_string_sync() -> str:
    """
    Generates a new SCRU64 ID encoded in the 12-digit canonical string representation
    using the global generator.

    By default, the global generator reads the node configuration from the
    `SCRU64_NODE_SPEC` environment variable when a generator method is first called, and
    it raises an error if it fails to do so. The node configuration is encoded in a node
    spec string consisting of `node_id` and `node_id_size` integers separated by a slash
    (e.g., "42/8", "0xb00/12"; see `NodeSpec` for details). You can configure the global
    generator differently by calling `GlobalGenerator.initialize` before the default
    initializer is triggered.

    This function usually returns a value immediately, but if not possible, it sleeps
    and waits for the next timestamp tick. It employs blocking sleep to wait; see
    `new_string` for the non-blocking equivalent.

    This function is thread-safe; multiple threads can call it concurrently.

    Raises:
        An error if the global generator is not properly configured.
    """
    return str(new_sync())

async def new() -> Scru64Id:
    """
    Generates a new SCRU64 ID object using the global generator.

    By default, the global generator reads the node configuration from the
    `SCRU64_NODE_SPEC` environment variable when a generator method is first called, and
    it raises an error if it fails to do so. The node configuration is encoded in a node
    spec string consisting of `node_id` and `node_id_size` integers separated by a slash
    (e.g., "42/8", "0xb00/12"; see `NodeSpec` for details). You can configure the global
    generator differently by calling `GlobalGenerator.initialize` before the default
    initializer is triggered.

    This function usually returns a value immediately, but if not possible, it sleeps
    and waits for the next timestamp tick.

    This function is thread-safe; multiple threads can call it concurrently.

    Raises:
        An error if the global generator is not properly configured.
    """
    return await GlobalGenerator.generate_or_await()

async def new_string() -> str:
    """
    Generates a new SCRU64 ID encoded in the 12-digit canonical string representation
    using the global generator.

    By default, the global generator reads the node configuration from the
    `SCRU64_NODE_SPEC` environment variable when a generator method is first called, and
    it raises an error if it fails to do so. The node configuration is encoded in a node
    spec string consisting of `node_id` and `node_id_size` integers separated by a slash
    (e.g., "42/8", "0xb00/12"; see `NodeSpec` for details). You can configure the global
    generator differently by calling `GlobalGenerator.initialize` before the default
    initializer is triggered.

    This function usually returns a value immediately, but if not possible, it sleeps
    and waits for the next timestamp tick.

    This function is thread-safe; multiple threads can call it concurrently.

    Raises:
        An error if the global generator is not properly configured.
    """
    return str(await GlobalGenerator.generate_or_await())


# coding=<utf-8>
"""
Author: DoranLyong

Load the pretrained networks in torchvision project.
    * [ref] https://github.com/pytorch/vision
    * [ref] https://github.com/deep-learning-with-pytorch/dlwpt-code/blob/master/p1ch2/3_cyclegan.ipynb
"""
import os
import os.path as osp

from PIL import Image
import torch
import torch.nn as nn
from torchvision import transforms

class ResNetBlock(nn.Module): # <1>

    def __init__(self, dim):
        super(ResNetBlock, self).__init__()
        self.conv_block = self.build_conv_block(dim)

    def build_conv_block(self, dim):
        conv_block = []

        conv_block += [nn.ReflectionPad2d(1)]

        conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=0, bias=True),
                       nn.InstanceNorm2d(dim),
                       nn.ReLU(True)]

        conv_block += [nn.ReflectionPad2d(1)]

        conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=0, bias=True),
                       nn.InstanceNorm2d(dim)]

        return nn.Sequential(*conv_block)  # conv_block 리스트의 모든 요소를 파라미터로 던짐

    def forward(self, x):
        out = x + self.conv_block(x) # <2>
        return out

class ResNetGenerator(nn.Module):

    def __init__(self, input_nc=3, output_nc=3, ngf=64, n_blocks=9): # <3>

        assert(n_blocks >= 0)
        super(ResNetGenerator, self).__init__()

        self.input_nc = input_nc
        self.output_nc = output_nc
        self.ngf = ngf

        model = [nn.ReflectionPad2d(3),
                 nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=True),
                 nn.InstanceNorm2d(ngf),
                 nn.ReLU(True)]

        n_downsampling = 2
        for i in range(n_downsampling):
            mult = 2**i
            model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3,
                                stride=2, padding=1, bias=True),
                      nn.InstanceNorm2d(ngf * mult * 2),
                      nn.ReLU(True)]

        mult = 2**n_downsampling
        for i in range(n_blocks):
            model += [ResNetBlock(ngf * mult)]

        for i in range(n_downsampling):
            mult = 2**(n_downsampling - i)
            model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2),
                                         kernel_size=3, stride=2,
                                         padding=1, output_padding=1,
                                         bias=True),
                      nn.InstanceNorm2d(int(ngf * mult / 2)),
                      nn.ReLU(True)]

        model += [nn.ReflectionPad2d(3)]
        model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)]
        model += [nn.Tanh()]

        self.model = nn.Sequential(*model)

    def forward(self, input): # <3>
        return self.model(input)

def main():

    """ 1. 모델 초기화 """
    net = ResNetGenerator()  # 모델 초기화

    model_path = osp.join(osp.abspath(""), "params", "horse2zebra_0.4.0.pth")
    model_data = torch.load(model_path)

    net.load_state_dict(model_data)  #모델 아키텍쳐에 학습된 가중치 파라미터를 로드

    """ 2. Data load & transform """
    imgPath = osp.join(osp.abspath(""), "sample_data", "horse_sample.jpg" )
    img = Image.open(imgPath) # 이미지 불러오기

    preprocess = transforms.Compose([   transforms.Resize(256),
                                        transforms.ToTensor(),
                                    ])

    img_t = preprocess(img)
    batch_t = torch.unsqueeze(img_t, 0)

    """ 3. Model Inference """
    net.eval()  # # Set evaluation mode for inference without backward

    batch_out = net(batch_t)

    out_t = (batch_out.data.squeeze() + 1.0) / 2.0
    out_img = transforms.ToPILImage()(out_t)
    out_img.save(osp.join(osp.abspath(""), "sample_data",'cycleGAN_zebra.jpg'))

if __name__ == "__main__":
    main()


from SPDSiamese.model import ST_TS_SPDC, Net
from SPDSiamese.Dataloader import Dataset_learning, Dataset_classification
from SPDSiamese.trainer import train, test
from SPDSiamese.Siamese import *
from SPDSiamese.optimizer import *
from SPDSiamese.download_dataset import *
from SPDSiamese.set_dataset import set_dataset, makedir_path
from torch.utils.data import Dataset, DataLoader
import argparse
import pickle
import shutil, os
import torch
from torch import nn
import numpy as np
import warnings
warnings.filterwarnings("ignore")

parser = argparse.ArgumentParser(description = "Online recognition parameters and datasets")
parser.add_argument(
    "--path",
    type = str,
    default = "",
    help = "Path to either the folder containing the skeletal data", )

parser.add_argument(
    "--dataset",
    type = str,
    default = "OAD",
    choices = ["ODHG", "OAD", "UOW", "InHard"],
    help = "the proposed skeletal datasets",
)

parser.add_argument(
    "--execution",
    type = str,
    default = "Classifier",
    choices = ["Classifier", "Detector"],
    help = "type of the execution",
)

parser.add_argument(
    "--interpolation",
    type = int,
    default = 500,
    help = "The skeletal sequences will be normalized to the proposed number of frames",
)

parser.add_argument(
    "--batch_size",
    type = int,
    default = 30,
    help = "batch size in DataLoader",
)

parser.add_argument(
    "--t0",
    type = int,
    default = 1,
    help = "t0: time interval of the window in spatial - temporal studies",
)

parser.add_argument(
    "--NS",
    type = int,
    default = 15,
    help = "NS: number of secondary subsequences in each primary subsequnece during the temporal - spatial studies",
)

parser.add_argument(
    "--eps",
    type = float,
    default = 10 ** ( - 4),
    help = "eps: threshold of the ReEig layer",
)

parser.add_argument(
    "--outs_trans",
    type = int,
    default = 200,
    help = "outs_trans: parameter of the transformation of the SPD matrix size in SPD Aggregation layer",
)

parser.add_argument(
    "--lr_learning",
    type = float,
    default = 10 ** ( - 5),
    help = "lr: optimizer learning rate of the learning model",
)

parser.add_argument(
    "--lr_classification",
    type = float,
    default = 7 * (10 ** ( - 4)),
    help = "lr: optimizer learning rate of the classification model",
)

parser.add_argument(
    "--margin",
    type = float,
    default = 7.0,
    help = "margin of the contrastive loss function of the Siamese network",
)

parser.add_argument(
    "--m",
    type = int,
    help = "m : refresh rate of the detector",
)

parser.add_argument(
    "--ws",
    type = int,
    help = "ws : window size of the detector",
)

parser.add_argument(
    "--learning_epochs",
    type = int,
    default = 10,
    help = "number of epoch needed for SPD learning",
)

parser.add_argument(
    "--classification_epochs",
    type = int,
    default = 100,
    help = "number of epoch needed for SPD classification",
)

parser.set_defaults(feature = False)
args = parser.parse_args()

if not os.path.isdir(args.path + args.dataset):
    download_dataset(args.dataset, args.path)
path = args.path + "/" + args.dataset + "/" + args.execution
type_detector = {"ODHG" : "Binary", "OAD" : "Binary", "InHard" : "Binary", "UOW" : "General"}
###         Checking the dataset            ###
if not os.path.isdir(path):
    makedir_path(path)
    makedir_path(path + "/models")
    primary_path = args.path + "/" + args.dataset + "/Online/"
    set_dataset(args.dataset, args.execution, path, primary_path, args.ws, args.m, type_detector)

ori_classes = [int(i) for i in os.listdir(path + "/train/")]
ori_classes.sort()
for i in range(len(ori_classes)):
    os.rename(path + "/train/" + str(ori_classes[i]), path + "/train/" + str(i))
    os.rename(path + "/test/" + str(ori_classes[i]), path + "/test/" + str(i))
N = len(ori_classes) # Number of classes

###       Loading dataset      ###
print("Loading dataset ...\n")
transformed_dataset = Dataset_learning(path, args.interpolation, train = True)
dataloader = DataLoader(transformed_dataset, batch_size = args.batch_size, shuffle = True, num_workers = 0)
transformed_dataset_test = Dataset_learning(path, args.interpolation, train = False)
dataloader_test = DataLoader(transformed_dataset_test, batch_size = args.batch_size, shuffle = False, num_workers = 0)

#Partitioning
parts = dict()
parts["InHard"] = [[12, 11, 10, 0, 1, 2, 3], [12, 11, 10, 0, 4, 5, 6], [12, 11, 10, 13, 14, 15, 16], [12, 11, 10, 17, 18, 19, 20]]
parts["ODHG"] = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]]
parts["OAD"] = [[3, 2, 20, 4, 5, 6, 7, 21, 22], [3, 2, 20, 1, 0, 12, 13, 14, 15], [3, 2, 20, 1, 0, 16, 17, 18, 19], [3, 2, 20, 8, 9, 10, 11, 23, 24]]
parts["UOW"] = [[3, 2, 20, 4, 5, 6, 7, 21, 22], [3, 2, 20, 1, 0, 12, 13, 14, 15], [3, 2, 20, 1, 0, 16, 17, 18, 19], [3, 2, 20, 8, 9, 10, 11, 23, 24]]

model = ST_TS_SPDC(N, parts[args.dataset], t0 = args.t0, NS = args.NS, eps = args.eps, vect = True, outs = args.outs_trans)
use_cuda = False
if use_cuda:
    model = model.cuda()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr = args.lr_learning)
optimizer = StiefelMetaOptimizer(optimizer)

print("---------------------------------- - \nSPD Matrices learning...\n---------------------------------- - ")
start_epoch = 1
for epoch in range(start_epoch, start_epoch + args.learning_epochs):
    train_loss, train_acc = train(epoch, model, optimizer, criterion, dataloader, use_cuda)
    test_loss, test_acc = test(epoch, model, optimizer, criterion, dataloader_test, use_cuda)
torch.save(model.state_dict(), path + "/models/spd_learning.pth")
makedir_path(path + "/learnt_spd_matrices/")
print("Loading SPD matrices...")
y = torch.empty(0, args.outs_trans, args.outs_trans)
l = torch.empty(0)
for i, r in enumerate(dataloader):
    y = torch.cat((y, model(torch.tensor(r['data'], requires_grad=True))[1]))
    l = torch.cat((l, r['label'].type(torch.LongTensor)))
torch.save({"data": y, "label" : l}, path + "/learnt_spd_matrices/train.pth")

y = torch.empty(0, args.outs_trans, args.outs_trans)
l = torch.empty(0)
for i, r in enumerate(dataloader_test):
    y = torch.cat((y, model(torch.tensor(r['data'], requires_grad=True))[1]))
    l = torch.cat((l, r['label'].type(torch.LongTensor)))
torch.save({"data": y, "label" : l}, path + "/learnt_spd_matrices/test.pth")

print("Loading Siamese dataset...")
transformed_dataset = Dataset_classification(path + "/learnt_spd_matrices")
train_loader = DataLoader(transformed_dataset, batch_size = args.batch_size, shuffle = True, num_workers = 0)

transformed_dataset_val = Dataset_classification(path + "/learnt_spd_matrices", train = False)
test_loader = DataLoader(transformed_dataset_val, batch_size = args.batch_size, shuffle = False, num_workers = 0)

siamese_transformed_dataset = SiameseDataset(Dataset_classification(path + "/learnt_spd_matrices"), N)
siamese_train_loader = DataLoader(siamese_transformed_dataset, batch_size = args.batch_size, shuffle = True, num_workers = 0)

siamese_transformed_dataset_val = SiameseDataset(Dataset_classification(path + "/learnt_spd_matrices", train = False), N)
siamese_test_loader = DataLoader(siamese_transformed_dataset_val, batch_size = args.batch_size, shuffle = False, num_workers = 0)

tu = [train_loader.dataset[i]['data'].unsqueeze(0) for i in range(len(train_loader.dataset))]
tot = torch.cat(tuple(tu), 0)
tu = [train_loader.dataset[i]['label'].unsqueeze(0) for i in range(len(train_loader.dataset))]
tot_label = torch.cat(tuple(tu), 0)
tu = [test_loader.dataset[i]['data'].unsqueeze(0) for i in range(len(test_loader.dataset))]
tot_test = torch.cat(tuple(tu), 0)
tu = [test_loader.dataset[i]['label'].unsqueeze(0) for i in range(len(test_loader.dataset))]
tot_label_test = torch.cat(tuple(tu), 0)

rep = torch.zeros(N, args.outs_trans, args.outs_trans)
for j in range(N):
    i = 0
    while int(train_loader.dataset[i]['label'].item())!= j:
        i +=1
    rep[j] = train_loader.dataset[i]['data']

margin = args.margin
embedding_net = Net(N, args.outs_trans)
model = SiameseNet(embedding_net)
cuda = False
if cuda:
    model.cuda()
loss_fn = ContrastiveLoss(margin)
lr = args.lr_classification
optimizer = torch.optim.Adam(model.parameters(), lr = lr)

start_epoch = 1
loss_train, loss_test, acc_train, acc_test = {}, {}, {}, {}
print("---------------------------------- - \nSiamese network :training...\n---------------------------------- - ")
for epoch in range(start_epoch, start_epoch + args.classification_epochs):
    print("\nEpoch ", epoch, ":")
    train_loss, train_acc = train_siamese(epoch, siamese_train_loader, model, loss_fn, optimizer, cuda, N, tot, tot_label, rep, margin)
    test_loss, test_acc = test_siamese(epoch, siamese_test_loader, model, loss_fn, cuda, N, tot_test, tot_label_test, rep, margin)
    loss_train[epoch], loss_test[epoch], acc_train[epoch], acc_test[epoch] = train_loss, test_loss, train_acc, test_acc
    L = [loss_train, loss_test, acc_train, acc_test]
    if test_acc >= max(acc_test.values()):
        torch.save(model.state_dict(), path + "/models/siamese_best.pth")

print("Choosing best representers ...")
model.load_state_dict(torch.load(path + "/models/siamese_best.pth"))
if len(tot_test) < 2000:
    x_test = model.get_embedding(tot_test)
else:
    x_test = torch.empty(len(tot_test), N)
    for i in range(len(tot_test) // 2000 - 2):
        x_test[2000 * i: 2000 * (i + 1)] = model.get_embedding(tot_test[2000 * i: 2000 * (i + 1)])
    x_test[2000 * (i + 1):] = model.get_embedding(tot_test[2000 * (i + 1):])

repp = rep.clone()
rep_best = repp
acc = {}
acc_best = {}
for i in range(N):
    acc[i] = {}
    acc_best[i] = []
    c = torch.where(tot_label == i)[0]
    for j in range(len(c)):
        rep_best[i] = tot[c[j]]
        x_rep = model.get_embedding(rep_best)
        dist_matrix = dis(x_test.unsqueeze(1).expand(x_test.size(0), N, N), x_rep.expand(x_test.size(0), N, N))
        d = dist_matrix.min(1)[1] - tot_label_test
        Acc = d[d == 0].size(0)
        acc[i][int(c[j].item())] = Acc / len(tot_label_test)
        l = []
    l = list(acc[i].values())
    l.sort()
    cc = l[-1]
    for k in acc[i]:
        if acc[i][k] == cc:
            acc_best[i].append(k)
    rep_best[i] = tot[acc_best[i][0]]

torch.save({"state":model.state_dict(), "rep":rep_best, "acc": cc}, path + "/models/siamese_best.pth")
parameters = {"path": args.path, "dataset": args.dataset, "execution": args.execution,
             "interpolation": args.interpolation, "batch_size": args.batch_size, "t0": args.t0,
             "NS": args.NS, "eps": args.eps, "outs_trans": args.outs_trans, "lr_learning": args.lr_learning,
             "args.lr_classification": args.lr_classification, "margin": args.margin, "m": args.m,
             "ws": args.ws, "learning_epochs": args.learning_epochs, "classification_epochs": args.classification_epochs,
             "ori_classes": ori_classes}
torch.save(parameters, path + "/models/parameters.pth")

print("The exectution is finished. The resulting accuracy is about " + str(np.round(cc * 10000)/100) +
    "%. You can check the models weights and the parameters in the directory " +  path + "/models/.")


"""
Plot a Hertzsprung-Russell diagram of the stars in the training set,
and a histogram of the [X/H] abundances for each element.
"""

import numpy as np
from astropy.table import Table

import matplotlib.colors as cm
import matplotlib.pyplot as plt
from colormaps import viridis, _viridis_data
from matplotlib.ticker import MaxNLocator

labelled_set = Table.read("/Users/arc/research/apogee/apogee-rg.fits")

# Identify the training set
np.random.seed(123)
q = np.random.randint(0, 10, len(labelled_set)) % 10
training_set = labelled_set[(q > 0)]

elements = [label for label in training_set.dtype.names \
    if  label.endswith("_H")  and \
        label.count("_") == 1 and \
        label not in ("PARAM_M_H", "SRC_H")]

# Get the bin sizes.
abundances = np.array([training_set[element] for element in elements])

fig, (ax_hrd, ax_hexbin, ax_hist) = plt.subplots(1, 3, figsize=(18.6, 6.3))

sort_by = np.argsort(training_set["FE_H"])
hrd = ax_hrd.scatter(training_set["TEFF"][sort_by], training_set["LOGG"][sort_by],
    c=training_set["FE_H"][sort_by], cmap=viridis, s=50, rasterized=True,
    lw=0.25, vmin=-2, vmax=0.5)

ax_hrd.set_xlim(5750, 3500)
ax_hrd.set_ylim(4.5, -0.5)

ax_hrd.set_xlabel(r"$T_{\rm eff}$ $(K)$")
ax_hrd.set_ylabel(r"$\log{g}$")

ax_hrd.xaxis.set_major_locator(MaxNLocator(5))
ax_hrd.yaxis.set_major_locator(MaxNLocator(5))

ax_hexbin.hexbin(training_set["TEFF"], training_set["LOGG"], bins="log",
    cmap="viridis", gridsize=75, marginals=True,
    extent=(ax_hrd.get_xlim()[0], ax_hrd.get_xlim()[1], ax_hrd.get_ylim()[0],
        ax_hrd.get_ylim()[1]))

ax_hexbin.set_xlim(ax_hrd.get_xlim())
ax_hexbin.set_ylim(ax_hrd.get_ylim())

ax_hexbin.set_xlabel(ax_hrd.get_xlabel())
ax_hexbin.set_ylabel(ax_hrd.get_ylabel())

ax_hexbin.xaxis.set_major_locator(MaxNLocator(5))
ax_hexbin.yaxis.set_major_locator(MaxNLocator(5))

bins = np.linspace(-3, 1, 25)

cmap = cm.LinearSegmentedColormap.from_list(
    "viridis", _viridis_data, len(elements))

for j, element in enumerate(elements):
    ax_hist.hist(training_set[element], bins=bins, color=cmap(j),
        histtype="step", lw=2, label=None, normed=True)

    ax_hist.plot([], [], color=cmap(j), lw=2,
        label=r"${\rm " + element.split("_")[0].title() + r"}$")

ax_hist.legend(frameon=False, loc="upper left", ncol=2)
ax_hist.set_xlabel(r"$[{\rm X}/{\rm H}]$")
ax_hist.yaxis.tick_right()
ax_hist.yaxis.set_label_position("right")
ax_hist.yaxis.set_major_locator(MaxNLocator(5))
ax_hist.xaxis.set_major_locator(MaxNLocator(5))

ax_hist.set_ylabel(r"${\rm Normalized}$ ${\rm count}$")

ax_hrd.set(adjustable='box-forced', aspect=np.ptp(ax_hrd.get_xlim())/np.ptp(ax_hrd.get_ylim()))
ax_hexbin.set(adjustable='box-forced', aspect=np.ptp(ax_hrd.get_xlim())/np.ptp(ax_hrd.get_ylim()))
ax_hist.set(adjustable='box-forced', aspect=np.ptp(ax_hist.get_xlim())/np.ptp(ax_hist.get_ylim()))

fig.tight_layout()

pos = ax_hrd.get_position()

ax_hrd_cbar = fig.add_axes([pos.x0 + 0.020, pos.y0 + pos.height - 0.130,
    pos.width * 0.60, 0.05])
cbar_hrd = plt.colorbar(hrd, cax=ax_hrd_cbar, orientation="horizontal")

cbar_hrd.set_ticks([-2, -1.5, -1, -0.5, 0, 0.5])
cbar_hrd.ax.set_xlabel(r"$[{\rm Fe}/{\rm H}]$")

fig.savefig("training_set_hrd.pdf", dpi=300)

#     cmaps._viridis_data,
#len(scale_factors))


"""
This script takes two csv files and compares them based on the field provided.
For example, if a field called "JOBID" is provided, this program takes File1 and
File2 and compares them based on the "JOBID" field. This program determines whether the field provided are
identical in both files or not. If they are not identical, a report is exported illustrating the differences.

To Run:
python data_compare.py -f1 {File1} -f2 {file2} -c {Field to compare} -o {Report Output Location}
"""
import argparse
import os
import pandas as pd
from pathlib import Path
import pendulum
from prefect import task, Flow, Parameter

now = pendulum.now()
current_dir = Path.cwd()

def sort_nums(num1, num2):
    """utility function to return numbers in order"""
    if num1 > num2:
        return num1, num2
    elif num2 > num1:
        return num2, num1

def timestamper(task, old_state, new_state):
    """
    Task state handler that timestamps new states
    and logs the duration between state changes using
    the task's logger.
    """
    new_state.timestamp = pendulum.now("utc")
    if hasattr(old_state, "timestamp"):
        duration = (new_state.timestamp - old_state.timestamp).in_seconds()
        task.logger.info(
            "{} seconds passed in between state transitions".format(duration)
        )
    return new_state

@task(name='Gather Data', state_handlers=[timestamper])
def gather_data(instance):
    """
    Gathers paths and converts to data frames per the arguments provided. Multiple
    checks are in place to ensure data exists prior to processing.
    :param args: Supplied arguments when the program is initiated
    :return: 2 dataframes and arguments object
    """
    paths = [instance.get('file1'), instance.get('file2')]
    if Path(instance.get('file1')).parents[0].is_dir() is True and Path(
            instance.get('file2')).parents[0].is_dir() is True:
        files = [f for f in paths if os.path.isfile(f)]
        if len(files) == 0:
            raise Exception('The files you passed do not exist!')
        dfs = []
        for file in files:
            try:
                if file.endswith('.csv'):
                    dfs.append(pd.read_csv(file))

                else:
                    raise Exception('Please pass a file ending in .csv')

            except Exception as exc:
                formatted = "Unable to locate files! Please ensure you have provided accurate file paths. {}".format(
                    repr(exc))
                raise Exception(formatted)

        return dfs, instance

    else:
        raise Exception('Please pass a valid file path.')

@task(name='Clean Data', state_handlers=[timestamper])
def clean_data(data):
    """
    cleans and standardizes data
    :param data: 2 dataframes and arguments object
    :return: cleaned data sets
    """
    # switch dict to flip the names of the RE data set to match SF
    col_switch = {'fund_id': 'project_id',
                  'gift_count': 'record_count.5',
                  'total': 'sum_of_amount.5'}

    # define package items
    dfs = data[0]
    args = data[1]

    # clean columns in dataframes
    for df in dfs:
        df.columns = [x.lower() for x in df.columns]
        if 'fund id' in df.columns:
            df.columns = [x.lower().replace(' ', '_') for x in df.columns]
            df['source'] = 'RE'
            df.rename(columns=col_switch, inplace=True)
        else:
            df.columns = [x.lower().replace(' ', '_') for x in df.columns]
            df['source'] = 'SF'
            df.drop(columns=['sum_of_amount', 'record_count', 'sum_of_amount.1', 'record_count.1', 'sum_of_amount.2',
                             'record_count.2', 'sum_of_amount.3',
                             'record_count.3', 'sum_of_amount.4', 'record_count.4'], inplace=True)

    # standardize project_IDs
    for df in dfs:
        df['project_id_new'] = df['project_id'].apply(lambda x: x.strip() if isinstance(x, str) else x)
        df['project_id_new'] = df['project_id_new'].apply(
            lambda x: x.replace(' - ', '-').lower() if isinstance(x, str) and '-' in x else x)
        df['project_id_new'] = df['project_id_new'].apply(
            lambda x: x.replace('- ', '-').lower() if isinstance(x, str) and '-' in x else x)
        df['project_id_new'] = df['project_id_new'].apply(
            lambda x: x.replace(' -', '-').lower() if isinstance(x, str) and '-' in x else x)
        df['project_id_new'] = df['project_id_new'].apply(
            lambda x: x.replace('-', '_').lower() if isinstance(x, str) and '-' in x else x)
        df['project_id_new'] = df['project_id_new'].apply(
            lambda x: x.replace(' ', '_').lower() if isinstance(x, str) and ' ' in x else x)
        df['project_id_new'] = df['project_id_new'].apply(lambda x: x.lower() if isinstance(x, str) else x)

    # standardize sum_of_amount
    for df in dfs:
        df['sum_of_amount_new'] = df['sum_of_amount.5'].apply(lambda x: x.strip() if isinstance(x, str) else x)
        df['sum_of_amount_new'] = df['sum_of_amount_new'].apply(
            lambda x: x.replace('USD', '').lower() if isinstance(x, str) and 'USD' in x else x)
        df['sum_of_amount_new'] = df['sum_of_amount_new'].apply(
            lambda x: x.replace('$', '').lower() if isinstance(x, str) and '$' in x else x)
        df['sum_of_amount_new'] = df['sum_of_amount_new'].apply(
            lambda x: x.replace(',', '').lower() if isinstance(x, str) and ',' in x else x)
        df['sum_of_amount_new'] = df['sum_of_amount_new'].apply(
            lambda x: float("{0:.2f}".format(float(x))) if isinstance(x, str) else x)

    return dfs, args

@task(name='Compare Data', state_handlers=[timestamper])
def compare_data(data):
    """
    Using pandas, this compares the data via the field provided.
    :param data: 2 dataframes and arguments object
    :return: either a string stating a match or the difference data.
    """
    dfs = data[0]
    args = data[1]

    # ID the project IDs that do not exist in both data sets
    missing = []
    for value in dfs[0]['project_id_new']:
        if value not in [x for x in dfs[1]['project_id_new']]:
            missing.append(value)

    for value in dfs[1]['project_id_new']:
        if value not in [x for x in dfs[0]['project_id_new']] and value not in missing:
            missing.append(value)

    # join data on values that exist in both data sets
    df = pd.merge(dfs[0], dfs[1], how='inner', on='project_id_new')

    differ = {}

    for index, row in df.iterrows():
        project = row['project_id_new']
        if row['sum_of_amount_new_x'] != row['sum_of_amount_new_y']:
            num1, num2 = sort_nums(row['sum_of_amount_new_x'], row['sum_of_amount_new_y'])
            variance = num1 - num2
            differ.update({project: variance})

    return missing, differ, args

@task(name='Report Findings', state_handlers=[timestamper])
def report_findings(findings):
    """
    Compiles differences into a report and structures it so it is legible.
    :param findings: Output of the comparison function.
    :return: Report saved to the location specified in the initial arguments.
    """
    missing = pd.DataFrame(findings[0], columns=['project_id'])
    differ = pd.DataFrame(list(findings[1].items()), columns=['project_id', 'variance'])
    args = findings[2]

    if args['output'] is not False:
        if Path(args['output']).is_dir():
            # build filenames
            missing_report_destination = Path(args['output'] + 'missing_report_{}'.format(str(now)) + '.csv')
            differ_report_destination = Path(args['output'] + 'differ_report_{}'.format(str(now)) + '.csv')

            # output df to csv
            missing.to_csv(missing_report_destination, index=False)
            differ.to_csv(differ_report_destination, index=False)

    elif args['output'] is False:
        # build filenames
        missing_report_destination = Path(str(current_dir) + 'missing_report_{}'.format(str(now)) + '.csv')
        differ_report_destination = Path(str(current_dir) + 'differ_report_{}'.format(str(now)) + '.csv')

        # output df to csv
        missing.to_csv(missing_report_destination, index=False)
        differ.to_csv(differ_report_destination, index=False)

def run(args):
    with Flow('Compare Data') as flow:
        gather = gather_data(args)
        clean = clean_data(gather)
        compare = compare_data(clean)
        report_findings(compare)

    return flow.run()

def main():
    parser = argparse.ArgumentParser(description='Data Comparison Tool')
    parser.add_argument('-f1', '--file1', default=False, required=True,
                        help='first file path to be compared'),
    parser.add_argument('-f2', '--file2', default=False, required=True,
                        help='second file path to be compared'),
    parser.add_argument('-c', '--compare_column', default=False,
                        required=False,
                        help='enter the column to be used during the comparison'),
    parser.add_argument('-o', '--output', default=False, required=True,
                        help='location where the report is saved.')

    results = run(vars(parser.parse_args()))

if __name__ == '__main__':
    main()


import statistics
import numpy as np
import os
from time import sleep
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as pl
import csv
import decimal
import math as m

def acf(series):
	n = len(series)
	data = np.asarray(series)
	mean = np.mean(data)
	c0 = np.sum((data - mean) ** 2) / float(n)

	def r(h):
		acf_lag = ((data[:n - h] - mean) * (data[h:] - mean)).sum() / float(n) / c0
		return round(acf_lag, 3)

	x = np.arange(n)
	acf_coeffs = list (map(r, x))
	return acf_coeffs

def computeCorrelation (timeSeries, outputFilename, dirs):

	arraySize = len (timeSeries)

	timeSeriesNumpy = np.array (timeSeries)
	time_axisvalues = [x for x in range(0, arraySize)]
	result = acf (timeSeriesNumpy)

	result_trimmed = result[:arraySize]

	rows = zip(time_axisvalues, result_trimmed)
	with open(dirs + "/acf_" + outputFilename + ".csv", "w") as csvfile:
		writer = csv.writer(csvfile)
		for row in rows:
			writer.writerow(row)

	binSize = 0.01
	nBins = m.ceil (2 / binSize)
	binPopulation = {}
	summation = 0

	for bin in range (nBins):
		binMin = -1 + bin * binSize
		binMax = binMin + binSize
		binPopulation[bin] = 0

		for x in timeSeries:
			if (x < binMax and x >= binMin):
				binPopulation[bin] += 1

	for x in range (60, 140, 1):
		summation += binPopulation[x]

	with open (dirs + "/COP_" + outputFilename + ".csv", "w") as output:
		for x, y in binPopulation.items ():
			output.write ("{}, {}\n".format (x, y))

	with open (dirs + "/fixedCOP_" + outputFilename + ".csv", "w") as output:
		for x, y in binPopulation.items ():
			try:
				y *= (4999/summation) # Change the normalization equation based on the input sample
			except:
				pass
			output.write ("{}, {}\n".format (x, y))

	with open (dirs + "/normCOP_" + outputFilename + ".csv", "w") as output:
		for x, y in binPopulation.items ():
			try:
				y /= summation
			except:
				pass
			output.write ("{}, {}\n".format (x, y))

def readfile(filename, dirs):
	chiral_overall = []
	chiral_inside = []
	chiral_outside = []

	with open(filename, "r") as file:
		next(file)
		for line in file:
			separated_values = line.split(",")
			chiral_overall.append(float(separated_values[0]))
			chiral_inside.append(float(separated_values[1]))
			chiral_outside.append(float(separated_values[2]))

	computeCorrelation (chiral_overall[:5000], "overall1", dirs)
	computeCorrelation (chiral_inside[:5000], "inside1", dirs)
	computeCorrelation (chiral_outside[:5000], "outside1", dirs)

	computeCorrelation (chiral_overall[5001:], "overall2", dirs)
	computeCorrelation (chiral_inside[5001:], "inside2", dirs)
	computeCorrelation (chiral_outside[5001:], "outside2", dirs)

def browseDirectories (filetemplate):
	# Goes through all directories and checks for the file "chiral_order_parameter.log"
	for dirs, dirname, files in os.walk("."):
		for file in files:
			if filetemplate in file:
				filePath = dirs + "/" + file
				print ("reading ", dirs + "/" + file)
				readfile (filePath, dirs)

if __name__ == '__main__':
	browseDirectories ("chiral_order_parameter.log")


from libqtile import bar, layout, widget
from libqtile.config import Click, Drag, Group, Key, Match, Screen
from libqtile.lazy import lazy
from libqtile.utils import guess_terminal
from libqtile import qtile
from pathlib import Path
import time
import colors as my_themes
import random
import os
import subprocess

from libqtile.log_utils import logger
from libqtile import hook

theme = my_themes.Dracula

@hook.subscribe.startup_once
def on_start_of_qtile():
    loc = os.path.expanduser('~/.config/qtile/autorun.sh')
    subprocess.Popen([loc])

@hook.subscribe.startup
async def on_start_or_restart():
    logger.info(f"{theme} is the theme and {theme.wallpaper_directory} is the dir")
    if theme.wallpaper_directory is not None:

        wallpapers = [file for file in  Path(theme.wallpaper_directory).expanduser().iterdir()]
        wallpaper_path = random.choice(wallpapers)

        for screen in qtile.screens:
            screen.cmd_set_wallpaper(wallpaper_path,'fill')

mod = "mod4"
terminal = guess_terminal()

groups = [
    Group(name='home', label=''),
    Group(name='terminal', label='', matches=[
        Match(title='Alacritty'),
    ]),
    Group(name='browser', label='', matches=[
        Match(wm_class='firefox'),
    ]),
    Group(name='explorer', label=''),
    Group(name='code', label='', matches=[
        Match(wm_class='Code'),
    ]),
    Group(name='games', label=''),
]

keys = [

    # Switch between app windows
    Key([mod], "h", lazy.layout.left(), desc="Move focus to left"),
    Key([mod], "l", lazy.layout.right(), desc="Move focus to right"),
    Key([mod], "j", lazy.layout.down(), desc="Move focus down"),
    Key([mod], "k", lazy.layout.up(), desc="Miove focus up"),
    Key([mod], "space", lazy.layout.next(), desc="Move window focus to other window"),

    # Moving app windows around
    Key([mod, "shift"], "h", lazy.layout.shuffle_left(), desc="Move window to the left"),
    Key([mod, "shift"], "l", lazy.layout.shuffle_right(), desc="Move window to the right"),
    Key([mod, "shift"], "j", lazy.layout.shuffle_down(), desc="Move window down"),
    Key([mod, "shift"], "k", lazy.layout.shuffle_up(), desc="Move window up"),

    # Grow app windows
    Key([mod, "control"], "h", lazy.layout.grow_left(), desc="Grow window to the left"),
    Key([mod, "control"], "l", lazy.layout.grow_right(), desc="Grow window to the right"),
    Key([mod, "control"], "j", lazy.layout.grow_down(), desc="Grow window down"),
    Key([mod, "control"], "k", lazy.layout.grow_up(), desc="Grow window up"),
    Key([mod], "n", lazy.layout.normalize(), desc="Reset all window sizes"),
    # Toggle between split and unsplit sides of stack.
    # Split = all windows displayed
    # Unsplit = 1 window displayed, like Max layout, but still with
    # multiple stack panes
    Key(
        [mod, "shift"],
        "Return",
        lazy.layout.toggle_split(),
        desc="Toggle between split and unsplit sides of stack",
    ),
    Key([mod], 'e', lazy.spawn(f'{terminal} -e ranger'), desc='Open File Explorer'),
    Key([mod], "Return", lazy.spawn(terminal), desc="Launch terminal"),
    Key([mod], "Tab", lazy.next_layout(), desc="Toggle between layouts"),
    Key([mod], "q", lazy.window.kill(), desc="Kill focused window"),
    Key([mod, "control"], "r", lazy.reload_config(), desc="Reload the config"),
    Key([mod, "control"], "q", lazy.shutdown(), desc="Shutdown Qtile"),
    Key([mod], "r", lazy.spawncmd(), desc="Spawn a command using a prompt widget"),
]

for index, _group in enumerate(groups):
    keyboard_number = str(index+1)
    keys.extend(
        [
            # mod1 + letter of group = switch to group
            Key([mod],keyboard_number,lazy.group[_group.name].toscreen(),desc="Switch to group {}".format(_group.name)),
            #mod1 + shift + letter of group = move focused window to group
            Key([mod, "shift"], keyboard_number, lazy.window.togroup(_group.name),desc="move focused window to group {}".format(_group.name)),
        ]
    )

USELESS_GAP = 10

layout_config = {
    "margin": USELESS_GAP,
    "border_width": 0
}

layouts = [
    layout.Tile(**layout_config),
    layout.Max(**layout_config),
    layout.MonadTall(**layout_config),
    layout.MonadWide(**layout_config),
]

widget_defaults = dict(
    font='fira code',
    fontsize=20,
    padding=3,
    background=theme.background,
    foreground=theme.foreground
)
extension_defaults = widget_defaults.copy()
SEP_ICON = ''

screens = [
    Screen(
        top=bar.Bar(
            [
                widget.GroupBox(highlight_method='block', disable_drag=True, active=theme.active, inactive=theme.inactive, block_highlight_text_color=theme.foreground),
                widget.Prompt(),
                widget.Spacer(length=bar.STRETCH),
                widget.Net(interface='enp0s3', prefix='M',format='{down}:{up}'),
                widget.TextBox(text=SEP_ICON),
                widget.CPU(format='{load_percent}%'),
                widget.TextBox(text=SEP_ICON),
                widget.Memory(measure_mem='G', format='{MemUsed:.2f}{mm}'),
                widget.TextBox(text=SEP_ICON),
                widget.Clock(format="%H:%M %m/%d/%Y"),
                widget.TextBox(text=SEP_ICON),
                widget.QuickExit(default_text='',countdown_format='{}'),
            ],
            24,
        ),
    ),
]

# Drag floating layouts.
mouse = [
    Drag([mod], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position()),
    Drag([mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()),
    Click([mod], "Button2", lazy.window.bring_to_front()),
]

dgroups_key_binder = None
dgroups_app_rules = []  # type: list
follow_mouse_focus = True
bring_front_click = False
cursor_warp = False
floating_layout = layout.Floating(
    float_rules=[
        # Run the utility of `xprop` to see the wm class and name of an X client.
        *layout.Floating.default_float_rules,
        Match(wm_class="confirmreset"),  # gitk
        Match(wm_class="makebranch"),  # gitk
        Match(wm_class="maketag"),  # gitk
        Match(wm_class="ssh-askpass"),  # ssh-askpass
        Match(title="branchdialog"),  # gitk
        Match(title="pinentry"),  # GPG key password entry
    ]
)
auto_fullscreen = True
focus_on_window_activation = "smart"
reconfigure_screens = True

# If things like steam games want to auto-minimize themselves when losing
# focus, should we respect this or not?
auto_minimize = True

wl_input_rules = None

# XXX: Gasp! We're lying here. In fact, nobody really uses or cares about this
# string besides java UI toolkits; you can see several discussions on the
# mailing lists, GitHub issues, and other WM documentation that suggest setting
# this string if your java app doesn't work correctly. We may as well just lie
# and say that we're a working one by default.
#
# We choose LG3D to maximize irony: it is a 3D non-reparenting WM written in
# java that happens to be on java's whitelist.
wmname = "LG3D"


from sklearn import tree
#Weight in grams and bumpy-TRUE OR FALSE
features=[[150,1],[170,1],[140,0],[130,0]]
#Orange-0 and apple -1
labels=[0,0,1,1]

clf=tree.DecisionTreeClassifier()
clf.fit(features,labels)

print(clf.predict([[200,1]]))


import textwrap
from typing import Union
import hashlib

JsonDict = dict     # Словарь для хранения данных в Json формате

JsonList = list     # Список для хранения данных в Json формате

Json = Union[JsonDict, JsonList]      # Данные в Json формате

def dedent_and_strip(text: str):
    return textwrap.dedent(text).strip()

def get_sha256_hash(obj: str) -> str:
    """
    Шифрует данные преобразуя их в цифровой отпечаток длинны 256 бит
    :param obj: обьект который будет зашифрован
    """
    sha = hashlib.sha256()
    tok = obj.encode()
    sha.update(tok)
    sha_token = sha.hexdigest()
    return sha_token


import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import HorizontalBarsDrawer
import sys

if __name__ == "__main__":
    mq = qrcode.QRCode(
        version = 1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size = 40,
        border = 10,
        )
    data_content = 'hello~'
    mq.add_data(data_content)
    mq.make(fit=True)
    img = mq.make_image(fill_color="black", back_color="white",image_factory=StyledPilImage, module_drawer=HorizontalBarsDrawer())
    #make_image(embeded_image_path='image.png')
    type(img)
    img.save('qrcode_image.png')


import numpy as np
import scipy.stats as stats
from scipy.stats import multivariate_normal
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math

def getdata(filename1,classnumber):
    '''
    导入单个txt文件，将其转成列表
    :param filename1: 文件名
    :param classnumber: 将其判为哪一类，0or1
    :return: 生成好的data,labels
    '''
    f  = open(filename1)
    lines  = f.readlines()
    mansls= []
    for line in lines:
        a=line.split()
        a= [float(i) for i in a ]
        mansls.append(a)
    manslabels= [classnumber]*np.shape(mansls)[0]
    return mansls,manslabels

def canshu(data):
    '''
    用于获得贝叶斯分类所需要的参数
    :param data: 原始数据列表
    :return: 身高、体重、鞋码的均值和方差
    '''
    manmeans = np.mean(data,axis=0)
    height=manmeans[0]#身高均值
    weight = manmeans[1]#体重均值
    shoesize = manmeans[2]#鞋码均值
    m = data - manmeans#xi-均值
    mvar = np.var(data,axis=0)
    hvar,wvar,svar = mvar[0:3]#方差，男生对应正态分布的参数
    return height,weight,shoesize,hvar,wvar,svar

def zhengtaifenbu(x1,mean,var):
    '''
    一维正态分布函数
    :param x1: X值
    :param x: 均值参数
    :param z: 方差参数
    :return:x对应的Y
    '''
    return stats.norm.pdf(x1,mean,math.sqrt(var))

def byesclassfier(manmean,manvar,girlmean,girlvar,x,w1xianyan,w2xianyan):
    '''
    一维正态分布分类
    求出x为w1的后验概率，w2的则为1-w1
    贝叶斯公式的实现
    :param manmean:男生样本均值
    :param manvar:男生样本方差
    :param girlmean:女生
    :param girlvar:女生方差
    :param x: 要预测的值
    :param w1xianyan: w1的先验概率
    :param w2xianyan: w2的先验概率
    :return:
    '''
    p2 = zhengtaifenbu(x,manmean,manvar)#类条件概率p(x|w1)
    px2 = zhengtaifenbu(x,girlmean,girlvar)*w2xianyan
    px = px2+p2*w1xianyan#p(x)
    p = p2*w1xianyan/px#后验概率
    return p

def duoweiclassifier(x,mansls,girls,xianyan1,xianyan2):
    '''
    多维正态分布分类
    :param x: 输入向量
    :param mansls: 男生数据样本
    :param girls: 女生数据样本
    :param xianyan1: w1先验概率
    :param xianyan2: w2先验概率
    :return:
    '''
    mu,thu=duoweicanshu(mansls)#通过函数求得两个变量值
    mu1,thu1=duoweicanshu(girls)
    y =duoweizhengtai(x,mu,thu)#p(x|w1)
    y1 = duoweizhengtai(x,mu1,thu1)#p(x|w2)
    px = y*xianyan1+y1*xianyan2#p(x)
    p = y*xianyan1/px#后验概率
    return p

def compare(p1,p2):
    if p1>=p2:
        return True
    else:
        return False

def accurcy(predict,test):
    count = 0
    n = np.shape(test)[0]
    for i in range(n) :
        if predict[i]==test[i]:
            count = count+1
    return count/n

def duoweizhengtai(x,mu,thu):
    '''
    多维正态分布
    :param x: 输入向量
    :param mu: 均值向量
    :param thu: 方差向量
    :return: X对应的正态分布值
    '''
    return multivariate_normal.pdf(x, mean=np.array(mu.T)[0], cov=thu.T)

def duoweicanshu(testdata):
    '''
    求多维正太分布需要的参数
    :param testdata: 数据集
    :return: 均值向量，方差向量
    '''
    data1=np.mat(np.array(testdata)[:,0:2].T)#这里选的是样本里面前两个特征值，若要修改则手动将[:,0:2]变成[:,1:3]
    dimens, nums = data1.shape[:2]
    mu =  np.mean(data1, axis=1)
    k=data1-mu
    s_in = 0
    for i in range(nums):
        x = k[:, i]
        s_in += np.dot(x, x.T)
    thu= s_in/nums
    return mu,thu

def parzen(alldata,x,h):
    '''
    parzen窗的实现，用的方窗

    :param alldata:所有数据集
    :param x: 窗的距离
    :param h: 窗的高度
    :return:
    '''
    n=alldata.shape[0]
    print(n)
    a=[]
    b=0
    print(alldata[0])
    for j in range(n):#遍历每个样本
        if abs((alldata[j]-x)/h) <= 0.5:#方窗
            q=1
        else:
            q=0
        b=q+b
    return b/n

def ROC(prediction,test,trueclass,flaseclass):
    '''

    :param prediction: 预测标志
    :param test: 真实标志
    :param trueclass: 视为真的类别值
    :param flaseclass: 视为假的类别值
    :return:
    '''
    #这里写的字母表示意义与教材的不一致，因此后面计算真阳率时不能用书上的公式
    TP=0#预测真为真
    FN=0#预测真为假
    FP=0#预测假为真
    TN=0#预测假为假

    for i in range(np.shape(test)[0]):
        print(TP,TN,FN,FP)
        if prediction[i]==trueclass and test[i]==trueclass:
            TP=TP+1
        elif prediction[i]==flaseclass and test[i]==flaseclass:
            TN=TN+1
        elif prediction[i]==trueclass and test[i]==flaseclass:
            FP=FP+1
        elif prediction[i]==flaseclass and test[i]==trueclass:
            FN=FN+1

    if (FP+TN)==0:#因为样本选取可能没有假例，此时预测假为假的TN值永远为零，当FP也为零时，分母为零，报错，这样做为了防止报错，因此最好不要用只有一类的样本集
        return TP/(TP+FN),0
    return TP/(TP+FN),FP / (FP + TN)

#----------一维正太-----------------------------
# mansls,manslabels = getdata('boy.txt'，0)
# girls,girlslabels = getdata('girl.txt'，1)
# height1,weight1,shoesize1,hvar1,wvar1,svar1 = canshu(mansls)
# height,weight,shoesize,hvar,wvar,svar = canshu(girls)
# xianyan1=0.8
# xianyan2=0.2
# #贝叶斯求后验概率公式中可以看到，第一类先验概率增大，后验概率也会增大，准确率也会上升
# p1=byesclassfier(height1,hvar1,height,hvar,180,xianyan1,xianyan2)
# #p2=byesclassfier(height,hvar,height1,hvar1,180,xianyan1,xianyan2)
# # print(compare(p1,p2))
# print('p1:',p1)
#
#
# #正太分布图
# # x= np.arange(100,250,0.1)
# # y  = zhengtaifenbu(x,height1,hvar1)
# # plt.plot(x,y)
# # plt.show()
#
# #预测数据
#testdata,testlabels = getdata('boy82.txt')#如果要预测女孩，那么要将getdata里的labels设置成0
# testls =[]
# for i in range(np.shape(testdata)[0]):
#     prediction = byesclassfier(height1, hvar1, height, hvar,testdata[i][0] , xianyan1, xianyan2)
#     if prediction>0.5:
#         testls.append(1)
#     else:
#         testls.append(0)
#
# print(accurcy(testls,testlabels))#准确率

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

#----------------二维正态（互相独立）------------------------------
# mansls,manslabels = getdata('boy.txt')
# girls,girlslabels = getdata('girl.txt')
# xianyan1=0.5
# xianyan2=0.5
# x=[]
# y=[]
# z=[]
# for i in range(100,200):
#     for j in range(30,100):
#         prediction = duoweiclassifier([i,j], mansls, girls, xianyan1, xianyan2)
#         if 0.50 == round(prediction,2):
#             x.append(i)
#             y.append(j)
# plt.figure(1)
# group1=np.mat(np.array(mansls)[:,0:2].T)
# group2=np.mat(np.array(girls)[:,0:2].T)
# ax = plt.subplot(111)
# #ax.plot(range(0,200),range(0,200))
#
# ax.scatter(np.array(group1)[0], np.array(group1)[1], s=30, c='red', marker='s')  # 训练数据散点图
# ax.scatter(np.array(group2)[0], np.array(group2)[1], s=30, c='green')
# print(x)
# print(y)
# plt.plot(x,y)
# plt.show()

#-----------三维作图求交线--------------------------
#
# x=[]
# y=[]
# z=[]
# for i in range(100,200):
#     for j in range(30,100):
#         x.append(i)
#         y.append(j)
#         prediction = duoweiclassifier([i,j], mansls, girls, xianyan1, xianyan2)
#         z.append(prediction)
# print(z)
# fig = plt.figure()
# fig1 = plt.figure()
# ax = fig.add_subplot(111, projection='3d')
# ax1 = fig1.add_subplot(111, projection='3d')
# ax.plot_trisurf(x, y, z)
# ax.plot_trisurf(x, y, [0.5]*np.shape(x)[0])
# plt.show()
#-----------------------错误率作图------------------------
# x = []
# y = []
#
# for j in np.arange(0,1,0.2):
#     xianyan1=j
#     xianyan2=1-j
#     testdata,testlabels = getdata('boy82.txt')#如果要预测女孩，那么要将getdata里的labels设置成0
#     testls =[]
#     for i in range(np.shape(testdata)[0]):
#         prediction = duoweiclassifier(testdata[i][0:2],mansls,girls,xianyan1,xianyan2)
#         if prediction>0.5:
#             testls.append(1)
#         else:
#             testls.append(0)
#     x.append(j)
#     y.append(accurcy(testls,testlabels))
#
# plt.plot(x,y)
# plt.show()
#-----------------------------------------------------------------
if __name__ == '__main__':

    testdata,testlabels = getdata('boy82.txt',1)#男孩标1，女孩标0
    mansls,manslabels = getdata('boy.txt',1)
    girls,girlslabels = getdata('girl.txt',0)
    testdata.extend(girls)#为了使测试样本更全面，直接把训练集女孩跟boy82加在一起当测试集来用
    testlabels.extend([0]*np.shape(girls)[0])
    x=[]
    y=[]
    #预测测试集
    for j in np.arange(0,1,0.01):
        testls = []#预测结果labels
        for i in range(np.shape(testdata)[0]):
            prediction = duoweiclassifier(testdata[i][0:2],mansls,girls,0.5,0.5)
            if prediction>j:
                testls.append(1)
            else:
                testls.append(0)

        r1,r2=ROC(testls,testlabels,0,1)#把女孩视为真
        x.append(r1)
        y.append(r2)
    plt.xlim(0,1)
    plt.ylim(0,1)
    plt.plot(y, x)
    plt.show()

    #-------------------------parzen方窗测试-------------------------
    # mansls, manslabels = getdata('boy.txt')
    # x=[]
    # y=[]
    # alldata = np.sort(np.array(mansls)[:, 0])
    # for i in range ((np.array(mansls)[:,0]).shape[0]):
    #
    #
    #     y.append(parzen(alldata,alldata[i],10))
    #     x.append(alldata[i])
    #
    #
    # plt.plot(x,y)
    # plt.show()