#-*- coding: utf-8 -*-

__name__ = 'publisher'

import feedparser
import operator
import time
import datetime
import sys
import atexit
import hashlib
import sqlite3
import sqlitebck
import loggerfactory
import threading
import Queue
import multiprocessing
import os
import ConfigParser

from forumlogger import *
from multiprocessing import current_process
from threading import current_thread
from response import Response, ResponseStatus

log = None
dbConnection = None
cursor = None
stopRequest = threading.Event()


class StopThreadException(Exception):
	""" To exit from publishForever """
	pass


class NewsItem:
	""" Abstraction around forum news item """

	def __init__(self, title, body, url):
		self.title = title
		self.body  = body
		self.url   = url

	def __unicode__(self):
		return u'[b]{0}[/b]\n[quote]{1}\n[url={2}]link[/url][/quote]\n'.format(self.title, self.body, self.url)

	def __str__(self):
		return unicode(self).encode('utf-8')


def fetchItems():
	""" Get all news items from DB that need to be publised """

	cursor.execute("SELECT hash, title, description, link FROM feed WHERE published = 0 ORDER BY link")
	rows = cursor.fetchall()

	newsItems = [] 

	for row in rows:
		newsItems.append(NewsItem(row[1], row[2], row[3]))

	return rows, newsItems


def setAsPublished(rows):
	""" Mark each item as published, so we don't have to publish it again """

	for row in rows:
		cursor.execute('UPDATE feed SET published = 1 where hash = ? and published = 0', (row[0],))

	dbConnection.commit()


def nextTime(times):
	""" Get the next time to sleep until before we start publishing """
	# currentTime is local
	currentTime = datetime.datetime.now().time()
	nextPublishTime = None
	dayCount = 0

	log.info('Determining the next publish time in {0}'.format(times))

	for t in times:
		if currentTime < t:
			log.info('Current time is less than {0}'.format(t))
			nextPublishTime = t	
			break

	if nextPublishTime is None:
		dayCount = 1
		nextPublishTime = times[0] if times else datetime.time(14, 55, 0)
 
	d = datetime.datetime.today().date() + datetime.timedelta(days=dayCount)
	return datetime.datetime.combine(d, nextPublishTime)


def sleepUntilWokenUp(interruptRequest, times):
	""" Sleep until this time is up or stop request is recieved """

	dt = nextTime(times)
	log.info('{0} Next publish datetime: [{1}]'.format(current_thread().name, dt))

	while dt >= datetime.datetime.today() and not interruptRequest.isSet():
		interruptRequest.wait(1)

	log.info('{0} is awake'.format(current_thread().name))


def backup(dbFile, dbBackupDir):
	#################################################################################
	# TODO: 
	#################################################################################
	#	1. Check for ending '/'
	#	2. Handle possible errors/exceptions from:
	#		a. sqlite3.connect
	#		b. sqlitebck.copy
	#################################################################################

	log.info('{0} Backing up DB file [{1}] to [{2}]'.format(current_thread().name, dbFile, dbBackupDir))
	dbCopyConnection = sqlite3.connect(dbBackupDir + os.path.split(dbFile)[1])
	sqlitebck.copy(dbConnection, dbCopyConnection)
	log.info('{0} ... OK'.format(current_thread().name))


def publishForever(queue, interruptRequest, times, submitType, dbFile, dbBackupDir):
	""" Sleep, check for items to publish, sleep again """

	params = None

	log.info('{0} started'.format(current_thread().name))
	log.info('{0} ... publish time: [{1}]'.format(current_thread().name, times))
	log.info('{0} ... submit type:  [{1}]'.format(current_thread().name, submitType))
	log.info('{0} ... database file:[{1}]'.format(current_thread().name, dbFile))

	
	def stopCommandHandler(params):
		raise StopThreadException('{0} requested to stop'.format(current_thread().name))


	def setCommandHandler(params):
		log.info('{0} There is a set request ({1})'.format(current_thread().name, params))

		if params['time']:
			times = params['time']
			log.info('{0} New times: {1}'.format(current_thread().name, times))

		if params['submit_type']:	
			submitType = params['submit_type']
			log.info('{0} New submit type: {1}'.format(current_thread().name, submitType))

		interruptRequest.clear()

		return (times, submitType)


	dispatch = {'stop': stopCommandHandler, 'set' : setCommandHandler}


	try:
		global dbConnection, cursor

		dbConnection = sqlite3.connect(dbFile)
		cursor = dbConnection.cursor()

		while True:
			while not interruptRequest.isSet():
				sleepUntilWokenUp(interruptRequest, times)
					
				if interruptRequest.isSet():
					break

				log.info('{0} Fetching fresh items'.format(current_thread().name))
				(rows, items) = fetchItems()
				log.info('{0} ... OK. Fetched {1} items'.format(current_thread().name, len(items)))

				# If there is nothing to publish, got back to sleep
				if not items:
					log.info('{0} Nothing to submit'.format(current_thread().name))
					continue

				log.info(u'{0} {1}'.format(current_thread().name, u''.join([unicode(i) for i in items])))

				try:
					fl = ForumLogger(interruptRequest, log, dbFile)

					if fl.loginToForum():
						fl.submit(items, submitType)
						setAsPublished(rows)
						backup(dbFile, dbBackupDir)
					else:
						log.error("{0} Can't login".format(current_thread().name))
				except Exception as ex:
					log.exception(u'{0} {1}'.format(current_thread().name, repr(ex)))


			log.info('{0} interrupted. Waiting for command'.format(current_thread().name))
			(command, params) = queue.get()
			log.info('{0} ... Recieved command [{1}]'.format(current_thread().name, command))

			(times, submitType) = dispatch[command](params)

			log.info('submitType {0}'.format(submitType))
			log.info('times {0}'.format(times))
				
	except StopThreadException as ex:
		# We were requested to stop
		log.info(repr(ex))
	except Exception as ex:
		log.exception(repr(ex))


def validateTime(rawStrTimes):
	""" 
	Traverse the list of string times and convert them to python datetime.time objects.
	E.g., ["09:00:00", "10:01:02", "22:15:30"] will be converted to
	[datetime.time(9, 0), datetime.time(10,1,2), datetime.time(22,15,30)] respectively
	"""

	pyTimes = []

	for t in rawStrTimes:
		try:
			pyTimes.append(datetime.datetime.strptime(t, "%H:%M:%S").time())
		except ValueError as err:
			log.warning('{0} validateTime - Can\'t convert "{1}". Skipping'.format(current_process().name, t))

	# Sort times in ascending order
	sorted(pyTimes)
	return pyTimes


def validateSubmitType(submitType):
	if submitType == 'post' or submitType == 'preview':
		return submitType
	else:
		log.warning('{0} validateSubmitType - Unknown submit type "{1}". Skipping'.format(current_process().name, submitType))
		return None


def readConfigFile(configFile, section, options=None):
	logs = [('info', 'Reading config file [{0}]'.format(configFile))]

	config = ConfigParser.ConfigParser()

	if not config.read(configFile):
		logs.append(('warning', 'Can\'t read config file'))
		return (options, logs)

	if section not in config.sections():
		logs.append(('warning', 'Section [{0}] not found. Will use defaults'.format(section)))
		return (options, logs)

	for op in options:
		if config.has_option(section, op):
			if op == 'time':
				options[op] = [t.strip() for t in config.get(section, op).split(',')]
			else:
				options[op] = config.get(section, op)

	return (options, logs)


def getStartParams(params):
	startParams = {
		'db_file':			'/tmp/feeds.db',
		'log_file':			'/tmp/publish1.log',
		'time':				["14:55:00"],
		'submit_type':		'post',
		'db_backup_dir':	'/home/shvonder/Dropbox/'
	}

	(startParams, logs) = readConfigFile(params['module_ini'], __name__.upper(), startParams)

	global log
	log = loggerfactory.createLogger(__name__, startParams['log_file'])

	logDispatch = {
		'info':  	lambda l, s: l.info(s),
		'debug': 	lambda l, s: l.debug(s),
		'warning':  lambda l, s: l.warning(s),
		'error':  	lambda l, s: l.error(s)
	}

	for l in logs:
		logDispatch[l[0]](log, l[1])

	log.info(startParams)
	return (startParams['db_file'], startParams['db_backup_dir'], validateTime(startParams['time']), startParams['submit_type'])


def stopCommandHandler(interruptRequest, commandQueue, params):
	""" Handle 'stop' command """

	log.info('{0} Handling [stop] command'.format(current_process().name))
	interruptRequest.set()
	commandQueue.put(('stop', None))
	stopRequest.set()
	log.info('{0} ... OK'.format(current_process().name))


def setCommandHandler(interruptRequest, commandQueue, params):
	""" Hanlde 'set' command """

	log.info('{0} Handling [set] command'.format(current_process().name))
	newParams = {'time' : [], 'submit_type' : None}

	if 'time' in params:
		newParams['time'] = validateTime(params['time'])
	if 'submit_type' in params:
		newParams['submit_type'] = validateSubmitType(params['submit_type'])

	interruptRequest.set()
	commandQueue.put(('set', newParams))
	log.info('{0} ... OK'.format(current_process().name))


def run(queue, params):
	""" Start a publisher thread to publish forever """
	
	try:
		(dbFile, dbBackupDir, times, submitType) = getStartParams(params)

		log.info('{0} started'.format(current_process().name))

		commandQueue = Queue.Queue()
		interruptRequest = threading.Event()
		publishThread = threading.Thread(target=publishForever, 
										 args=(commandQueue, interruptRequest, times, submitType, dbFile, dbBackupDir), 
										 name="PublishThread")
		publishThread.start()

		dispatch = {'stop' : stopCommandHandler, 'set' : setCommandHandler}

		while not stopRequest.isSet():
			(command, params) = queue.get()
			log.info('{0} recieved command: [{1}]'.format(current_process().name, str(command)))

			try:
				dispatch[command](interruptRequest, commandQueue, params)
				queue.put(Response())
			except Exception as ex:
				queue.put(Response(message=str(ex), status=ResponseStatus.ERROR))

		log.info('{0} Waiting for {1} to stop'.format(current_process().name, publishThread.name))
		publishThread.join()
		log.info('{0} ... OK'.format(current_process().name))
	except Exception as ex:
		log.exception(repr(ex))

	log.info('{0} terminated'.format(__name__))

