# -*- coding: utf-8 -*-
# 版权所有 2019 深圳米筐科技有限公司（下称“米筐科技”）
#
# 除非遵守当前许可，否则不得使用本软件。
#
#     * 非商业用途（非商业用途指个人出于非商业目的使用本软件，或者高校、研究所等非营利机构出于教育、科研等目的使用本软件）：
#         遵守 Apache License 2.0（下称“Apache 2.0 许可”），
#         您可以在以下位置获得 Apache 2.0 许可的副本：http://www.apache.org/licenses/LICENSE-2.0。
#         除非法律有要求或以书面形式达成协议，否则本软件分发时需保持当前许可“原样”不变，且不得附加任何条件。
#
#     * 商业用途（商业用途指个人出于任何商业目的使用本软件，或者法人或其他组织出于任何目的使用本软件）：
#         未经米筐科技授权，任何个人不得出于任何商业目的使用本软件（包括但不限于向第三方提供、销售、出租、出借、转让本软件、
#         本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务），任何法人或其他组织不得出于任何目的使用本软件，
#         否则米筐科技有权追究相应的知识产权侵权责任。
#         在此前提下，对本软件的使用同样需要遵守 Apache 2.0 许可，Apache 2.0 许可与本许可冲突之处，以本许可为准。
#         详细的授权流程，请联系 public@ricequant.com 获取。

import time
from decimal import Decimal

import numpy as np

from rqalpha.const import ORDER_STATUS, ORDER_TYPE, SIDE, POSITION_EFFECT, POSITION_DIRECTION, ALGO
from rqalpha.utils import id_gen, decimal_rounding_floor, get_position_direction
from rqalpha.utils.repr import property_repr, properties
from rqalpha.utils.logger import user_system_log
from rqalpha.environment import Environment


class Order(object):

    order_id_gen = id_gen(int(time.time()) * 10000)

    __repr__ = property_repr

    def __init__(self):
        self._order_id = None
        self._secondary_order_id = None
        self._calendar_dt = None
        self._trading_dt = None
        self._quantity = None
        self._order_book_id = None
        self._side = None
        self._position_effect = None
        self._message = None
        self._filled_quantity = None
        self._status = None
        self._frozen_price = None
        self._init_frozen_cash = None
        self._type = None
        self._avg_price = None
        self._transaction_cost = None
        self._style = None
        self._kwargs = {}

    @staticmethod
    def _str_to_enum(enum_class, s):
        return enum_class.__members__[s]

    def get_state(self):
        return {
            'order_id': self._order_id,
            'secondary_order_id': self._secondary_order_id,
            'calendar_dt': self._calendar_dt,
            'trading_dt': self._trading_dt,
            'order_book_id': self._order_book_id,
            'quantity': self._quantity,
            'side': self._side,
            'position_effect': self._position_effect,
            'message': self._message,
            'filled_quantity': self._filled_quantity,
            'status': self._status,
            'frozen_price': self._frozen_price,
            'type': self._type,
            'transaction_cost': self._transaction_cost,
            'avg_price': self._avg_price,
            'kwargs': self._kwargs,
        }

    def set_state(self, d):
        self._order_id = d['order_id']
        if 'secondary_order_id' in d:
            self._secondary_order_id = d['secondary_order_id']
        self._calendar_dt = d['calendar_dt']
        self._trading_dt = d['trading_dt']
        self._order_book_id = d['order_book_id']
        self._quantity = d['quantity']
        self._side = SIDE[d["side"]]
        self._position_effect = POSITION_EFFECT[d["position_effect"]] if d["position_effect"] else None
        self._message = d['message']
        self._filled_quantity = d['filled_quantity']
        self._status = ORDER_STATUS[d["status"]]
        self._frozen_price = d['frozen_price']
        self._type = ORDER_TYPE[d["type"]]
        self._transaction_cost = d['transaction_cost']
        self._avg_price = d['avg_price']
        self._kwargs = d['kwargs']

    @classmethod
    def __from_create__(cls, order_book_id, quantity, side, style, position_effect, **kwargs):
        env = Environment.get_instance()
        order = cls()
        order._order_id = next(order.order_id_gen)
        order._calendar_dt = env.calendar_dt
        order._trading_dt = env.trading_dt
        order._quantity = quantity
        order._order_book_id = order_book_id
        order._side = side
        order._position_effect = position_effect
        order._message = ""
        order._filled_quantity = 0
        order._status = ORDER_STATUS.PENDING_NEW
        order._style = style
        if isinstance(style, LimitOrder):
            if env.config.base.round_price:
                tick_size = env.data_proxy.get_tick_size(order_book_id)
                style.round_price(tick_size)
            order._frozen_price = style.get_limit_price()
            order._type = ORDER_TYPE.LIMIT
        elif isinstance(style, ALGO_ORDER_STYLES):
            algo_price, _ = env.data_proxy.get_algo_bar(order_book_id, style, env.calendar_dt)
            order._frozen_price = env.get_last_price(order_book_id) if np.isnan(algo_price) else algo_price
            order._type = ORDER_TYPE.ALGO
        else:
            order._frozen_price = env.get_last_price(order_book_id)
            order._type = ORDER_TYPE.MARKET
        order._avg_price = 0
        order._transaction_cost = 0
        order._kwargs = kwargs
        return order

    @property
    def order_id(self):
        # type: () -> int
        """
        [int] 唯一标识订单的id
        """
        return self._order_id

    @property
    def secondary_order_id(self):
        """
        [str] 实盘交易中交易所产生的订单ID
        """
        return self._secondary_order_id

    @property
    def trading_datetime(self):
        """
        [datetime.datetime] 订单的交易日期（对应期货夜盘）
        """
        return self._trading_dt

    @property
    def datetime(self):
        """
        [datetime.datetime] 订单创建时间
        """
        return self._calendar_dt

    @property
    def quantity(self):
        """
        [int] 订单数量
        """
        if np.isnan(self._quantity):
            raise RuntimeError("Quantity of order {} is not supposed to be nan.".format(self.order_id))
        return self._quantity

    @property
    def unfilled_quantity(self):
        """
        [int] 订单未成交数量
        """
        return self.quantity - self.filled_quantity

    @property
    def order_book_id(self):
        """
        [str] 合约代码
        """
        return self._order_book_id

    @property
    def side(self):
        # type: () -> SIDE
        """
        [SIDE] 订单方向
        """
        return self._side

    @property
    def position_effect(self):
        """
        [POSITION_EFFECT] 订单开平（期货专用）
        """
        if self._position_effect is None:
            if self._side == SIDE.BUY:
                return POSITION_EFFECT.OPEN
            else:
                return POSITION_EFFECT.CLOSE
        return self._position_effect

    @property
    def position_direction(self):
        # type: () -> POSITION_DIRECTION
        return get_position_direction(self._side, self._position_effect)

    @property
    def message(self):
        """
        [str] 信息。比如拒单时候此处会提示拒单原因
        """
        return self._message

    @property
    def filled_quantity(self):
        """
        [int] 订单已成交数量
        """
        if np.isnan(self._filled_quantity):
            raise RuntimeError("Filled quantity of order {} is not supposed to be nan.".format(self.order_id))
        return self._filled_quantity

    @property
    def status(self):
        """
        [ORDER_STATUS] 订单状态
        """
        return self._status

    @property
    def price(self):
        """
        [float] 订单价格，只有在订单类型为'限价单'的时候才有意义
        """
        return 0 if self.type == ORDER_TYPE.MARKET else self.frozen_price

    @property
    def type(self):
        """
        [ORDER_TYPE] 订单类型
        """
        return self._type

    @property
    def style(self):
        """
        [ORDER_STYLE] 订单类型
        """
        return self._style

    @property
    def avg_price(self):
        """
        [float] 成交均价
        """
        return self._avg_price

    @property
    def transaction_cost(self):
        """
        [float] 费用
        """
        return self._transaction_cost

    @property
    def frozen_price(self):
        """
        [float] 冻结价格
        """
        if np.isnan(self._frozen_price):
            raise RuntimeError("Frozen price of order {} is not supposed to be nan.".format(self.order_id))
        return self._frozen_price

    @property
    def init_frozen_cash(self):
        """
        [float] 冻结资金
        """
        if np.isnan(self._init_frozen_cash):
            raise RuntimeError("Frozen cash of order {} is not supposed to be nan.".format(self.order_id))
        return self._init_frozen_cash

    @property
    def kwargs(self):
        return self._kwargs

    def __getattr__(self, item):
        try:
            return self.__dict__["_kwargs"][item]
        except KeyError:
            raise AttributeError("'{}' object has no attribute '{}'".format(self.__class__.__name__, item))

    def is_final(self):
        return self._status not in {
            ORDER_STATUS.PENDING_NEW,
            ORDER_STATUS.ACTIVE,
            ORDER_STATUS.PENDING_CANCEL
        }

    def is_active(self):
        return self.status == ORDER_STATUS.ACTIVE

    def active(self):
        self._status = ORDER_STATUS.ACTIVE

    def set_pending_cancel(self):
        if not self.is_final():
            self._status = ORDER_STATUS.PENDING_CANCEL

    def fill(self, trade):
        quantity = trade.last_quantity
        assert self.filled_quantity + quantity <= self.quantity
        new_quantity = self._filled_quantity + quantity
        self._transaction_cost += trade.commission + trade.tax
        if trade.position_effect != POSITION_EFFECT.MATCH:
            self._avg_price = (self._avg_price * self._filled_quantity + trade.last_price * quantity) / new_quantity
        self._filled_quantity = new_quantity
        if self.unfilled_quantity == 0:
            self._status = ORDER_STATUS.FILLED

    def mark_rejected(self, reject_reason):
        if not self.is_final():
            self._message = reject_reason
            self._status = ORDER_STATUS.REJECTED
            user_system_log.warn(reject_reason)

    def mark_cancelled(self, cancelled_reason, user_warn=True):
        if not self.is_final():
            self._message = cancelled_reason
            self._status = ORDER_STATUS.CANCELLED
            if user_warn:
                user_system_log.warn(cancelled_reason)

    def set_frozen_price(self, value):
        self._frozen_price = value

    def set_frozen_cash(self, value):
        self._init_frozen_cash = value

    def set_secondary_order_id(self, secondary_order_id):
        self._secondary_order_id = str(secondary_order_id)

    def __simple_object__(self):
        return properties(self)


class OrderStyle(object):
    def get_limit_price(self):
        raise NotImplementedError


class AlgoOrder(OrderStyle):
    __repr__ = ORDER_TYPE.ALGO.__repr__

    def __init__(self, start_min, end_min):
        self.start_min = start_min
        self.end_min = end_min

    def get_limit_price(self):
        return None


class TWAPOrder(AlgoOrder):
    TYPE = ALGO.TWAP
    __repr__ = ALGO.TWAP.__repr__


class VWAPOrder(AlgoOrder):
    TYPE = ALGO.VWAP
    __repr__ = ALGO.VWAP.__repr__


class MarketOrder(OrderStyle):
    __repr__ = ORDER_TYPE.MARKET.__repr__

    def get_limit_price(self):
        return None


class LimitOrder(OrderStyle):
    __repr__ = ORDER_TYPE.LIMIT.__repr__

    def __init__(self, limit_price):
        self.limit_price = float(limit_price)

    def get_limit_price(self):
        return self.limit_price

    def round_price(self, tick_size):
        if tick_size:
            with decimal_rounding_floor():
                limit_price_decimal = Decimal("{:.4f}".format(self.limit_price))
                tick_size_decimal = Decimal("{:.4f}".format(tick_size))
                self.limit_price = float((limit_price_decimal / tick_size_decimal).to_integral() * tick_size_decimal)
        else:
            user_system_log.warn('Invalid tick size: {}'.format(tick_size))


ALGO_ORDER_STYLES = (VWAPOrder, TWAPOrder)
ALL_ORDER_STYPES = (LimitOrder, MarketOrder, TWAPOrder, VWAPOrder)
