from __future__ import annotations

from decimal import Decimal
from functools import lru_cache
from math import floor
from typing import List, Optional, TypeVar, Union

from kraken.base_api import KrakenBaseSpotAPI, defined, ensure_string
from kraken.spot.market import Market

Self = TypeVar("Self")

class Trade(KrakenBaseSpotAPI):
    """
    Class that implements the Kraken Trade Spot client

    :param key: Spot API public key (default: ``""``)
    :type key: str, optional
    :param secret: Spot API secret key (default: ``""``)
    :type secret: str, optional
    :param url: The URL to access the Kraken API (default: https://api.kraken.com)
    :type url: str, optional
    :param sandbox: Use the sandbox (not supported for Spot trading so far, default: ``False``)
    :type sandbox: bool, optional

    .. code-block:: python
        :linenos:
        :caption: Spot Trade: Create the trade client

        >>> from kraken.spot import Trade
        >>> trade = Trade() # unauthenticated
        >>> auth_trade = Trade(key="api-key", secret="secret-key") # authenticated

    .. code-block:: python
        :linenos:
        :caption: Spot Trade: Create the trade client as context manager

        >>> from kraken.spot import Trade
        >>> with Trade(key="api-key", secret="secret-key") as trade:
        ...     print(trade.create_order(...))

    """

    def __init__(
        self: Trade,
        key: str = "",
        secret: str = "",
        url: str = "",
    ) -> None:
        super().__init__(key=key, secret=secret, url=url)
        self.__market: Market = Market()

    def __enter__(self: Self) -> Self:
        super().__enter__()
        return self

    @ensure_string("oflags")
    def create_order(  # noqa: PLR0913 PLR0912
        self: Trade,
        ordertype: str,
        side: str,
        pair: str,
        volume: Union[str, float],
        price: Optional[Union[str, float]] = None,
        price2: Optional[Union[str, float]] = None,
        truncate: bool = False,
        trigger: Optional[str] = None,
        leverage: Optional[str] = None,
        reduce_only: Optional[bool] = False,
        stptype: Optional[str] = "cancel-newest",
        oflags: Optional[Union[str, List[str]]] = None,
        timeinforce: Optional[str] = None,
        displayvol: Optional[str] = None,
        starttm: Optional[str] = "0",
        expiretm: Optional[str] = None,
        close_ordertype: Optional[str] = None,
        close_price: Optional[Union[str, float]] = None,
        close_price2: Optional[Union[str, float]] = None,
        deadline: Optional[str] = None,
        validate: bool = False,
        userref: Optional[int] = None,
    ) -> dict:
        """
        Create a new order and place it on the market.

        Requires the ``Create and modify orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/addOrder

        :param ordertype: The kind of the order, one of: ``market``, ``limit``, ``take-profit``,
            ``stop-loss-limit``, ``take-profit-limit`` and ``settle-position``
            (see: https://support.kraken.com/hc/en-us/sections/200577136-Order-types)
        :type ordertype: str
        :param side: ``buy`` or ``sell``
        :type side: str
        :param pair: The asset to trade
        :type pair: str
        :param volume: The volume of the position to create
        :type volume: str | float
        :param price: The limit price for ``limit`` orders and the trigger price for orders with
            ``ordertype`` one of ``stop-loss``, ``stop-loss-limit``, ``take-profit``, and ``take-profit-limit``
        :type price: str | float, optional
        :param price2: The limit price for ``stop-loss-limit`` and ``take-profit-limit`` orders
            The price2 can also be set to absolut or relative changes.
                * Prefixed using ``+`` or ``-`` defines the change in the quote asset
                * Prefixed by # is the same as ``+`` and ``-`` but the sign is set automatically
                * The percentage sign ``%`` can be used to define relative changes.
        :type price2: str | float, optional
        :param truncate: If enabled: round the ``price`` and ``volume`` to Kraken's
            maximum allowed decimal places. See https://support.kraken.com/hc/en-us/articles/4521313131540
            fore more information about decimals.
        :type truncate: bool, optional
        :param trigger: What triggers the position of ``stop-loss``, ``stop-loss-limit``, ``take-profit``, and
            ``take-profit-limit`` orders. Will also be used for associated conditional close orders.
            Kraken will use ``last`` if nothing is specified.
        :type trigger: str, optional
        :param leverage: The leverage
        :type leverage: str | float, optional
        :param reduce_only: Reduce existing orders (default: ``False``)
        :type reduce_only: bool, optional
        :param stptype: Define what cancels the order, one of ``cancel-newest``,
            ``cancel-oldest``, ``cancel-both`` (default: ``cancel-newest``)
        :type stptype: str, optional
        :param oflags: Order flags like ``post``, ``fcib``, ``fciq``, ``nomp``,
            ``viqc`` (see the referenced Kraken documentation for more information)
        :type oflags: str | List[str], optional
        :param timeinforce: how long the order remains in the orderbook, one of:
            ``GTC``, ``IOC``, ``GTD`` (see the referenced Kraken documentation for more information)
        :type timeinforce: str, optional
        :param displayvol: Define how much of the volume is visible in the orderbook (iceberg)
        :type displayvol: str | float, optional
        :param starttim: Unix timestamp or seconds defining the start time (default: ``"0"``)
        :type starttim: str, optional
        :param expiretm: Unix timestamp or time in seconds defining the expiration of the order,
            (default: ``"0"`` - i.e., no expiration)
        :type expiretm: str, optional
        :param close_ordertype: Conditional close order type, one of: ``limit``, ``stop-loss``,
            ``take-profit``, ``stop-loss-limit``, ``take-profit-limit``
            (see the referenced Kraken documentation for more information)
        :type close_ordertype: str, optional
        :param close_price: Conditional close price
        :type close_price: str | float, optional
        :param close_price2: The price2 for the conditional order - see the price2 parameter description
        :type close_price2: str | float, optional
        :param deadline: RFC3339 timestamp + {0..60} seconds that defines when the matching
            engine should reject the order.
        :type deadline: str, optional
        :param validate: Validate the order without placing on the market (default: ``False``)
        :type validate: bool, optional
        :param userref: User reference id for example to group orders
        :type userref: int, optional
        :raises ValueError: If input is not correct
        :return: The transaction id
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Create a market order

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.create_order(
            ...     ordertype="market",
            ...     side="buy",
            ...     pair="XBTUSD",
            ...     volume="0.0001"
            ... )
            {
                'txid': ['TNGMNU-XQSRA-LKCWOK'],
                'descr': {
                    'order': 'buy 4.00000000 XBTUSD @ limit 23000.0'
                }
            }

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Create limit order

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.create_order(
            ...     ordertype="limit",
            ...     side="buy",
            ...     pair="XBTUSD",
            ...     volume=4,
            ...     price=23000,
            ...     expiretm=121,
            ...     displayvol=0.5,
            ...     oflags=["post", "fcib"]
            ... )
            {
                'txid': ['TPPI2H-CUZZ2-EQR2IE'],
                'descr': {
                    'order': 'buy 4.0000 XBTUSD @ limit 23000.0'
                }
            }

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Create a stop loss order

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.create_order(
            ...     ordertype="stop-loss",
            ...     pair="XBTUSD",
            ...     volume=20,
            ...     price=22000,
            ...     side="buy",
            ... )
            {
                'txid': ['THNUL1-8ZAS5-EEF3A8'],
                'descr': {
                    'order': 'buy 20.00000000 XBTUSD @ stop loss 22000.0'
                }
            }

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Create stop-loss-limit and take-profit-limit orders

            '''
            When the price hits $25000:
               1. A limit buy order will be placed at $24000 with 2x leverage.
               2. When the limit order gets closed/filled at $24000
                  The stop-loss-limit part is done and the tale-profit-limit
                  part begins.
               3. When the price hits $27000 a limit order will be placed at
                  $26800 to sell 1.2 BTC. This ensures that the asset will
                  be sold for $26800 or better.
            '''
            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> from datetime import datetime, timedelta, timezone
            >>> deadline = (
            ...     datetime.now(timezone.utc) + timedelta(seconds=20)
            ... ).isoformat()
            >>> trade.create_order(
            ...     ordertype="stop-loss-limit",
            ...     pair="XBTUSD",
            ...     side="buy",
            ...     volume=1.2,
            ...     price=24000,
            ...     price2=25000,
            ...     validate=True, # just validate the input, do not place on the market
            ...     trigger="last",
            ...     timeinforce="GTC",
            ...     leverage=4,
            ...     deadline=deadline,
            ...     close_ordertype="take-profit-limit",
            ...     close_price=27000,
            ...     close_price2=26800,
            ... )
            {
                'descr': {
                    'order': 'buy 0.00100000 XBTUSD @ stop loss 24000.0 -> limit 25000.0 with 2:1 leverage',
                    'close': 'close position @ take profit 27000.0 -> limit 26800.0'
                }
            }

            '''
            The price2 and close_price2 can also be set to absolut or relative changes.
                * Prefixed using "+" or "-" defines the change in the quote asset
                * Prefixed by # is the same as "+" and "-" but the sign is set automatically
                * The the percentage sign "%" can be used to define relative changes.
            '''
            >>> trade.create_order(
            ...     ordertype="stop-loss-limit",
            ...     pair="XBTUSD",
            ...     side="buy",
            ...     volume=1.2,
            ...     price=24000,
            ...     price2="+1000",
            ...     validate=True,
            ...     trigger="last",
            ...     timeinforce="GTC",
            ...     close_ordertype="take-profit-limit",
            ...     close_price=27000,
            ...     close_price2="#2%",
            ... )
            {
                'descr': {
                    'order': 'buy 0.00100000 XBTUSD @ stop loss 24000.0 -> limit +1000.0',
                    'close': 'close position @ take profit 27000.0 -> limit -2.0000%'
                }
            }
        """
        params: dict = {
            "ordertype": ordertype,
            "type": side,
            "pair": pair,
            "volume": volume
            if not truncate
            else self.truncate(amount=volume, amount_type="volume", pair=pair),
            "stp_type": stptype,
            "starttm": starttm,
            "validate": validate,
            "reduce_only": reduce_only,
        }

        trigger_ordertypes: tuple = (
            "stop-loss",
            "stop-loss-limit",
            "take-profit-limit",
            "take-profit-limit",
        )

        if defined(trigger):
            if ordertype not in trigger_ordertypes:
                raise ValueError(f"Cannot use trigger on ordertype {ordertype}!")
            params["trigger"] = trigger
        if defined(timeinforce):
            params["timeinforce"] = timeinforce
        if defined(expiretm):
            params["expiretm"] = str(expiretm)
        if defined(price):
            params["price"] = (
                price
                if not truncate
                else self.truncate(amount=price, amount_type="price", pair=pair)
            )
        if ordertype in ("stop-loss-limit", "take-profit-limit"):
            if not defined(price2):
                raise ValueError(
                    f"Ordertype {ordertype} requires a secondary price (price2)!",
                )
            params["price2"] = str(price2)
        elif price2 is not None:
            raise ValueError(
                f"Ordertype {ordertype} dos not allow a second price (price2)!",
            )
        if defined(leverage):
            params["leverage"] = str(leverage)
        if defined(oflags):
            params["oflags"] = oflags
        if defined(close_ordertype):
            params["close[ordertype]"] = close_ordertype
        if defined(close_price):
            params["close[price]"] = str(close_price)
        if defined(close_price2):
            params["close[price2]"] = str(close_price2)
        if defined(deadline):
            params["deadline"] = deadline
        if defined(userref):
            params["userref"] = userref
        if defined(displayvol):
            params["displayvol"] = str(displayvol)

        return self._request(  # type: ignore[return-value]
            method="POST",
            uri="/private/AddOrder",
            params=params,
        )

    def create_order_batch(
        self: Trade,
        orders: List[dict],
        pair: str,
        deadline: Optional[str] = None,
        validate: bool = False,
    ) -> dict:
        """
        Create a batch of max 15 orders for a specific asset pair.

        Requires the ``Create and modify orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/addOrderBatch

        :param orders: Dictionary of order objects (see the referenced Kraken documentation for more information)
        :type orders: List[dict]
        :param pair: Asset pair to place the orders for
        :type pair: str
        :param deadline: RFC3339 timestamp + {0..60} seconds that defines when the matching engine should reject the order.
        :type deadline: str, optional
        :param validate: Validate the orders without placing them. (default: ``False``)
        :type validate: bool, optional
        :return: Information about the placed orders
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Create a batch order

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.create_order_batch(orders=[
            ...         {
            ...             "close": {
            ...                 "ordertype": "stop-loss-limit",
            ...                 "price": 21000,
            ...                 "price2": 20000,
            ...             },
            ...             "ordertype": "limit",
            ...             "price": 25000,
            ...             "timeinforce": "GTC",
            ...             "type": "buy",
            ...             "userref": 16861348843,
            ...             "volume": 1,
            ...         },
            ...         {
            ...             "ordertype": "limit",
            ...             "price": 1000000,
            ...             "timeinforce": "GTC",
            ...             "type": "sell",
            ...             "userref": 16861348843,
            ...             "volume": 2,
            ...         },
            ...     ],
            ...     pair="BTC/USD"
            ... )
            {
                'orders': [{
                    'order': 'buy 1 BTCUSD @ limit 25000',
                    'txid': 'O5TLGX-DKKTU-WKRAZ5',
                    'close': 'close position @ stop loss 21000.0 -> limit 20000.0'
                }, {
                    'order': "sell 2 BTCUSD @ limit 1000000',
                    'txid': 'OBGFYP-XVQNL-P4GMWF'
                }]
            }
        """
        params: dict = {"orders": orders, "pair": pair, "validate": validate}
        if defined(deadline):
            params["deadline"] = deadline
        return self._request(  # type: ignore[return-value]
            method="POST",
            uri="/private/AddOrderBatch",
            params=params,
            do_json=True,
        )

    @ensure_string("oflags")
    def edit_order(  # noqa: PLR0913
        self: Trade,
        txid: str,
        pair: str,
        volume: Optional[Union[str, int, float]] = None,
        price: Optional[Union[str, int, float]] = None,
        price2: Optional[Union[str, int, float]] = None,
        truncate: bool = False,
        oflags: Optional[str] = None,
        deadline: Optional[str] = None,
        cancel_response: Optional[bool] = None,
        validate: bool = False,
        userref: Optional[int] = None,
    ) -> dict:
        """
        Edit an open order.

        Requires the ``Create and modify orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/editOrder

        :param txid: The txid of the order to edit
        :type txid: str
        :param pair: The asset pair of the order
        :type pair: str
        :param volume: Set a new volume
        :type volume: str | int | float, optional
        :param price: Set a new price
        :type price: str | int | float, optional
        :param price2: Set a new second price
        :type price2: str | int | float, optional
        :param truncate: If enabled: round the ``price`` and ``volume`` to Kraken's
            maximum allowed decimal places. See https://support.kraken.com/hc/en-us/articles/4521313131540
            fore more information about decimals.
        :type truncate: bool, optional
        :param oflags: Order flags like ``post``, ``fcib``, ``fciq``, ``nomp``,
            ``viqc`` (see the referenced Kraken documentation for more information)
        :type oflags: str | List[str], optional
        :param deadline: (see the referenced Kraken documentation for more information)
        :type deadline: string
        :param cancel_response: See the referenced Kraken documentation for more information
        :type cancel_response: bool, optional
        :param validate: Validate the order without placing on the market (default: ``False``)
        :type validate: bool, optional
        :param userref: User reference id for example to group orders
        :type userref: int
        :return: Success or failure
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Modify an order

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.edit_order(txid="OBGFYP-XVQNL-P4GMWF",
            ...     volume=0.75,
            ...     pair="XBTUSD",
            ...     price=1250000
            ... )
            {
                'status': 'ok',
                'txid': 'OFVXHJ-KPQ3B-VS7ELA',
                'originaltxid': 'OBGFYP-XVQNL-P4GMWF',
                'volume': '0.75',
                'price': '1250000',
                'orders_cancelled': 1,
                'descr': {
                    'order': 'sell 0.75 XXBTZUSD @ limit 1250000'
                }
            }
        """
        params: dict = {"txid": txid, "pair": pair, "validate": validate}
        if defined(userref):
            params["userref"] = userref
        if defined(volume):
            params["volume"] = (
                str(volume)
                if not truncate
                else self.truncate(amount=volume, amount_type="volume", pair=pair)
            )
        if defined(price):
            params["price"] = (
                str(price)
                if not truncate
                else self.truncate(amount=price, amount_type="price", pair=pair)
            )
        if defined(price2):
            params["price2"] = price2
        if defined(oflags):
            params["oflags"] = oflags
        if defined(cancel_response):
            params["cancel_response"] = cancel_response
        if defined(deadline):
            params["deadline"] = deadline
        return self._request(  # type: ignore[return-value]
            "POST",
            uri="/private/EditOrder",
            params=params,
        )

    @ensure_string("txid")
    def cancel_order(self: Trade, txid: str) -> dict:
        """
        Cancel a specific order by ``txid``. Instead of a transaction id
        a user reference id can be passed.

        Requires the ``Cancel/close orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/cancelOrder

        :param txid: Transaction id or comma delimited list of user reference ids to cancel.
        :type txid: str
        :return: Success or failure - Number of closed orders
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Cancel an order

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.cancel_order(txid="OAUHYR-YCVK6-P22G6P")
            { 'count': 1 }
        """
        return self._request(  # type: ignore[return-value]
            method="POST",
            uri="/private/CancelOrder",
            params={"txid": txid},
        )

    def cancel_all_orders(self: Trade) -> dict:
        """
        Cancel all open orders.

        Requires the ``Cancel/close orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/cancelAllOrders

        :return: Success or failure - Number of closed orders
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Cancel all open orders

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.cancel_all_orders()
            { 'count': 2 }
        """
        return self._request(  # type: ignore[return-value]
            method="POST",
            uri="/private/CancelAll",
        )

    def cancel_all_orders_after_x(self: Trade, timeout: int = 0) -> dict:
        """
        Cancel all orders after a timeout. This can be used as Dead Man's Switch.

        Requires the ``Create and modify orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/cancelAllOrdersAfter

        :param timeout: Optional The timeout in seconds, deactivate by passing the default: ``0``
        :type timeout: int, optional
        :return: Current time and trigger time
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Set the Death Man's Switch

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.cancel_all_orders_after_x(timeout=60)
            {
                'currentTime': '2023-04-06T06:51:56Z',
                'triggerTime': '2023-04-06T06:52:56Z'
            }
        """
        return self._request(  # type: ignore[return-value]
            method="POST",
            uri="/private/CancelAllOrdersAfter",
            params={"timeout": timeout},
        )

    def cancel_order_batch(self: Trade, orders: List[Union[str, int]]) -> dict:
        """
        Cancel a a list of orders by ``txid`` or ``userref``

        Requires the ``Cancel/close orders`` permission in
        the API key settings.

        - https://docs.kraken.com/rest/#operation/cancelOrderBatch

        :param orders: List of orders to cancel
        :type orders: List[str | int]
        :return: Success or failure - Number of closed orders
        :rtype: dict

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Cancel multiple orders

            >>> from kraken.spot import Trade
            >>> trade = Trade(key="api-key", secret="secret-key")
            >>> trade.cancel_order_batch(
            ...     orders=["OG5IL4-6AR7I-ZAPZEZ", "OAUHYR-YCVK6-P22G6P"]
            ... )
            { count': 2 }
        """
        return self._request(  # type: ignore[return-value]
            method="POST",
            uri="/private/CancelOrderBatch",
            params={"orders": orders},
            do_json=True,
        )

    @lru_cache()
    def truncate(
        self: Trade,
        amount: Union[Decimal, float, str],
        amount_type: str,
        pair: str,
    ) -> str:
        """
        Kraken only allows volume and price amounts to be specified with a specific number of
        decimal places, and these vary depending on the currency pair used.

        This function converts an amount of a specific type and pair to a string that uses
        the correct number of decimal places.

        - https://support.kraken.com/hc/en-us/articles/4521313131540

        This function uses caching. Run ``truncate.clear_cache()`` to clear.

        :param amount: The floating point number to represent
        :type amount: Decimal | float | str
        :param amount_type: What the amount represents. Either ``"price"`` or ``"volume"``
        :type amount_type: str
        :param pair: The currency pair the amount is in reference to.
        :type pair: str
        :raises ValueError: If the ``amount_type`` is ``price`` and the price is less
            than the costmin.
        :raises ValueError: If the ``amount_type`` is ``volume`` and the volume is
            less than the ordermin.
        :raises ValueError: If no valid ``amount_type`` was passed.
        :return: A string representation of the amount.
        :rtype: str

        .. code-block:: python
            :linenos:
            :caption: Spot Trade: Truncate

            >>> print(trade.truncate(
            ...     amount=0.123456789,
            ...     amount_type="volume",
            ...     pair="XBTUSD"
            ... ))
            0.12345678

            >>> print(trade.truncate(
            ...     amount=21123.12849829993,
            ...     amount_type="price",
            ...     pair="XBTUSD")
            ... ))
            21123.1

            >>> print(trade.truncate(
            ...     amount=0.1,
            ...     amount_type="volume",
            ...     pair="XBTUSD"
            ... ))
            0.10000000

            >>> print(trade.truncate(
            ...     amount=21123,
            ...     amount_type="price",
            ...     pair="XBTUSD"
            ... ))
            21123.0
        """
        if amount_type not in ("price", "volume"):
            raise ValueError("Amount type must be 'volume' or 'price'!")

        pair_data: dict = self.__market.get_asset_pairs(pair=pair)
        data: dict = pair_data[next(iter(pair_data))]

        pair_decimals: int = int(data["pair_decimals"])
        lot_decimals: int = int(data["lot_decimals"])

        ordermin: Decimal = Decimal(data["ordermin"])
        costmin: Decimal = Decimal(data["costmin"])

        amount = Decimal(amount)
        decimals: int

        if amount_type == "price":
            if costmin > amount:
                raise ValueError(f"Price is less than the costmin: {costmin}!")
            decimals = pair_decimals
        else:  # amount_type == "volume":
            if ordermin > amount:
                raise ValueError(f"Volume is less than the ordermin: {ordermin}!")
            decimals = lot_decimals

        amount_rounded: float = floor(float(amount) * 10**decimals) / 10**decimals
        return f"{amount_rounded:.{decimals}f}"

__all__ = ["Trade"]


import logging

from .models import LogApiRequestsModel

logger = logging.getLogger(__name__)

class LogApiRequestsMiddleware:
    """
    Writes API request history to DB
    """
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if 'api' in request.path:
            LogApiRequestsModel.objects.create(user=request.user.username, url=request.path)
            logger.debug(f'MIDDLEWARE REQUEST ADDED LOG {request.user.username}, {request.path}')
        return response


import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import LabelEncoder
from xgboost import XGBClassifier
from sklearn.metrics import classification_report, accuracy_score
import pickle

# Load the data
data = pd.read_csv("crop_data.csv")

# Encode crop names as numeric labels
le = LabelEncoder()
data["Crop"] = le.fit_transform(data["Crop"])

# Split the data into features (X) and target (y)
X = data.drop("Crop", axis=1)
y = data["Crop"]

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Set up the hyperparameter grid
param_grid = {
    "learning_rate": [0.05, 0.1, 0.15],
    "max_depth": [3, 4, 5],
    "n_estimators": [50, 100, 200],
    "reg_lambda": [0.1, 0.5, 1],
    "subsample": [0.5, 0.7, 0.9],
}

# Train an XGBoost Classifier with grid search
clf = XGBClassifier(objective="multi:softmax")
grid_search = GridSearchCV(
    clf, param_grid=param_grid, cv=3, n_jobs=-1, verbose=2, scoring="accuracy"
)
grid_search.fit(X_train, y_train)

# Print the best hyperparameters and score
print("Best hyperparameters:", grid_search.best_params_)
print("Best accuracy score:", grid_search.best_score_)

# Save the trained model
clf = grid_search.best_estimator_
with open("xgboost_classifier.pkl", "wb") as f:
    pickle.dump(clf, f)

# Make predictions on the test set
y_pred = clf.predict(X_test)

# Decode numeric labels back to crop names
y_test = le.inverse_transform(y_test)
y_pred = le.inverse_transform(y_pred)

# Evaluate the model
print("Classification report:")
print(classification_report(y_test, y_pred))

# Calculate the accuracy of the model
accuracy = accuracy_score(y_test, y_pred)
print("Overall classification accuracy:", accuracy)


# IP 확인 API로 접근해서 결과 출력하기
# 모듈 읽어 들이기
import urllib.request

# 데이터 읽어 들이기
url = "http://api.aoikujira.com/ip/ini"
# url = "ftp://api.aoikujira.com/ip/ini"
# url = "https://www.daum.net/?nil_profile=mini&nil_src=daum"
res = urllib.request.urlopen(url)
data = res.read()

# 바이너리를 문자열로 변환하기
text = data.decode("utf-8")
print(text)


from collections.abc import Callable

def mutate(par: list, p: int, d: float) -> list:
    newpar = par.copy()
    newpar[p - 1] += d
    return newpar

def hj(f: Callable[[list[float]], float], parv: list[float], maxiter: int, startstep: float, endstep: float) -> list[float]:
    p = len(parv)
    currentstep = startstep
    iter = 0
    par = parv.copy()
    while iter < maxiter:
        fold = f(par)
        fnow = fold
        for currentp in range(1, p + 1):
            mutateleft = mutate(par, currentp, -currentstep)
            fleft = f(mutateleft)
            mutateright = mutate(par, currentp, currentstep)
            fright = f(mutateright)
            if fleft < fold:
                par = mutateleft
                fnow = fleft
            elif fright < fold:
                par = mutateright
                fnow = fright

        if fold <= fnow:
            currentstep = currentstep / 2.0

        if currentstep < endstep:
            break

        iter += 1

    return par


#!/usr/bin/env python3
import os
import socket
import sys
import time

from PyQt5 import QtCore
from PyQt5.QtCore import QSize, pyqtSignal, Qt
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QTransform
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QLabel, QMainWindow, QSlider
from pynput.keyboard import Listener

import rospy
from sensor_msgs.msg import Joy
from duckietown_msgs.msg import BoolStamped

HZ = 30
SCREEN_SIZE = 300
KEY_LEFT = 'left'
KEY_RIGHT = 'right'
KEY_UP = 'up'
KEY_DOWN = 'down'
KEY_A = 'a'
KEY_Q = 'q'
KEY_S = 's'
KEY_I = 'i'
KEY_E = 'e'
KEY_P = 'p'

Keys = {
    'Key.up': KEY_UP,
    'Key.down': KEY_DOWN,
    'Key.left': KEY_LEFT,
    'Key.right': KEY_RIGHT,
    KEY_A: KEY_A,
    KEY_Q: KEY_Q,
    KEY_S: KEY_S,
    KEY_I: KEY_I,
    KEY_E: KEY_E,
    KEY_P: KEY_P
}

speed_tang = 1.0
speed_norm = 1.0
time_to_wait = 10000
estop_deadzone_secs = 0.5
e_stop = False

class ROSManager(QThread):

    def __init__(self, parent):
        QThread.__init__(self, parent)
        self._parent = parent
        rospy.init_node('virtual_joy', anonymous=False)
        self.sub_estop = rospy.Subscriber(
            "~emergency_stop",
            BoolStamped,
            self.cbEStop,
            queue_size=1
        )
        self.pub_joystick = rospy.Publisher(
            "~joy",
            Joy,
            queue_size=1
        )
        self.pub_int = rospy.Publisher(
            "~intersection_go",
            BoolStamped,
            queue_size=1
        )
        self.commands = set()
        self.standing = False
        self.estop_last_time = time.time()
        self.last_ms = 0
        self.emergency_stop = False
        self._is_shutdown = False

    def shutdown(self):
        self._is_shutdown = True

    def cbEStop(self, estop_msg):
        """
        Callback that process the received :obj:`BoolStamped` messages.
        Args:
            estop_msg (:obj:`BoolStamped`): the emergency_stop message to process.
        """
        global e_stop
        e_stop = self.emergency_stop = estop_msg.data

    def run(self):
        while not self._is_shutdown:
            ms_now = int(round(time.time() * 1000))

            try:
                if ms_now - self.last_ms > time_to_wait:
                    rospy.get_master().getSystemState()
                    self.last_ms = ms_now
            except socket.error:
                print("Error starting main loop in virtual joystick gui")

            msg = self.get_raw_message()
            force_joy_publish = self._parent.inFocus

            # Arrows events
            if KEY_LEFT in self.commands:
                msg.axes[3] += speed_norm

            if KEY_RIGHT in self.commands:
                msg.axes[3] -= speed_norm

            if KEY_UP in self.commands:
                msg.axes[1] += speed_tang

            if KEY_DOWN in self.commands:
                msg.axes[1] -= speed_tang

            if KEY_A in self.commands:
                msg.buttons[7] = 1

            if KEY_S in self.commands:
                msg.buttons[6] = 1

            if KEY_I in self.commands:
                msg.buttons[3] = 1

            if KEY_P in self.commands:
                msg_int = BoolStamped()
                msg_int.data = True
                self.pub_int.publish(msg_int)

            if KEY_E in self.commands and \
                    (time.time() - self.estop_last_time > estop_deadzone_secs):
                msg.buttons[3] = 1
                self.estop_last_time = time.time()
                force_joy_publish = True

            if KEY_Q in self.commands:
                print('Received shutdown request (Event `Q` button down).')
                self.shutdown()
                self._parent.shutdown()

            stands = (sum(map(abs, msg.axes)) == 0 and sum(map(abs, msg.buttons)) == 0)

            if not stands or not self.standing or force_joy_publish:
                self.pub_joystick.publish(msg)

            self.standing = stands

            time.sleep(1 / HZ)

    def action(self, commands):
        self.commands = commands

    @staticmethod
    def get_raw_message():
        return Joy(
            axes=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
            buttons=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        )

class MyKeyBoardThread(QThread):
    key_board_event = pyqtSignal(object)

    def __init__(self, parent):
        QThread.__init__(self, parent)
        self._parent = parent
        self.listener = None
        self.commands = set()

    def add_listener(self, listener):
        self.key_board_event.connect(listener)

    def on_press(self, key):
        if not self._parent.inFocus:
            return
        key_val = str(key)
        if len(key_val) == 3:
            key_val = key_val[1]
        if key_val in Keys:
            self.commands.add(Keys[key_val])
        self.key_board_event.emit(self.commands)

    def on_release(self, key):
        if not self._parent.inFocus:
            return
        key_val = str(key)
        if len(key_val) == 3:
            key_val = key_val[1]
        if key_val in Keys:
            self.commands.discard(Keys[key_val])
        self.key_board_event.emit(self.commands)

    def run(self):
        with Listener(on_press=self.on_press, on_release=self.on_release) as listener:
            self.listener = listener
            self.listener.join()

    def shutdown(self):
        self.listener.stop()
        self.quit()

class MainWindow(QMainWindow):

    def __init__(self, title, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        script_path = os.path.dirname(__file__)
        self.script_path = (script_path + "/") if script_path else ""
        self.setWindowTitle(title)
        self.setWindowIcon(QIcon(self.script_path + '../images/logo.png'))
        self._hasfocus = False

        # ros class
        self.ros = ROSManager(self)
        self.ros.start()
        # Key board
        self.key_board_event = MyKeyBoardThread(self)
        self.key_board_event.add_listener(self.visual_joystick)
        self.key_board_event.start()
        # UI JOYSTICK
        self.widget = Joystick(self)
        self.widget.ros_fun.connect(self.visual_joystick)
        self.resize(self.widget.pixmap.width() + 20, self.widget.pixmap.height())
        self.setFixedSize(self.widget.pixmap.width() + 20, self.widget.pixmap.height())
        ####
        self.setCentralWidget(self.widget)
        self.ros_commands = set()
        self.was_added_new_key = False
        self.widget.change_state()
        self.installEventFilter(self)

    def eventFilter(self, object, event):
        if event.type() in [QtCore.QEvent.WindowActivate, QtCore.QEvent.WindowDeactivate, QtCore.QEvent.FocusIn, QtCore.QEvent.FocusOut]:
            self._hasfocus = (event.type() in [QtCore.QEvent.WindowActivate, QtCore.QEvent.FocusIn])
            self.widget.change_state()
        # ---
        return False

    @property
    def inFocus(self):
        return self._hasfocus

    def visual_joystick(self, commands):
        active = self.isActiveWindow()
        if active and self.inFocus:
            self.widget.light_d_pad(commands)
            if commands:
                self.ros.action(commands)

    def shutdown(self):
        self.ros.shutdown()
        self.key_board_event.shutdown()
        self.close()

class Joystick(QWidget):
    ros_fun = pyqtSignal(set)

    def __init__(self, parent, *args, **kwargs):
        super(Joystick, self).__init__(*args, **kwargs)
        self._parent = parent
        # GUI stuff init
        self.label_up = None
        self.label_left = None
        self.label_down = None
        self.label_right = None
        self.label_stop = None
        self.main_label = None
        self.label_publisher = None
        self.pixmap = None
        # state init
        self.state_right = True
        self.state_left = True
        self.state_down = True
        self.state_up = True
        # ---
        script_path = os.path.dirname(__file__)
        self.script_path = (script_path + "/") if script_path else ""
        self.initUI()
        self.command = set()
        self.timer = QTimer()
        self.timer.timeout.connect(self.timer_fun)

    def timer_fun(self):
        self.ros_fun.emit(self.command)

    def on_press_timer(self):
        self.timer.start(int(1000 / HZ))

    def on_release_timer(self):
        self.timer.stop()
        self.command.clear()
        self.ros_fun.emit(set())

    def initUI(self):
        self.main_label = QLabel(self)
        self.pixmap = QPixmap(self.script_path + '../images/d-pad.png')
        self.pixmap = self.pixmap.scaled(SCREEN_SIZE, SCREEN_SIZE, Qt.KeepAspectRatio)
        self.main_label.setPixmap(self.pixmap)
        self.resize(SCREEN_SIZE, SCREEN_SIZE)
        # NOTE: if SCREEN_SIZE changed, need to change funs for buttons
        self.create_up_button()
        self.create_left_button()
        self.create_right_button()
        self.create_down_button()
        self.create_d_pad()
        self.create_slider()

    def create_slider(self):
        self.sld = QSlider(Qt.Vertical, self)
        self.sld.setFocusPolicy(Qt.NoFocus)
        self.sld.setRange(0, 100)
        self.sld.setValue(100)
        self.sld.setPageStep(1)
        self.sld.setGeometry(0, 0, SCREEN_SIZE * 2 + 20, SCREEN_SIZE)
        self.sld.valueChanged.connect(self.changeSlider)

    def changeSlider(self, value):
        global speed_norm, speed_tang
        speed_norm = value / 100
        speed_tang = value / 100

    def light_d_pad(self, commands):
        self.state_left = not (KEY_LEFT in commands)
        self.state_right = not (KEY_RIGHT in commands)
        self.state_up = not (KEY_UP in commands)
        self.state_down = not (KEY_DOWN in commands)
        self.change_state()

    def change_state(self):
        if not e_stop:
            self.label_up.setHidden(self.state_up)
            self.label_down.setHidden(self.state_down)
            self.label_left.setHidden(self.state_left)
            self.label_right.setHidden(self.state_right)
            self.label_stop.setHidden(True)
        else:
            self.label_up.setHidden(True)
            self.label_down.setHidden(True)
            self.label_left.setHidden(True)
            self.label_right.setHidden(True)
            self.label_stop.setHidden(False)
        # ---
        self.label_publisher.setHidden(not self._parent.inFocus)

    def create_d_pad(self):
        self.label_up = QLabel(self)
        self.label_left = QLabel(self)
        self.label_down = QLabel(self)
        self.label_right = QLabel(self)
        self.label_stop = QLabel(self)
        self.label_publisher = QLabel(self)
        img = QPixmap(self.script_path + '../images/d-pad-pressed.png')
        img = img.scaled(SCREEN_SIZE, SCREEN_SIZE, Qt.KeepAspectRatio)
        t = QTransform()
        self.label_up.setPixmap(img)
        t.rotate(90)
        self.label_right.setPixmap(img.transformed(t))
        t.rotate(90)
        self.label_down.setPixmap(img.transformed(t))
        t.rotate(90)
        self.label_left.setPixmap(img.transformed(t))
        # e-stop
        img = QPixmap(self.script_path + '../images/d-e-stop.png')
        img = img.scaled(SCREEN_SIZE, SCREEN_SIZE, Qt.KeepAspectRatio)
        self.label_stop.setPixmap(img)
        # publisher icon
        img = QPixmap(self.script_path + '../images/d-publisher.png')
        img = img.scaled(SCREEN_SIZE, SCREEN_SIZE, Qt.KeepAspectRatio)
        self.label_publisher.setPixmap(img)
        # ---
        self.change_state()

    def create_up_button(self):
        button_up = QPushButton("", self)
        icon = QIcon(self.script_path + '../images/up_button.jpg')
        button_up.setIcon(icon)
        size = 100
        button_up.setIconSize(QSize(size, size))
        button_up.resize(QSize(size - 25, size - 10))
        button_up.move(int(SCREEN_SIZE / 2 - SCREEN_SIZE / 8), 15)
        button_up = self.add_listener_to_button(button_up, {'up'})
        button_up.pressed.connect(lambda: self.change_command({'up'}))

    def change_command(self, cm):
        self.command = cm

    def add_listener_to_button(self, button, command=None):
        if command is None:
            command = {''}

        def fun():
            self.command = command
            self.on_press_timer()

        button.pressed.connect(fun)
        button.released.connect(self.on_release_timer)
        return button

    def create_left_button(self):
        button = QPushButton("", self)
        icon = QIcon(self.script_path + '../images/left_button.jpg')
        button.setIcon(icon)
        size = 120
        button.setIconSize(QSize(size, size))
        button.resize(QSize(size - 25, size - 25))
        button.move(10, int(SCREEN_SIZE / 2 - size / 2 + 25 / 2))
        button = self.add_listener_to_button(button, {'left'})
        button.pressed.connect(lambda: self.change_command({'left'}))

    def create_right_button(self):
        button = QPushButton("", self)
        icon = QIcon(self.script_path + '../images/right_button.jpg')
        button.setIcon(icon)
        size = 120
        button.setIconSize(QSize(size, size))
        button.resize(QSize(size - 25, size - 25))
        button.move(int(SCREEN_SIZE / 2 + size / 2 - 25 / 2), int(SCREEN_SIZE / 2 - 47))
        button = self.add_listener_to_button(button, {'right'})
        button.pressed.connect(lambda: self.change_command({'right'}))

    def create_down_button(self):
        button = QPushButton("", self)
        icon = QIcon(self.script_path + '../images/down_button.jpg')
        button.setIcon(icon)
        size = 120
        button.setIconSize(QSize(size, size))
        button.resize(QSize(size - 25, size - 25))
        button.move(int(SCREEN_SIZE / 2 - size / 2 + 13), int(SCREEN_SIZE / 2 + 47))
        button = self.add_listener_to_button(button, {'down'})
        button.pressed.connect(lambda: self.change_command({'down'}))

def print_hint():
    print("\n\n\n")
    print("Virtual Joystick for your Duckiebot")
    print("-----------------------------------")
    print("\n")
    print("[ARROW_KEYS]:    Use them to steer your Duckiebot")
    print("         [q]:    Quit the program")
    print("         [a]:    Start lane-following a.k.a. autopilot")
    print("         [s]:    Stop lane-following")
    print("         [i]:    Toggle anti-instagram")
    print("         [e]:    Toggle emergency stop")
    print("\n")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        raise Exception("No hostname specified!")
    else:
        veh_name = sys.argv[1]
    # ---
    print_hint()
    app = QApplication(sys.argv)
    app.setApplicationName(f"{veh_name} - Virtual Joystick")
    m = MainWindow(veh_name)
    m.resize(SCREEN_SIZE, SCREEN_SIZE)
    m.show()
    exit_code = app.exec_()
    m.key_board_event.terminate()
    m.key_board_event.wait()
    m.ros.terminate()
    m.ros.wait()
    sys.exit(exit_code)


"""
- Problem: https://leetcode.com/problems/unique-binary-search-trees-ii
"""
class TreeNode:
    def __init__(self, val = 0, left = None, right = None):
        self.val = val
        self.left = left
        self.right = right

def generateTreesSub(s, e):
    if s > e:
        return [None]
    res = []
    for i in range(s, e+1):
        left = generateTreesSub(s, i-1)
        right = generateTreesSub(i+1, e)

        for l in left:
            for r in right:
                root = TreeNode(i)
                root.left = l
                root.right = r
                res.append(root)
    return res

def generateTrees(n):
    return generateTreesSub(1,n)

if __name__ == "__main__":
    res = generateTrees(3)


# обработка ошибок
import os

while True:
    try:
        enter = float(input('Введите: '))
        print(100/enter)
        break
    except ValueError:  # сработает при такой ошибке
        print('Вы ввели не число!!!')
    except ZeroDivisionError:  # сработает при такой ошибке
        print('Делить на 0 нельзя!!!')
    else:
        print('Пользователь молодец.')
    finally:  # сработает в любом случае
        print('Вывод finally')

print('Все нормально\n')

url_list = ['text.txt', 'text1.txt', 'text25.txt', 'text2.txt']
list_defect = []
list_info = []

for url in url_list:
    try:
        r = open(os.path.join(f'{os.getcwd()}/examples_for_lesson_29/{url}'))
        list_info.append(r.read())
    except Exception:
        list_defect.append(url)
        continue

r.close()

print(list_info)
print(list_defect)


import random
#動詞の活用語尾
suf=[["o","as","at","amus","atis","ant"],["o","s","t","mus","tis","nt"]\
     ,["o","is","it","imus","itis","unt"],["io","is","it","imus","itis","iunt"],["o","s","t","mus","tis","unt"]]
#不定形の語尾
hutei=["are","re","ere","ere","ire"]
#[変化の種類,意味]
dic={"am{}":[0,"愛する"],"can{}":[2,"歌う"],"habe{}":[1,"持つ"],"cap{}":[3,"捕らえる"],\
     "aud{}":[4,"聞く"],"voc{}":[0,"呼ぶ"],"vide{}":[1,"見る"],"ven{}":[4,"来る"]}
q=["一人称単数","二人称単数","三人称単数","一人称複数","二人称複数","三人称複数"]
print("次の動詞を現在形、能動相で、指示された形でラテン語で書け")
while True:
    A=random.randint(0,5)
    Question=q[A]
    QWard=random.choice(list(dic.keys()))
    B=dic[QWard][0]
    QW=QWard.format(hutei[B])
    QAns=QWard.format(suf[B][A])#答え
    ans=input("{0}({1})の{2}形\n".format(QW,dic[QWard][1],Question))
    if ans==QAns:
        print("正解")
    else:
        print("残念。正解は{}".format(QAns))


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

# Author: stdrickforce (Tengyuan Fan)
# Email: <stdrickforce@gmail.com> <fantengyuan@baixing.com>

import thriftpy

from thriftpy.rpc import make_server

ping_thrift = thriftpy.load(
    "./ping.thrift", module_name='ping_thrift'
)

class Dispatcher(object):

    def __init__(self):
        self.logs = {}

    def ping(self):
        print("ping request")

    def foo(self, v_i16, v_bool, v_i32, v_str, v_list, v_set, v_map, v_st, v_st_map):
        print("foo: %d" % v_i16)
        # print(v_i16, v_bool, v_i32, v_str, v_list, v_set, v_map, v_st)
        # for key, val in v_st_map.items():
            # print(key, val)
    def foo1(self, v_i16):
        print("foo1: %d" % v_i16)
    def foo2(self, v_i16):
        print("foo2: %d" % v_i16)
        return 0

server = make_server(
    ping_thrift.Ping,
    Dispatcher(),
    "0.0.0.0",
    6000
)
print("server is listening on :6000")
server.serve()


import json
import uuid
import sqlite3
import sys
from typing import List, Optional

from goet.lib.converter.converter import converter
from goet.lib.frame.frame import Frame
from goet.tracer.base import BaseTracer

CURR_FRAME_ID: int = 0
PREV_FRAME_IDS: List[Optional[int]] = [None]

class SqlTracer(BaseTracer):
    """SqlTracer is used to record Python runtime.

    >>> with SqlTracer.trace_manager() as t:
    ...     fn()
    """

    def __init__(self, connection: sqlite3.Connection):
        self.connection = connection
        self.cursor = connection.cursor()
        self.run_id = str(uuid.uuid4())

    def __exit__(self, *exc):
        sys.settrace(None)
        val = super().__exit__(*exc)
        self.connection.commit()
        return val

    def dispatch_call(self, frame):
        PREV_FRAME_IDS.append(CURR_FRAME_ID)

    def dispatch_line(self, sysframe):
        with self.pause_tracing():
            global CURR_FRAME_ID
            CURR_FRAME_ID += 1

            frame = Frame.from_sysframe(sysframe, CURR_FRAME_ID, PREV_FRAME_IDS[-1])

            sql = f"""
            INSERT INTO frames (run_id, f_id, f_back_id, f_filename, f_funcname, f_lineno, f_locals)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            """
            args = (
                self.run_id,
                frame.f_id,
                frame.f_back_id,
                frame.f_filename,
                frame.f_funcname,
                frame.f_lineno,
                json.dumps(converter.unstructure(frame.f_locals)),
            )
            self.cursor.execute(sql, args)

            sys.settrace(self.tracefunc)

    def dispatch_return(self, frame):
        PREV_FRAME_IDS.pop()

    def dispatch_exception(self, frame):
        pass

    def dispatch_opcode(self, frame):
        pass


# N개의 수가 주어지고 : 1 <= N <= 1,000,000
# N개의 수 각 항을 자기보다 작은 수의 개수로 변환

from collections import defaultdict as dd
import sys
input = sys.stdin.readline

t = int(input())
d = dd(int)
li = list(map(int,input().split()))
order = -1
acc = 0
_li = li.copy()
_li.sort(key=lambda x : x)
for i in range(t):
    if i > 0 and _li[i] == _li[i-1]:
        d[_li[i]] = order
    else:
        order += 1
        d[_li[i]] = order
for i in li:
    print(d[i], end=' ')


from tensorflow.python.keras.saving import save

import h5py
import json
import inspect

from deepposekit.models.layers.util import ImageNormalization
from deepposekit.models.layers.convolutional import (
    UpSampling2D,
    SubPixelDownscaling,
    SubPixelUpscaling,
)
from deepposekit.models.layers.deeplabcut import ImageNetPreprocess

from deepposekit.io import TrainingGenerator
from deepposekit.models.LEAP import LEAP
from deepposekit.models.StackedDenseNet import StackedDenseNet
from deepposekit.models.StackedHourglass import StackedHourglass
from deepposekit.models.DeepLabCut import DeepLabCut

MODELS = {
    "LEAP": LEAP,
    "StackedDenseNet": StackedDenseNet,
    "StackedHourglass": StackedHourglass,
    "DeepLabCut": DeepLabCut,
}

CUSTOM_LAYERS = {
    "ImageNormalization": ImageNormalization,
    "UpSampling2D": UpSampling2D,
    "SubPixelDownscaling": SubPixelDownscaling,
    "SubPixelUpscaling": SubPixelUpscaling,
    "ImageNetPreprocess": ImageNetPreprocess,
}

def load_model(path, generator=None, augmenter=None, custom_objects=None, compile=True):
    """
    Load the model

    Example
    -------
    model = load_model('model.h5', augmenter)

    """
    if custom_objects:
        if isinstance(custom_objects, dict):
            base_objects = CUSTOM_LAYERS
            custom_objects = dict(
                list(base_objects.items()) + list(custom_objects.items())
            )
    else:
        custom_objects = CUSTOM_LAYERS

    if isinstance(path, str):
        if path.endswith(".h5") or path.endswith(".hdf5"):
            filepath = path
        else:
            raise ValueError("file must be .h5 file")
    else:
        raise TypeError("file must be type `str`")

    train_model = save.load_model(filepath, custom_objects=custom_objects, compile=compile)

    with h5py.File(filepath, "r") as h5file:
        train_generator_config = h5file.attrs.get("train_generator_config")
        if train_generator_config is None:
            raise ValueError("No data generator found in config file")
        train_generator_config = json.loads(train_generator_config.decode("utf-8"))[
            "config"
        ]

        model_config = h5file.attrs.get("pose_model_config")
        if model_config is None:
            raise ValueError("No pose model found in config file")
        model_name = json.loads(model_config.decode("utf-8"))["class_name"]
        model_config = json.loads(model_config.decode("utf-8"))["config"]

    if generator is not None:
        signature = inspect.signature(TrainingGenerator.__init__)
        keys = [key for key in signature.parameters.keys()]
        keys.remove("self")
        keys.remove("augmenter")
        keys.remove("generator")
        kwargs = {key: train_generator_config[key] for key in keys}
        kwargs["augmenter"] = augmenter
        kwargs["generator"] = generator
        train_generator = TrainingGenerator(**kwargs)
    else:
        train_generator = None

    Model = MODELS[model_name]
    signature = inspect.signature(Model.__init__)
    keys = [key for key in signature.parameters.keys()]
    keys.remove("self")
    keys.remove("train_generator")
    if "kwargs" in keys:
        keys.remove("kwargs")
    kwargs = {key: model_config[key] for key in keys}
    kwargs["train_generator"] = train_generator

    # Pass to skip initialization and manually intialize
    kwargs["skip_init"] = True

    model = Model(**kwargs)
    model.train_model = train_model
    model.__init_train_model__()
    model.__init_input__(model_config["image_shape"])

    kwargs = {}
    kwargs["output_shape"] = model_config["output_shape"]
    kwargs["keypoints_shape"] = model_config["keypoints_shape"]
    kwargs["downsample_factor"] = model_config["downsample_factor"]
    kwargs["output_sigma"] = model_config["output_sigma"]
    model.__init_predict_model__(**kwargs)

    return model


from django.shortcuts import render, get_object_or_404
from django.core import serializers
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from . import models
from . import forms

def home(request):
	context = {
		'recipes': models.Recipe.objects.all(),
		'ingredients': models.Ingredient.objects.all(),
	}
	return render(request, 'recipe_manager/index.html', context)

def edit_ingredient(request, ingredient_id):
	ingredient = models.Ingredient()
	initial_form = {}

	if ingredient_id > 0:
		ingredient = get_object_or_404(models.Ingredient, pk=ingredient_id)
		initial_form = {
			'name': ingredient.name,
			'article': ingredient.article,
			'quantity': ingredient.quantity,
			'currency': ingredient.currency,
			'currencyUnit': ingredient.currencyUnit,
			'quantityUnit': ingredient.quantityUnit,
		}

	if request.method == 'POST':
		form = forms.IngredientForm(request.POST, request.FILES)

		if form.is_valid():
			if form.cleaned_data['image']:
				ingredient.image = models.Ingredient(image=form.cleaned_data['image']).image
			if form.cleaned_data['remove_image']:
				ingredient.image = None
			ingredient.name = form.cleaned_data['name']
			ingredient.article = form.cleaned_data['article']
			ingredient.quantity = form.cleaned_data['quantity']
			ingredient.currency = form.cleaned_data['currency']
			ingredient.currencyUnit = form.cleaned_data['currencyUnit']
			ingredient.quantityUnit = form.cleaned_data['quantityUnit']
			ingredient.save()

			return HttpResponseRedirect(reverse('recipe_manager:home') )
	else:
		form = forms.IngredientForm(initial=initial_form)

	context = {
	'form': form,
	'ingredient': ingredient,
	}

	return render(request, 'recipe_manager/edit_ingredient.html', context)

def get_recipe_and_price_lists(recipe_id):
	recipe_list = models.RecipeList.objects.filter(recipe=recipe_id)
	price_list = {}

	for ingredient_list in recipe_list.all():
		price_list[ingredient_list.ingredient.id] = ingredient_list.quantity * ingredient_list.ingredient.currency / ingredient_list.ingredient.quantity

	return [recipe_list, price_list]

def add_recipe_ingredient(request):
	if request.method == 'POST':
		form_ingredient = forms.RecipeIngredientForm(request.POST)

		if form_ingredient.is_valid():
			recipe_id = request.POST.get('recipe_id', '')
			ingredient_id = request.POST.get('ingredient_id', '')
			quantity = form_ingredient.cleaned_data['quantity']
			recipe_list, _ = get_recipe_and_price_lists(recipe_id)

			if not recipe_list.filter(ingredient=ingredient_id): # can't add if ingredient is already in the recipe
				recipe_list_new = models.RecipeList()
				recipe_list_new.recipe = get_object_or_404(models.Recipe, pk=recipe_id)
				recipe_list_new.quantity = quantity
				recipe_list_new.ingredient = get_object_or_404(models.Ingredient, pk=ingredient_id)
				recipe_list_new.save()

		return HttpResponseRedirect(reverse(f'recipe_manager:edit recipe', kwargs={'recipe_id':recipe_id}))

def edit_recipe(request, recipe_id):
	recipe = models.Recipe()
	recipe_list = []
	initial_name_form = {}
	price_list = {}
	form_ingredient = forms.RecipeIngredientForm(initial={'recipe_id': recipe.id})

	if recipe_id > 0:
		recipe = get_object_or_404(models.Recipe, pk=recipe_id)
		initial_name_form = {
			'name': recipe.name,
		}
		recipe_list, price_list = get_recipe_and_price_lists(recipe_id)

	ingredients_in_recipe = [rl.ingredient.id for rl in recipe_list]

	if request.method == 'POST':
		form_name = forms.RecipeNameForm(request.POST)

		if form_name.is_valid():
			recipe.name = form_name.cleaned_data['name']
			recipe.save()

			return HttpResponseRedirect(reverse(f'recipe_manager:edit recipe', kwargs={'recipe_id':recipe.id}))
	else:
		form_name = forms.RecipeNameForm(initial=initial_name_form)

	context = {
		'form_name': form_name,
		'form_ingredient': form_ingredient,
		'recipe': recipe,
		'recipe_list': recipe_list,
		'price_list': price_list,
		'ingredients_in_recipe': ingredients_in_recipe,
		'ingredients': models.Ingredient.objects.all(),
	}

	return render(request, 'recipe_manager/edit_recipe.html', context)

# Remove endpoints
def remove_ingredient(request, ingredient_id):
	ingredient = get_object_or_404(models.Ingredient, pk=ingredient_id)
	ingredient.delete()
	return HttpResponseRedirect(reverse('recipe_manager:home') )

def remove_recipe(request, recipe_id):
	recipe = get_object_or_404(models.Recipe, pk=recipe_id)
	recipe.delete()
	return HttpResponseRedirect(reverse('recipe_manager:home'))

def remove_recipe_ingredient(request, recipe_id, ingredient_id):
	models.RecipeList.objects.filter(recipe=recipe_id, ingredient=ingredient_id).delete()
	return HttpResponseRedirect(reverse('recipe_manager:edit recipe', kwargs={'recipe_id':recipe_id}))
# Remove endpoints

# Filters
from django.template.defaulttags import register

@register.filter
def get_item(dictionary, key):
	return dictionary.get(key)

@register.filter
def sum_dict_items(dictionary):
	return sum([dictionary.get(key) for key, _ in dictionary.items()])

@register.filter
def contains(list, element):
	if not list:
		return False
	return element in list
# Filters

# Below would be used only for integration with a pure js framework like React
def db_to_json(query):
	data = serializers.serialize('json', query)
	return HttpResponse(data, content_type='application/json')

def recipes(request):
	return db_to_json(models.Recipe.objects.all())

def ingredients(request):
	return db_to_json(models.Ingredient.objects.all())

def quantity_unit(request):
	return db_to_json(models.QuantityUnit.objects.all().order_by('num_order'))

def currency_unit(request):
	return db_to_json(models.CurrencyUnit.objects.all().order_by('num_order'))


__author__ = 'alipirani'
import os
from config_settings import ConfigSectionMap
from sys import platform as _platform
from modules.log_modules import keep_logging
from modules.logging_subprocess import *

def vcftools_vcf2fasta_filter2(only_snp_filter2_vcf, out_path, analysis, reference, logger, Config):
    bgzip_cmd = "bgzip -f %s" % (only_snp_filter2_vcf)
    tabix_cmd = "tabix %s.gz" % (only_snp_filter2_vcf)
    vcftools_vcf2fasta_filter2_cmd = "cat %s | vcf-consensus %s.gz > %s_filter2_consensus.fa" % (reference, only_snp_filter2_vcf, only_snp_filter2_vcf)
    try:
        call(bgzip_cmd, logger)
        call(tabix_cmd, logger)
        call(vcftools_vcf2fasta_filter2_cmd, logger)
    except sp.CalledProcessError:
        keep_logging('- Error in vcftools vcf 2 fasta step. Exiting.', '- Error in vcftools vcf 2 fasta step. Exiting.', logger, 'exception')
        sys.exit(1)
    bash_script_file = "%s.sh" % (only_snp_filter2_vcf)
    f1=open(bash_script_file, 'w+')
    f1.write(vcftools_vcf2fasta_filter2_cmd)
    bash_command = "bash %s" % bash_script_file
    keep_logging(bash_command, bash_command, logger, 'debug')
    call(bash_command, logger)
    if _platform == "darwin":
        change_header_cmd = "sed -i '' 's/>.*/>%s/g' %s_filter2_consensus.fa" % (analysis, only_snp_filter2_vcf)
        call(change_header_cmd, logger)
        keep_logging(change_header_cmd, change_header_cmd, logger, 'debug')
    else:
        change_header_cmd = "sed -i 's/>.*/>%s/g' %s_filter2_consensus.fa" % (analysis, only_snp_filter2_vcf)
        call(change_header_cmd, logger)
        keep_logging(change_header_cmd, change_header_cmd, logger, 'debug')

def vcfstats(final_raw_vcf, out_path, analysis, logger, Config):
    bgzip_cmd = "bgzip -f -c %s > %s/%s_aln_mpileup_raw.vcf_5bp_indel_removed.vcf.gz" % (final_raw_vcf, out_path, analysis)
    tabix_cmd = "tabix -f %s.gz" % (final_raw_vcf)
    vcfstat_cmd = "vcf-stats %s.gz > %s/%s_vcf_stats" % (final_raw_vcf, out_path, analysis)
    try:
        call(bgzip_cmd, logger)
        call(tabix_cmd, logger)
        call(vcfstat_cmd, logger)
    except sp.CalledProcessError:
        keep_logging('- Error in vcftools vcf stats step. Exiting.', '- Error in vcftools vcf stats step. Exiting.', logger, 'exception')
        sys.exit(1)
    vcf_stats_file = "%s/%s_vcf_stats" % (out_path, analysis)
    return vcf_stats_file

def only_snp_filter2_vcf(gatk_filter2_final_vcf, out_path, analysis, reference):
    onlysnp_filter2_cmd = "vcftools --vcf %s --remove-indels --recode --recode-INFO-all --out %s/%s_filter2_onlysnp.vcf" % (gatk_filter2_final_vcf, out_path, analysis)
    keep_logging('Running Command: [%s]' % onlysnp_filter2_cmd, 'Running Command: [%s]' % onlysnp_filter2_cmd, logger, 'info')
    keep_logging(onlysnp_filter2_cmd, onlysnp_filter2_cmd, logger, 'debug')
    only_snp_filter2_vcf_file = "%s/%s_filter2_onlysnp.vcf.recode.vcf" % (out_path, analysis)
    return only_snp_filter2_vcf_file

def only_snp_filter1_vcf(gatk_filter1_final_vcf, out_path, analysis, reference):
    onlysnp_filter1_cmd = "vcftools --vcf %s --remove-indels --recode --recode-INFO-all --out %s/%s_filter1_onlysnp.vcf" % (gatk_filter1_final_vcf, out_path, analysis)
    keep_logging('Running Command: [%s]' % onlysnp_filter1_cmd, 'Running Command: [%s]' % onlysnp_filter1_cmd, logger, 'info')
    keep_logging(onlysnp_filter1_cmd, onlysnp_filter1_cmd, logger, 'debug')
    only_snp_filter1_vcf_file = "%s/%s_filter1_onlysnp.vcf.recode.vcf" % (out_path, analysis)
    return only_snp_filter1_vcf_file

def only_snp_raw_vcf(final_raw_vcf, out_path, analysis, reference):
    onlysnp_raw_cmd = "vcftools --vcf %s --remove-indels --recode --recode-INFO-all --out %s/%s_raw_onlysnp.vcf" % (final_raw_vcf, out_path, analysis)
    keep_logging(onlysnp_raw_cmd, onlysnp_raw_cmd, logger, 'debug')
    only_snp_raw_vcf_file = "%s/%s_raw_onlysnp.vcf.recode.vcf" % (out_path, analysis)
    return only_snp_raw_vcf_file

def vcftools_vcf2fasta_filter1(only_snp_filter1_vcf_file, out_path, analysis, reference):
    bgzip_cmd = "bgzip -f %s" % (only_snp_filter1_vcf_file)
    keep_logging(bgzip_cmd, bgzip_cmd, logger, 'debug')
    try:
        call(bgzip_cmd, logger)
    except sp.CalledProcessError:
        keep_logging('Error in vcf2fasta step', 'Error in vcf2fasta step', logger, 'exception')
        sys.exit(1)
    tabix_cmd = "tabix %s.gz" % (only_snp_filter1_vcf_file)
    keep_logging(tabix_cmd, tabix_cmd, logger, 'debug')
    vcftools_vcf2fasta_filter1_cmd = "cat %s | vcf-consensus %s.gz > %s/%s_filter1_consensus.fa" % (reference, only_snp_filter1_vcf_file, out_path, analysis)
    keep_logging('Running Command: [%s]' % vcftools_vcf2fasta_filter1_cmd, 'Running Command: [%s]' % vcftools_vcf2fasta_filter1_cmd, logger, 'info')
    if _platform == "darwin":
        change_header_cmd = "sed -i '' 's/>.*/>%s/g' %s/%s_filter1_consensus.fa" % (analysis, out_path, analysis)
        keep_logging(change_header_cmd, change_header_cmd, logger, 'debug')
    else:
        change_header_cmd = "sed -i 's/>.*/>%s/g' %s/%s_filter1_consensus.fa" % (analysis, out_path, analysis)
        keep_logging(change_header_cmd, change_header_cmd, logger, 'debug')


import json
import boto3
import os

dynamodb=boto3.client('dynamodb')

def handler(event, context):
    connectionId=event['requestContext']['connectionId']

    dynamodb.delete_item(
        TableName=os.environ.get('TABLE_NAME'),
        Key={'socketId':{'S':connectionId}}
    )
    return {}


import requests
import db_connector
import sys

user_id = sys.argv[1]

#  Creating the user with a POST
post = requests.post(f'http://127.0.0.1:5000/data/{user_id}', json={"user_name": "oriel"})
if post.ok:
    print('User Created:', post.json())
else:
        print('response code 500:', post.json())

# Checking the user exists with a GET
try:
    get = requests.get(f'http://127.0.0.1:5000/data/{user_id}')
    if get.ok:
        print('User verified and exists:', get.json())
    else:
        print('response code 500:', get.json())
except:
    print('No access to server with GET')

#  Login to the DB and pull the name of the user
# print(add_user(user_id))