#
# Copyright 2015 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cimport numpy as np
import numpy as np
import pandas as pd
cimport cython
from cpython cimport bool

cdef np.int64_t _nanos_in_minute = 60000000000
NANOS_IN_MINUTE = _nanos_in_minute

cpdef enum:
    BAR = 0
    SESSION_START = 1
    SESSION_END = 2
    MINUTE_END = 3
    BEFORE_TRADING_START_BAR = 4

cdef class MinuteSimulationClock:
    cdef bool minute_emission
    cdef np.int64_t[:] market_opens_nanos, market_closes_nanos, bts_nanos, \
        sessions_nanos
    cdef dict minutes_by_session

    def __init__(self,
                 sessions,
                 market_opens,
                 market_closes,
                 before_trading_start_minutes,
                 minute_emission=False):
        self.minute_emission = minute_emission

        self.market_opens_nanos = market_opens.values.astype(np.int64)
        self.market_closes_nanos = market_closes.values.astype(np.int64)
        self.sessions_nanos = sessions.values.astype(np.int64)
        self.bts_nanos = before_trading_start_minutes.values.astype(np.int64)

        self.minutes_by_session = self.calc_minutes_by_session()

    @cython.boundscheck(False)
    @cython.wraparound(False)
    cdef dict calc_minutes_by_session(self):
        cdef dict minutes_by_session
        cdef int session_idx
        cdef np.int64_t session_nano
        cdef np.ndarray[np.int64_t, ndim=1] minutes_nanos

        minutes_by_session = {}
        for session_idx, session_nano in enumerate(self.sessions_nanos):
            minutes_nanos = np.arange(
                self.market_opens_nanos[session_idx],
                self.market_closes_nanos[session_idx] + _nanos_in_minute,
                _nanos_in_minute
            )
            minutes_by_session[session_nano] = pd.to_datetime(
                minutes_nanos, utc=True, box=True
            )
        return minutes_by_session

    def __iter__(self):
        minute_emission = self.minute_emission

        for idx, session_nano in enumerate(self.sessions_nanos):
            yield pd.Timestamp(session_nano, tz='UTC'), SESSION_START

            bts_minute = pd.Timestamp(self.bts_nanos[idx], tz='UTC')
            regular_minutes = self.minutes_by_session[session_nano]

            if bts_minute > regular_minutes[-1]:
                # before_trading_start is after the last close,
                # so don't emit it
                for minute, evt in self._get_minutes_for_list(
                    regular_minutes,
                    minute_emission
                ):
                    yield minute, evt
            else:
                # we have to search anew every session, because there is no
                # guarantee that any two session start on the same minute
                bts_idx = regular_minutes.searchsorted(bts_minute)

                # emit all the minutes before bts_minute
                for minute, evt in self._get_minutes_for_list(
                    regular_minutes[0:bts_idx],
                    minute_emission
                ):
                    yield minute, evt

                yield bts_minute, BEFORE_TRADING_START_BAR

                # emit all the minutes after bts_minute
                for minute, evt in self._get_minutes_for_list(
                    regular_minutes[bts_idx:],
                    minute_emission
                ):
                    yield minute, evt

            yield regular_minutes[-1], SESSION_END

    def _get_minutes_for_list(self, minutes, minute_emission):
        for minute in minutes:
            yield minute, BAR
            if minute_emission:
                yield minute, MINUTE_END
