from datetime import datetime
import re

import numpy as np

from pandas.tseries.offsets import DateOffset
from pandas.util.decorators import cache_readonly
import pandas.tseries.offsets as offsets
import pandas.core.common as com
import pandas.lib as lib

class FreqGroup(object):
    FR_ANN = 1000
    FR_QTR = 2000
    FR_MTH = 3000
    FR_WK = 4000
    FR_BUS = 5000
    FR_DAY = 6000
    FR_HR = 7000
    FR_MIN = 8000
    FR_SEC = 9000
    FR_UND = -10000

def get_freq_group(freq):
    if isinstance(freq, basestring):
        base, mult = get_freq_code(freq)
        freq = base
    return (freq // 1000) * 1000

def get_freq(freq):
    if isinstance(freq, basestring):
        base, mult = get_freq_code(freq)
        freq = base
    return freq

def get_freq_code(freqstr):
    """

    Parameters
    ----------

    Returns
    -------
    """
    if isinstance(freqstr, DateOffset):
        freqstr = (get_offset_name(freqstr), freqstr.n)

    if isinstance(freqstr, tuple):
        if (com.is_integer(freqstr[0]) and
            com.is_integer(freqstr[1])):
            #e.g., freqstr = (2000, 1)
            return freqstr
        else:
            #e.g., freqstr = ('T', 5)
            try:
                code = _period_str_to_code(freqstr[0])
                stride = freqstr[1]
            except:
                code = _period_str_to_code(freqstr[1])
                stride = freqstr[0]
            return code, stride

    if com.is_integer(freqstr):
        return (freqstr, 1)

    base, stride = _base_and_stride(freqstr)
    code = _period_str_to_code(base)

    return code, stride


def _get_freq_str(base, mult=1):
    code = _reverse_period_code_map.get(base)
    if mult == 1:
        return code
    return str(mult) + code


#----------------------------------------------------------------------
# Offset names ("time rules") and related functions


from pandas.tseries.offsets import (Day, BDay, Hour, Minute, Second, Milli,
                                    Week, Micro, MonthEnd, MonthBegin,
                                    BMonthBegin, BMonthEnd, YearBegin, YearEnd,
                                    BYearBegin, BYearEnd, QuarterBegin,
                                    QuarterEnd, BQuarterBegin, BQuarterEnd)

_offset_map = {
    'D'     : Day(),
    'B'     : BDay(),
    'H'     : Hour(),
    'T'     : Minute(),
    'S'     : Second(),
    'L'     : Milli(),
    'U'     : Micro(),
    None    : None,

    # Monthly - Calendar
    'M'      : MonthEnd(),
    'MS'     : MonthBegin(),

    # Monthly - Business
    'BM'     : BMonthEnd(),
    'BMS'    : BMonthBegin(),

    # Annual - Calendar
    'A-JAN' : YearEnd(month=1),
    'A-FEB' : YearEnd(month=2),
    'A-MAR' : YearEnd(month=3),
    'A-APR' : YearEnd(month=4),
    'A-MAY' : YearEnd(month=5),
    'A-JUN' : YearEnd(month=6),
    'A-JUL' : YearEnd(month=7),
    'A-AUG' : YearEnd(month=8),
    'A-SEP' : YearEnd(month=9),
    'A-OCT' : YearEnd(month=10),
    'A-NOV' : YearEnd(month=11),
    'A-DEC' : YearEnd(month=12),
    'A'     : YearEnd(month=12),

    # Annual - Calendar (start)
    'AS-JAN' : YearBegin(month=1),
    'AS'     : YearBegin(month=1),
    'AS-FEB' : YearBegin(month=2),
    'AS-MAR' : YearBegin(month=3),
    'AS-APR' : YearBegin(month=4),
    'AS-MAY' : YearBegin(month=5),
    'AS-JUN' : YearBegin(month=6),
    'AS-JUL' : YearBegin(month=7),
    'AS-AUG' : YearBegin(month=8),
    'AS-SEP' : YearBegin(month=9),
    'AS-OCT' : YearBegin(month=10),
    'AS-NOV' : YearBegin(month=11),
    'AS-DEC' : YearBegin(month=12),

    # Annual - Business
    'BA-JAN' : BYearEnd(month=1),
    'BA-FEB' : BYearEnd(month=2),
    'BA-MAR' : BYearEnd(month=3),
    'BA-APR' : BYearEnd(month=4),
    'BA-MAY' : BYearEnd(month=5),
    'BA-JUN' : BYearEnd(month=6),
    'BA-JUL' : BYearEnd(month=7),
    'BA-AUG' : BYearEnd(month=8),
    'BA-SEP' : BYearEnd(month=9),
    'BA-OCT' : BYearEnd(month=10),
    'BA-NOV' : BYearEnd(month=11),
    'BA-DEC' : BYearEnd(month=12),
    'BA'     : BYearEnd(month=12),

    # Annual - Business (Start)
    'BAS-JAN' : BYearBegin(month=1),
    'BAS'     : BYearBegin(month=1),
    'BAS-FEB' : BYearBegin(month=2),
    'BAS-MAR' : BYearBegin(month=3),
    'BAS-APR' : BYearBegin(month=4),
    'BAS-MAY' : BYearBegin(month=5),
    'BAS-JUN' : BYearBegin(month=6),
    'BAS-JUL' : BYearBegin(month=7),
    'BAS-AUG' : BYearBegin(month=8),
    'BAS-SEP' : BYearBegin(month=9),
    'BAS-OCT' : BYearBegin(month=10),
    'BAS-NOV' : BYearBegin(month=11),
    'BAS-DEC' : BYearBegin(month=12),

    # Quarterly - Calendar
    # 'Q'     : QuarterEnd(startingMonth=3),
    'Q-JAN' : QuarterEnd(startingMonth=1),
    'Q-FEB' : QuarterEnd(startingMonth=2),
    'Q-MAR' : QuarterEnd(startingMonth=3),
    'Q-APR' : QuarterEnd(startingMonth=4),
    'Q-MAY' : QuarterEnd(startingMonth=5),
    'Q-JUN' : QuarterEnd(startingMonth=6),
    'Q-JUL' : QuarterEnd(startingMonth=7),
    'Q-AUG' : QuarterEnd(startingMonth=8),
    'Q-SEP' : QuarterEnd(startingMonth=9),
    'Q-OCT' : QuarterEnd(startingMonth=10),
    'Q-NOV' : QuarterEnd(startingMonth=11),
    'Q-DEC' : QuarterEnd(startingMonth=12),

    # Quarterly - Calendar (Start)
    # 'QS'     : QuarterBegin(startingMonth=1),
    'QS-JAN' : QuarterBegin(startingMonth=1),
    'QS-FEB' : QuarterBegin(startingMonth=2),
    'QS-MAR' : QuarterBegin(startingMonth=3),
    'QS-APR' : QuarterBegin(startingMonth=4),
    'QS-MAY' : QuarterBegin(startingMonth=5),
    'QS-JUN' : QuarterBegin(startingMonth=6),
    'QS-JUL' : QuarterBegin(startingMonth=7),
    'QS-AUG' : QuarterBegin(startingMonth=8),
    'QS-SEP' : QuarterBegin(startingMonth=9),
    'QS-OCT' : QuarterBegin(startingMonth=10),
    'QS-NOV' : QuarterBegin(startingMonth=11),
    'QS-DEC' : QuarterBegin(startingMonth=12),

    # Quarterly - Business
    'BQ-JAN' : BQuarterEnd(startingMonth=1),
    'BQ-FEB' : BQuarterEnd(startingMonth=2),
    'BQ-MAR' : BQuarterEnd(startingMonth=3),

    # 'BQ'     : BQuarterEnd(startingMonth=3),
    'BQ-APR' : BQuarterEnd(startingMonth=4),
    'BQ-MAY' : BQuarterEnd(startingMonth=5),
    'BQ-JUN' : BQuarterEnd(startingMonth=6),
    'BQ-JUL' : BQuarterEnd(startingMonth=7),
    'BQ-AUG' : BQuarterEnd(startingMonth=8),
    'BQ-SEP' : BQuarterEnd(startingMonth=9),
    'BQ-OCT' : BQuarterEnd(startingMonth=10),
    'BQ-NOV' : BQuarterEnd(startingMonth=11),
    'BQ-DEC' : BQuarterEnd(startingMonth=12),

    # Quarterly - Business (Start)
    'BQS-JAN' : BQuarterBegin(startingMonth=1),
    'BQS'     : BQuarterBegin(startingMonth=1),
    'BQS-FEB' : BQuarterBegin(startingMonth=2),
    'BQS-MAR' : BQuarterBegin(startingMonth=3),
    'BQS-APR' : BQuarterBegin(startingMonth=4),
    'BQS-MAY' : BQuarterBegin(startingMonth=5),
    'BQS-JUN' : BQuarterBegin(startingMonth=6),
    'BQS-JUL' : BQuarterBegin(startingMonth=7),
    'BQS-AUG' : BQuarterBegin(startingMonth=8),
    'BQS-SEP' : BQuarterBegin(startingMonth=9),
    'BQS-OCT' : BQuarterBegin(startingMonth=10),
    'BQS-NOV' : BQuarterBegin(startingMonth=11),
    'BQS-DEC' : BQuarterBegin(startingMonth=12),

    # Weekly
    'W-MON' : Week(weekday=0),
    'W-TUE' : Week(weekday=1),
    'W-WED' : Week(weekday=2),
    'W-THU' : Week(weekday=3),
    'W-FRI' : Week(weekday=4),
    'W-SAT' : Week(weekday=5),
    'W-SUN' : Week(weekday=6),

}

_offset_to_period_map = {
    'WEEKDAY' : 'D',
    'EOM' : 'M',
    'BM' : 'M',
    'BQS' : 'Q',
    'QS' : 'Q',
    'BQ' : 'Q',
    'BA' : 'A',
    'AS' : 'A',
    'BAS' : 'A',
    'MS' : 'M',
    'D' : 'D',
    'B' : 'B',
    'T' : 'T',
    'S' : 'S',
    'H' : 'H',
    'Q' : 'Q',
    'A' : 'A',
    'W' : 'W',
    'M' : 'M'
}

need_suffix = ['QS', 'BQ', 'BQS', 'AS', 'BA', 'BAS']
_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP',
           'OCT', 'NOV', 'DEC']
for __prefix in need_suffix:
    for _m in _months:
        _offset_to_period_map['%s-%s' % (__prefix, _m)] = \
            _offset_to_period_map[__prefix]
for __prefix in ['A', 'Q']:
    for _m in _months:
        _alias = '%s-%s' % (__prefix, _m)
        _offset_to_period_map[_alias] = _alias

_days = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']
for _d in _days:
    _offset_to_period_map['W-%s' % _d] = 'W-%s' % _d

def get_period_alias(offset_str):
    """ alias to closest period strings BQ->Q etc"""
    return _offset_to_period_map.get(offset_str, None)

_rule_aliases = {
    # Legacy rules that will continue to map to their original values
    # essentially for the rest of time

    'WEEKDAY': 'B',
    'EOM': 'BM',

    'W@MON': 'W-MON',
    'W@TUE': 'W-TUE',
    'W@WED': 'W-WED',
    'W@THU': 'W-THU',
    'W@FRI': 'W-FRI',
    'W@SAT': 'W-SAT',
    'W@SUN': 'W-SUN',
    'W': 'W-SUN',

    'Q@JAN': 'BQ-JAN',
    'Q@FEB': 'BQ-FEB',
    'Q@MAR': 'BQ-MAR',
    'Q' : 'Q-DEC',

    'A@JAN' : 'BA-JAN',
    'A@FEB' : 'BA-FEB',
    'A@MAR' : 'BA-MAR',
    'A@APR' : 'BA-APR',
    'A@MAY' : 'BA-MAY',
    'A@JUN' : 'BA-JUN',
    'A@JUL' : 'BA-JUL',
    'A@AUG' : 'BA-AUG',
    'A@SEP' : 'BA-SEP',
    'A@OCT' : 'BA-OCT',
    'A@NOV' : 'BA-NOV',
    'A@DEC' : 'BA-DEC',

    # lite aliases
    'Min': 'T',
    'min': 'T',
    'ms': 'L',
    'us': 'U'
}

for _i, _weekday in enumerate(['MON', 'TUE', 'WED', 'THU', 'FRI']):
    for _iweek in xrange(4):
        _name = 'WOM-%d%s' % (_iweek + 1, _weekday)
        _offset_map[_name] = offsets.WeekOfMonth(week=_iweek, weekday=_i)
        _rule_aliases[_name.replace('-', '@')] = _name

_legacy_reverse_map = dict((v, k) for k, v in _rule_aliases.iteritems())

# for helping out with pretty-printing and name-lookups

_offset_names = {}
for name, offset in _offset_map.iteritems():
    if offset is None:
        continue
    offset.name = name
    _offset_names[offset] = name


def inferTimeRule(index):
    from pandas.tseries.index import DatetimeIndex
    import warnings
    warnings.warn("This method is deprecated, use infer_freq or inferred_freq"
                  " attribute of DatetimeIndex", FutureWarning)

    freq = DatetimeIndex(index).inferred_freq
    if freq is None:
        raise Exception('Unable to infer time rule')

    offset = to_offset(freq)
    return get_legacy_offset_name(offset)


def to_offset(freqstr):
    """
    Return DateOffset object from string representation

    Example
    -------
    to_offset('5Min') -> Minute(5)
    """
    if freqstr is None:
        return None

    if isinstance(freqstr, DateOffset):
        return freqstr

    if isinstance(freqstr, tuple):
        name = freqstr[0]
        stride = freqstr[1]
        if isinstance(stride, basestring):
            name, stride = stride, name
        name, _ = _base_and_stride(name)
        delta = get_offset(name) * stride
    else:
        delta = None
        stride_sign = None
        try:
            for stride, name, _ in opattern.findall(freqstr):
                offset = get_offset(name)
                if not stride:
                    stride = 1
                stride = int(stride)
                if stride_sign is None:
                    stride_sign = np.sign(stride)
                offset = offset * int(np.fabs(stride) * stride_sign)
                if delta is None:
                    delta = offset
                else:
                    delta = delta + offset
        except Exception:
            raise ValueError("Could not evaluate %s" % freqstr)

    if delta is None:
        raise ValueError('Unable to understand %s as a frequency' % freqstr)

    return delta


# hack to handle WOM-1MON
opattern = re.compile(r'([\-]?\d*)\s*([A-Za-z]+([\-@]\d*[A-Za-z]+)?)')

def _base_and_stride(freqstr):
    """
    Return base freq and stride info from string representation

    Example
    -------
    _freq_and_stride('5Min') -> 'Min', 5
    """
    groups = opattern.match(freqstr)

    if not groups:
        raise ValueError("Could not evaluate %s" % freqstr)

    stride = groups.group(1)

    if len(stride):
        stride = int(stride)
    else:
        stride = 1

    base = groups.group(2)

    return (base, stride)

def get_base_alias(freqstr):
    """
    Returns the base frequency alias, e.g., '5D' -> 'D'
    """
    return _base_and_stride(freqstr)[0]

_dont_uppercase = ['MS', 'ms']


def get_offset(name):
    """
    Return DateOffset object associated with rule name

    Example
    -------
    get_offset('EOM') --> BMonthEnd(1)
    """
    if name not in _dont_uppercase:
        name = name.upper()

        if name in _rule_aliases:
            name = _rule_aliases[name]
        elif name.lower() in _rule_aliases:
            name = _rule_aliases[name.lower()]
    else:
        if name in _rule_aliases:
            name = _rule_aliases[name]

    offset = _offset_map.get(name)

    if offset is not None:
        return offset
    else:
        raise Exception('Bad rule name requested: %s!' % name)


getOffset = get_offset


def hasOffsetName(offset):
    return offset in _offset_names

def get_offset_name(offset):
    """
    Return rule name associated with a DateOffset object

    Example
    -------
    get_offset_name(BMonthEnd(1)) --> 'EOM'
    """
    name = _offset_names.get(offset)

    if name is not None:
        return name
    else:
        raise Exception('Bad rule given: %s!' % offset)

def get_legacy_offset_name(offset):
    """
    Return the pre pandas 0.8.0 name for the date offset
    """
    name = _offset_names.get(offset)
    return _legacy_reverse_map.get(name, name)

get_offset_name = get_offset_name

def get_standard_freq(freq):
    """
    Return the standardized frequency string
    """
    if freq is None:
        return None

    if isinstance(freq, DateOffset):
        return get_offset_name(freq)

    code, stride = get_freq_code(freq)
    return _get_freq_str(code, stride)

#----------------------------------------------------------------------
# Period codes

# period frequency constants corresponding to scikits timeseries
# originals
_period_code_map = {
    # Annual freqs with various fiscal year ends.
    # eg, 2005 for A-FEB runs Mar 1, 2004 to Feb 28, 2005
    "A-DEC" : 1000,  # Annual - December year end
    "A-JAN" : 1001,  # Annual - January year end
    "A-FEB" : 1002,  # Annual - February year end
    "A-MAR" : 1003,  # Annual - March year end
    "A-APR" : 1004,  # Annual - April year end
    "A-MAY" : 1005,  # Annual - May year end
    "A-JUN" : 1006,  # Annual - June year end
    "A-JUL" : 1007,  # Annual - July year end
    "A-AUG" : 1008,  # Annual - August year end
    "A-SEP" : 1009,  # Annual - September year end
    "A-OCT" : 1010,  # Annual - October year end
    "A-NOV" : 1011,  # Annual - November year end

    # Quarterly frequencies with various fiscal year ends.
    # eg, Q42005 for Q-OCT runs Aug 1, 2005 to Oct 31, 2005
    "Q-DEC" : 2000 ,    # Quarterly - December year end
    "Q-JAN" : 2001,    # Quarterly - January year end
    "Q-FEB" : 2002,    # Quarterly - February year end
    "Q-MAR" : 2003,    # Quarterly - March year end
    "Q-APR" : 2004,    # Quarterly - April year end
    "Q-MAY" : 2005,    # Quarterly - May year end
    "Q-JUN" : 2006,    # Quarterly - June year end
    "Q-JUL" : 2007,    # Quarterly - July year end
    "Q-AUG" : 2008,    # Quarterly - August year end
    "Q-SEP" : 2009,    # Quarterly - September year end
    "Q-OCT" : 2010,    # Quarterly - October year end
    "Q-NOV" : 2011,    # Quarterly - November year end

    "M"     : 3000,   # Monthly

    "W-SUN" : 4000,    # Weekly - Sunday end of week
    "W-MON" : 4001,    # Weekly - Monday end of week
    "W-TUE" : 4002,    # Weekly - Tuesday end of week
    "W-WED" : 4003,    # Weekly - Wednesday end of week
    "W-THU" : 4004,    # Weekly - Thursday end of week
    "W-FRI" : 4005,    # Weekly - Friday end of week
    "W-SAT" : 4006,    # Weekly - Saturday end of week

    "B"      : 5000,   # Business days
    "D"      : 6000,   # Daily
    "H"      : 7000,   # Hourly
    "T"      : 8000,   # Minutely
    "S"      : 9000,   # Secondly
    None     : -10000  # Undefined

}

_reverse_period_code_map = {}
for _k, _v in _period_code_map.iteritems():
    _reverse_period_code_map[_v] = _k

# Additional aliases
_period_code_map.update({
    "Q"     : 2000,    # Quarterly - December year end (default quarterly)
    "A"     : 1000,  # Annual
    "W"     : 4000,    # Weekly
})

def _period_alias_dictionary():
    """
    Build freq alias dictionary to support freqs from original c_dates.c file
    of the scikits.timeseries library.
    """
    alias_dict = {}

    M_aliases = ["M", "MTH", "MONTH", "MONTHLY"]
    B_aliases = ["B", "BUS", "BUSINESS", "BUSINESSLY", 'WEEKDAY']
    D_aliases = ["D", "DAY", "DLY", "DAILY"]
    H_aliases = ["H", "HR", "HOUR", "HRLY", "HOURLY"]
    T_aliases = ["T", "MIN", "MINUTE", "MINUTELY"]
    S_aliases = ["S", "SEC", "SECOND", "SECONDLY"]
    U_aliases = ["U", "UND", "UNDEF", "UNDEFINED"]

    for k in M_aliases:
        alias_dict[k] = 'M'

    for k in B_aliases:
        alias_dict[k] = 'B'

    for k in D_aliases:
        alias_dict[k] = 'D'

    for k in H_aliases:
        alias_dict[k] = 'H'

    for k in T_aliases:
        alias_dict[k] = 'Min'

    for k in S_aliases:
        alias_dict[k] = 'S'

    for k in U_aliases:
        alias_dict[k] = None

    A_prefixes = ["A", "Y", "ANN", "ANNUAL", "ANNUALLY", "YR", "YEAR",
                  "YEARLY"]

    Q_prefixes = ["Q", "QTR", "QUARTER", "QUARTERLY", "Q-E",
                  "QTR-E", "QUARTER-E", "QUARTERLY-E"]

    month_names = [
        [ "DEC", "DECEMBER" ],
        [ "JAN", "JANUARY" ],
        [ "FEB", "FEBRUARY" ],
        [ "MAR", "MARCH" ],
        [ "APR", "APRIL" ],
        [ "MAY", "MAY" ],
        [ "JUN", "JUNE" ],
        [ "JUL", "JULY" ],
        [ "AUG", "AUGUST" ],
        [ "SEP", "SEPTEMBER" ],
        [ "OCT", "OCTOBER" ],
        [ "NOV", "NOVEMBER" ] ]

    seps = ["@", "-"]

    for k in A_prefixes:
        alias_dict[k] = 'A'
        for m_tup in month_names:
            for sep in seps:
                m1, m2 = m_tup
                alias_dict[k + sep + m1] = 'A-' + m1
                alias_dict[k + sep + m2] = 'A-' + m1

    for k in Q_prefixes:
        alias_dict[k] = 'Q'
        for m_tup in month_names:
            for sep in seps:
                m1, m2 = m_tup
                alias_dict[k + sep + m1] = 'Q-' + m1
                alias_dict[k + sep + m2] = 'Q-' + m1

    W_prefixes = ["W", "WK", "WEEK", "WEEKLY"]

    day_names = [
        [ "SUN", "SUNDAY" ],
        [ "MON", "MONDAY" ],
        [ "TUE", "TUESDAY" ],
        [ "WED", "WEDNESDAY" ],
        [ "THU", "THURSDAY" ],
        [ "FRI", "FRIDAY" ],
        [ "SAT", "SATURDAY" ] ]

    for k in W_prefixes:
        alias_dict[k] = 'W'
        for d_tup in day_names:
            for sep in ["@", "-"]:
                d1, d2 = d_tup
                alias_dict[k + sep + d1] = 'W-' + d1
                alias_dict[k + sep + d2] = 'W-' + d1

    return alias_dict

_reso_period_map = {
    "year"    : "A",
    "quarter" : "Q",
    "month"   : "M",
    "day"     : "D",
    "hour"    : "H",
    "minute"  : "T",
    "second"  : "S",
}

def _infer_period_group(freqstr):
    return _period_group(_reso_period_map[freqstr])

def _period_group(freqstr):
    base, mult = get_freq_code(freqstr)
    return base // 1000 * 1000

_period_alias_dict = _period_alias_dictionary()

def _period_str_to_code(freqstr):
    # hack
    freqstr = _rule_aliases.get(freqstr, freqstr)
    freqstr = _rule_aliases.get(freqstr.lower(), freqstr)

    try:
        freqstr = freqstr.upper()
        return _period_code_map[freqstr]
    except:
        alias = _period_alias_dict[freqstr]
        return _period_code_map[alias]



def infer_freq(index, warn=True):
    """
    Infer the most likely frequency given the input index. If the frequency is
    uncertain, a warning will be printed

    Parameters
    ----------
    index : DatetimeIndex

    Returns
    -------
    freq : string or None
        None if no discernable frequency
    """
    from pandas.tseries.index import DatetimeIndex

    if not isinstance(index, DatetimeIndex):
        index = DatetimeIndex(index)

    inferer = _FrequencyInferer(index, warn=warn)
    return inferer.get_freq()

_ONE_MICRO = 1000L
_ONE_MILLI = _ONE_MICRO * 1000
_ONE_SECOND = _ONE_MILLI * 1000
_ONE_MINUTE = 60 * _ONE_SECOND
_ONE_HOUR = 60 * _ONE_MINUTE
_ONE_DAY = 24 * _ONE_HOUR

class _FrequencyInferer(object):
    """
    Not sure if I can avoid the state machine here
    """

    def __init__(self, index, warn=True):
        self.index = index
        self.values = np.asarray(index).view('i8')
        self.warn = warn

        if len(index) < 3:
            raise ValueError('Need at least 3 dates to infer frequency')

        self.deltas = lib.unique_deltas(self.values)
        self.is_unique = len(self.deltas) == 1
        self.is_monotonic = self.index.is_monotonic

    def get_freq(self):
        if not self.is_monotonic:
            return None

        delta = self.deltas[0]
        if _is_multiple(delta, _ONE_DAY):
            return self._infer_daily_rule()
        else:
            # Possibly intraday frequency
            if not self.is_unique:
                return None
            if _is_multiple(delta, _ONE_HOUR):
                # Hours
                return _maybe_add_count('H', delta / _ONE_HOUR)
            elif _is_multiple(delta, _ONE_MINUTE):
                # Minutes
                return _maybe_add_count('T', delta / _ONE_MINUTE)
            elif _is_multiple(delta, _ONE_SECOND):
                # Seconds
                return _maybe_add_count('S', delta / _ONE_SECOND)
            elif _is_multiple(delta, _ONE_MILLI):
                # Milliseconds
                return _maybe_add_count('L', delta / _ONE_MILLI)
            elif _is_multiple(delta, _ONE_MICRO):
                # Microseconds
                return _maybe_add_count('U', delta / _ONE_MICRO)
            else:
                # Nanoseconds
                return _maybe_add_count('N', delta)

    @cache_readonly
    def day_deltas(self):
        return [x / _ONE_DAY for x in self.deltas]

    @cache_readonly
    def fields(self):
        return lib.build_field_sarray(self.values)

    @cache_readonly
    def rep_stamp(self):
        return lib.Timestamp(self.values[0])

    def month_position_check(self):
        # TODO: cythonize this, very slow
        calendar_end = True
        business_end = True
        calendar_start = True
        business_start = True

        years = self.fields['Y']
        months = self.fields['M']
        days = self.fields['D']
        weekdays = self.index.dayofweek

        from calendar import monthrange
        for y, m, d, wd in zip(years, months, days, weekdays):
            wd = datetime(y, m, d).weekday()

            if calendar_start:
                calendar_start &= d == 1
            if business_start:
                business_start &= d == 1 or (d <= 3 and wd == 0)

            _, daysinmonth = monthrange(y, m)
            cal = d == daysinmonth
            if calendar_end:
                calendar_end &= cal
            if business_end:
                business_end &= cal or (daysinmonth - d < 3 and wd == 4)

        if calendar_end:
            return 'ce'
        elif business_end:
            return 'be'
        elif calendar_start:
            return 'cs'
        elif business_start:
            return 'bs'
        else:
            return None

    @cache_readonly
    def mdiffs(self):
        nmonths = self.fields['Y'] * 12 + self.fields['M']
        return lib.unique_deltas(nmonths.astype('i8'))

    @cache_readonly
    def ydiffs(self):
        return lib.unique_deltas(self.fields['Y'].astype('i8'))

    def _infer_daily_rule(self):
        annual_rule = self._get_annual_rule()
        if annual_rule:
            nyears = self.ydiffs[0]
            month = _month_aliases[self.rep_stamp.month]
            return _maybe_add_count('%s-%s' % (annual_rule, month), nyears)

        quarterly_rule = self._get_quarterly_rule()
        if quarterly_rule:
            nquarters = self.mdiffs[0] / 3
            month = _month_aliases[self.rep_stamp.month]
            return _maybe_add_count('%s-%s' % (quarterly_rule, month),
                                    nquarters)

        monthly_rule = self._get_monthly_rule()
        if monthly_rule:
            return monthly_rule

        if self.is_unique:
            days = self.deltas[0] / _ONE_DAY
            if days % 7 == 0:
                # Weekly
                alias = _weekday_rule_aliases[self.rep_stamp.weekday()]
                return _maybe_add_count('W-%s' % alias, days / 7)
            else:
                return _maybe_add_count('D', days)

        # Business daily. Maybe
        if self.day_deltas == [1, 3]:
            return 'B'

    def _get_annual_rule(self):
        if len(self.ydiffs) > 1:
            return None

        if len(algos.unique(self.fields['M'])) > 1:
            return None

        pos_check = self.month_position_check()
        return {'cs': 'AS', 'bs': 'BAS',
                'ce': 'A', 'be': 'BA'}.get(pos_check)

    def _get_quarterly_rule(self):
        if len(self.mdiffs) > 1:
            return None

        if not self.mdiffs[0] % 3 == 0:
            return None

        pos_check = self.month_position_check()
        return {'cs': 'QS', 'bs': 'BQS',
                'ce': 'Q', 'be': 'BQ'}.get(pos_check)

    def _get_monthly_rule(self):
        if len(self.mdiffs) > 1:
            return None
        pos_check = self.month_position_check()
        return {'cs': 'MS', 'bs': 'BMS',
                'ce': 'M', 'be': 'BM'}.get(pos_check)


import pandas.core.algorithms as algos

def _maybe_add_count(base, count):
    if count > 1:
        return '%d%s' % (count, base)
    else:
        return base

def is_subperiod(source, target):
    """
    Returns True if downsampling is possible between source and target
    frequencies

    Parameters
    ----------
    source : string
        Frequency converting from
    target : string
        Frequency converting to

    Returns
    -------
    is_subperiod : boolean
    """
    if isinstance(source, offsets.DateOffset):
        source = source.rule_code

    if isinstance(target, offsets.DateOffset):
        target = target.rule_code

    target = target.upper()
    source = source.upper()
    if _is_annual(target):
        return source in ['D', 'B', 'M', 'H', 'T', 'S']
    elif _is_quarterly(target):
        return source in ['D', 'B', 'M', 'H', 'T', 'S']
    elif target == 'M':
        return source in ['D', 'B', 'H', 'T', 'S']
    elif _is_weekly(target):
        return source in [target, 'D', 'B', 'H', 'T', 'S']
    elif target == 'B':
        return source in ['B', 'H', 'T', 'S']
    elif target == 'D':
        return source in ['D', 'H', 'T', 'S']

def is_superperiod(source, target):
    """
    Returns True if upsampling is possible between source and target
    frequencies

    Parameters
    ----------
    source : string
        Frequency converting from
    target : string
        Frequency converting to

    Returns
    -------
    is_superperiod : boolean
    """
    if isinstance(source, offsets.DateOffset):
        source = source.rule_code

    if isinstance(target, offsets.DateOffset):
        target = target.rule_code

    target = target.upper()
    source = source.upper()
    if _is_annual(source):
        if _is_annual(target):
            return _get_rule_month(source) == _get_rule_month(target)

        if _is_quarterly(target):
            smonth = _get_rule_month(source)
            tmonth = _get_rule_month(target)
            return _quarter_months_conform(smonth, tmonth)
        return target in ['D', 'B', 'M', 'H', 'T', 'S']
    elif _is_quarterly(source):
        return target in ['D', 'B', 'M', 'H', 'T', 'S']
    elif source == 'M':
        return target in ['D', 'B', 'H', 'T', 'S']
    elif _is_weekly(source):
        return target in [source, 'D', 'B', 'H', 'T', 'S']
    elif source == 'B':
        return target in ['D', 'B', 'H', 'T', 'S']
    elif source == 'D':
        return target in ['D', 'B', 'H', 'T', 'S']

def _get_rule_month(source, default='DEC'):
    source = source.upper()
    if '-' not in source:
        return default
    else:
        return source.split('-')[1]

def _is_annual(rule):
    rule = rule.upper()
    return rule == 'A' or rule.startswith('A-')

def _quarter_months_conform(source, target):
    snum = _month_numbers[source]
    tnum = _month_numbers[target]
    return snum % 3 == tnum % 3

def _is_quarterly(rule):
    rule = rule.upper()
    return rule == 'Q' or rule.startswith('Q-')


def _is_weekly(rule):
    rule = rule.upper()
    return rule == 'W' or rule.startswith('W-')


DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']

MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL',
          'AUG', 'SEP', 'OCT', 'NOV', 'DEC']

_month_numbers = dict((k, i) for i, k in enumerate(MONTHS))



_weekday_rule_aliases = dict((k, v) for k, v in enumerate(DAYS))
_month_aliases = dict((k + 1, v) for k, v in enumerate(MONTHS))

def _is_multiple(us, mult):
    return us % mult == 0
