import logging, re, os, shutil, sys
from datetime import date, datetime
from dataclasses import dataclass
from collections import defaultdict
from os.path import expanduser
from typing import List, Optional

from dateutil.parser import parse as dateutil_parse
from modgrammar import *
import yaml

from invoice import Invoice


def get_default_settings():
    settings = {
        'billcode': True,
        'billcodes': {},
        'billrate': 1000.,
        'footer': [],
        'prefix': '',
        'invoice_on': 'marker',
        'invoice_marker': '====',
        'summary_on': 'marker',
        'summary_marker': '----',
        'verbose': 0,
        'weekly_summary_template': '----------       {hours_this_week} ({hours_since_invoice} uninvoiced)',
        'invoice_template': '==========       {hours_this_week} ({hours_since_invoice} since invoice)',
        'invoice_filename_template': 'invoice-{invoice_code}.pdf',
        'address': []
    }
    return settings

def samefile(f1, f2):
    '''
    A simple replacement of the os.path.samefile() function not existing
    on the Windows platform.
    MAC/Unix supported in standard way :).

    Author: Denis Barmenkov <denis.barmenkov@gmail.com>

    Source: code.activestate.com/recipes/576886/

    Copyright: this code is free, but if you want to use it, please
               keep this multiline comment along with function source.
               Thank you.

    2009-08-19 20:13
    '''
    try:
        return os.path.samefile(f1, f2)
    except AttributeError:
        f1 = os.path.abspath(f1).lower()
        f2 = os.path.abspath(f2).lower()
        return f1 == f2

@dataclass
class TimesheetLineItem:
    date: date
    prefix: Optional[str] = None
    suffix: Optional[str] = None
    billcode: Optional[str] = None
    hours: Optional[int] = None
    ranges: Optional[List[int]] = None

# @dataclass
# class TimesheetSummary:
#     hours:

logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)

grammar_whitespace_mode = 'explicit'

class MyDate(Grammar):
    # grammar = (WORD('0-9', "-0-9/", grammar_name='date'))
    grammar = (WORD('2', "-0-9", fullmatch=True, grammar_name='date')
        | WORD('0-9', "-0-9/", grammar_name='date'))
    grammar_tags = ['date']

class BillCode(Grammar):
    """ All capital letter billing code. """
    grammar = (WORD("A-Z", grammar_name='bill_code'))

class Hours(Grammar):
    grammar = (WORD(".0-9", grammar_name='hours'), OPTIONAL("h"))

class Hour(Grammar):
    grammar = WORD("0-9", min=1, max=2, grammar_name='hour')

class Minute(Grammar):
    grammar = WORD("0-9", min=1, max=2, grammar_name='minute')

class AMPM(Grammar):
    grammar = L("A") | L("P") | L("a") | L("p")

class MyTime(Grammar):
    grammar = (G(Hour, OPTIONAL(":", Minute))), OPTIONAL(AMPM)
    # grammar = (WORD("0-9:", grammar_name='time'), OPTIONAL(L("A") | L("P") | L("a") | L("p"), grammar_name='ampm'))

class Range(Grammar):
    grammar = G(MyTime, OPTIONAL(WHITESPACE), '-', OPTIONAL(WHITESPACE),
        OPTIONAL(MyTime), OPTIONAL('(', Hours, ')'), grammar_name='range')

class RangeList(Grammar):
    grammar = LIST_OF(G(Range | Hours), sep=G(",", OPTIONAL(WHITESPACE)), grammar_name="ranges")

class Prefix(Grammar):
    grammar = (ZERO_OR_MORE(L('*') | WHITESPACE), )

class Suffix(Grammar):
    grammar = (OPTIONAL(WHITESPACE), OPTIONAL(L('#'), REST_OF_LINE), EOF)

class MyGrammar (Grammar):
    grammar = (
        G(Prefix, MyDate, WHITESPACE, Hours, WHITESPACE, RangeList, Suffix, grammar_name="3args") |
        G(Prefix, MyDate, WHITESPACE, RangeList,  Suffix, grammar_name="2argrange") |
        G(Prefix, MyDate, WHITESPACE, Hours, Suffix, grammar_name="2arghours") |
        G(Prefix, MyDate, WHITESPACE, BillCode, WHITESPACE, Hours, WHITESPACE, RangeList, Suffix, grammar_name="3args") |
        G(Prefix, MyDate, WHITESPACE, BillCode, WHITESPACE, RangeList,  Suffix, grammar_name="2argrange") |
        G(Prefix, MyDate, WHITESPACE, BillCode, WHITESPACE, Hours, Suffix, grammar_name="2arghours") |
        G(Prefix, MyDate, Suffix, grammar_name="justdate")
    )

myparser = MyGrammar.parser()

time_regex = re.compile(r'(\d{1,2})(:\d+)?([aApP])?')
def parse_time(cur_date, time_str, after=None):
    """ Parse time

    >>> parse_time(datetime(2015, 6, 3, 0, 0), '12p')
    datetime.datetime(2015, 6, 3, 12, 0)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '12:01p')
    datetime.datetime(2015, 6, 3, 12, 1)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '12a')
    datetime.datetime(2015, 6, 3, 0, 0)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '1')
    datetime.datetime(2015, 6, 3, 13, 0)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '1a')
    datetime.datetime(2015, 6, 3, 1, 0)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '11:45p')
    datetime.datetime(2015, 6, 3, 23, 45)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '12:45a')
    datetime.datetime(2015, 6, 3, 0, 45)
    >>> parse_time(datetime(2015, 6, 3, 0, 0), '12:45p')