# -*- coding: utf-8 -*-
import re
import time
import struct
from datetime import datetime
from collections import deque
from ctypes import c_uint16
from .EPGConfig import Timer, get_histtimings
from .EPGImport import ISO639_associations
from .. import isDreamOS, LANG
from ..log import logger
from ..modules.six import PY3, ensure_str, ensure_text
from ..modules.six.moves import range, zip, filter

PATTERN = re.compile(r'''\.(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''')
LANG = ISO639_associations.get(LANG, 'eng')
EPOCH_AS_FILETIME = 116444736000000000  # January 1, 1970 as MS file time
HUNDREDS_OF_NANOSECONDS = 10000000
histseconds, timespan = get_histtimings()

def utc_offset():
	timestamp = time.time()
	utime = datetime.utcfromtimestamp(timestamp)
	btime = datetime.fromtimestamp(timestamp)
	return (btime - utime).total_seconds()


utc_offset = utc_offset()


def MSfiletime_to_timestamp(MStimestamp):
	timestamp, _ = divmod((MStimestamp - EPOCH_AS_FILETIME), HUNDREDS_OF_NANOSECONDS)
	return int(timestamp - utc_offset)


def length(d):
	return struct.unpack('<H', d[:2])[0]


def parse_events(data):
	pack_len = 12
	events_num = length(data) * pack_len
	data = data[2:]
	return [MSfiletime_to_timestamp(struct.unpack('<hqH', data[i:i+pack_len])[1]) for i in range(0, events_num, pack_len)]


def parse_titles(data, encoding):
	if data[:26] != b'\x4a\x54\x56\x20\x33\x2e\x78\x20\x54\x56\x20\x50\x72\x6f\x67\x72\x61\x6d\x20\x44\x61\x74\x61\x0a\x0a\x0a':
		raise Exception('Invalid JTV 3.x TV Programm Data format')
	data = data[26:]
	while data:
		title_length = length(data)
		data = data[2:]
		yield data[:title_length].decode(encoding)   # yield title
		data = data[title_length:]


class JTVConverter(object):

	@classmethod
	def enumFile(cls, zipobj, source, jtv_encoding="cp1251"):
		if not source.channels.items:
			logger.warn("JTVConverter is nothing to enumerate. The channels lookup table is empty")
			return
		now_timestamp_utc = time.time()
		if histseconds:
			now_timestamp_utc -= histseconds
			logger.info("Keep outdated EPG events to -%s" % time.strftime("%H:%M:%S", time.gmtime(histseconds)))
		now_timestamp_utc = int(now_timestamp_utc)
		LANG_FLAG = 0x800    # bit 11 indicates UTF-8 for filenames
		with Timer() as timer:
			logger.info("Enumerating '%s' event information ..." % source.description)
			for zinfo in filter(lambda x: x.filename.endswith('.pdt'), zipobj.infolist()):
				filename = zinfo.filename
				if zinfo.flag_bits & LANG_FLAG == 0:
					if PY3:
						filename = bytes(filename, encoding='cp437')
					unicode_fname = filename.decode('cp866', 'replace')
				else:
					unicode_fname = filename
				if PY3:
					channel_id = ensure_str('%s' % unicode_fname[0:-4]).replace('_', ' ').lower()
				else:
					channel_id = ensure_text('%s' % unicode_fname[0:-4]).replace('_', ' ').lower().encode('utf-8')

				if channel_id not in source.channels.items:
					continue
				try:
					with zipobj.open(zinfo.filename) as f:
						titles = parse_titles(f.read(), jtv_encoding)
				except Exception as err:
					logger.warn("JTVConverter failed to process titles in %s: %s" % (zinfo.filename, err))
					continue
				try:
					with zipobj.open(zinfo.filename[:-4] + '.ndx') as f:
						events = parse_events(f.read())
					events = deque(zip(events[:-1], events[1:]))
				except Exception as err:
					logger.warn("JTVConverter failed to process events in %s: %s" % (events_fname, err))
					continue

				events_tuple = tuple()  # tuple of tuples

				for title in titles:
					timer.counter += 1
					try:
						t = list(events.popleft())
						try:
							title, descr = [ensure_str(s.strip()) for s in PATTERN.split(title)]
						except:
							try:
								title, descr = [ensure_str(s.strip()) for s in title.split('.', 1)]
							except:
								title, descr = ensure_str(title.strip()), ''

						if t[0] <= timespan and now_timestamp_utc <= t[1] >= t[0]:
							t[1] -= t[0]
							if t[1] >= 86400:
								raise IndexError(t)

							# ETSI EN 300 468 v V1.11.1
							# 1. start time (long)
							# 2. duration (int)
							# 3. event title (string)
							# 4. short description (string) -> 6.2.37 Short event descriptor
							# 5. extended description (string) -> 6.2.15 Extended event descriptor
							# 6. event type (byte) or list or tuple of event types -> 6.2.9 Content descriptor
							# 7. optional dvb event ID (int), if not supplied, it will default to 0, which implies
							#    an auto-generated ID based on the start time
							# 8. optional list or tuple of tuples
							#    (country[string 3 bytes], parental_rating [byte]) -> 6.2.28 Parental rating descriptor
							events_tuple += (tuple(t + [title, '', descr, 0, c_uint16(t[0]).value, ((LANG, 0),)]),)
					except IndexError:
						pass  # Title has no end time or unacceptably large duration
					except Exception as err:
						logger.error("JTVConverter parsing JTV event error: %s" % err)
					finally:
						t = int(time.time()) - histseconds
						if t - now_timestamp_utc >= 10:
							logger.info("Processed: %s events" % timer.counter)
							now_timestamp_utc = t

				yield list(source.channels.items[channel_id]), events_tuple

			timer.msg = 'Processed {} events in {} or ~{:.0f} per second'.format(timer.counter, time.strftime("%H:%M:%S", time.gmtime(timer.elapsed)), timer.counter/timer.elapsed)
