#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import optparse
import os
import pg
import types
import urllib
import urlparse
import datetime
import re
import json
import time
from pianos import parseConnectionString


	
class Error( Exception ):
	pass
	
	
class Database:

	def __init__( self, params ):
		self.db = None
		self.params = parseConnectionString( params )
		
	def connect( self ):
		self.db = pg.connect( self.params[2], host = self.params[0], port = self.params[1], user = self.params[3], passwd = self.params[4] )
	
	def query( self, sql ):
		res = self.db.query( sql )
		try:
			return res.dictresult()
		except:
			return None
			
class sqlverbatim:
	def __init__( self, value ):
		self.value = value
	def __str__( self ):
		return str( self.value )

def sqlarg( args ):
	all = []
	for a in args:
		if type( a ) is types.InstanceType:
			t = 'verbatim'
		else:
			if a is None:
				t = 'null'
			elif type( a ) is types.StringType:
				t = 'string'
			elif type( a ) is types.BooleanType:
				t = 'bool'
			else:
				t = 'verbatim'
			
		if t == 'null':
			v = 'NULL'
		elif t == 'string':
			v = "E'" + pg.escape_string( a ) + "'"
		elif t == 'bool':
			v = 'true' if a else 'false'
		else:
			v = str( a )

		all.append( v )
		
	return ','.join( all )
	

class Action:
	def __init__( self ):
		self.db = None
		
	def connectDB( self, args ):
		if len( args ) < 1:
			print 'ERROR: missing database connection URL'
			return False

		self.db = Database( args[0] )
		self.db.connect()
		self.db.query( "BEGIN;" )
		return True
	
	def commit( self ):
		print ""
		print "Is this OK ? Please type 'yes' to COMMIT the transaction."
		answer = raw_input()
		if answer == 'yes':
			print "Committing ..."
			self.db.query( "COMMIT;" )
			print "Done !"
		else:
			print "Rolling back ..."
			self.db.query( "ROLLBACK;" )
			print "No changes made"
	
	def getArg( self, args, key ):
		for a in args:
			tokens = a.split( '=' )
			if len( tokens ) == 2 and tokens[0] == key:
				return tokens[1]
		# raise ValueError('Could not find the parameter '+key)
		return None
	
	def forAllAssignments( self, args, function ):
		exfilter = self.getArg( args, 'exfilter' )
		if exfilter:
			exs = self.db.query( "SELECT * FROM exercise WHERE name ILIKE E'" + pg.escape_string( '%' + exfilter + '%' ) + "';" )
		else:
			exs = self.db.query( "SELECT * FROM exercise;" )
		
		for ex in exs:
			userfilter = self.getArg( args, 'userfilter' )
			if userfilter:
				filter = "AND person.username ILIKE E'" + pg.escape_string( '%' + userfilter + '%' ) + "'"
			else:
				filter = ''
			assignments = self.db.query( "SELECT assignment.id,person.username FROM assignment,object,person WHERE assignment.id = object.id AND object.creator = person.id AND assignment.exercise = " + str( ex['id'] ) + " " + filter + ";" )
			for a in assignments:
				function( ex, a )
	
	
	
	
class Fix3To4NIJ( Action ):

	def help( self ):
		return ( 
			'Sets the mark-value observation (normally indicated by the user during the analysis stage), required when running the NIJ plugin, but typically missing after the migrate_3_to_4.py script has run',
'''Options:
exfilter: modify only exercises matching the filter (used by an ILIKE '%<exfilter>%' operator)' 
userfilter: same but for usernames''' )
			
	def run( self, args ):
		if not self.connectDB( args ):
			return 1
		self.numFixed = 0
		self.forAllAssignments( args, self.onAssignment )
		if self.numFixed:
			self.commit()
			
	def onAssignment( self, exercise, assignment ):
		if self.db.query( "SELECT count(*) FROM result WHERE assignment = " + str( assignment['id'] ) + " AND type = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND step = (SELECT ID FROM resultstep WHERE short = 'ANALYSIS');" )[0]['count'] == 1:
			if self.db.query( "SELECT count(*) FROM observation WHERE assignment = " + str( assignment['id'] ) + " AND restype = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'ANALYSIS') AND type = (SELECT id FROM observationtype WHERE name = 'mark-value');" )[0]['count'] != 1:
				print "Fixing exercise", exercise['name'], ", assignment", assignment['id'], "(user", assignment['username'], ")"
				self.db.query( "INSERT INTO observation(assignment,restype,resstep,type,value) VALUES (" + str( assignment['id'] ) + ",(SELECT ID FROM resulttype WHERE short = 'VALIDATED'),(SELECT ID FROM resultstep WHERE short = 'ANALYSIS'),(SELECT id FROM observationtype WHERE name = 'mark-value'),'1');" )
				self.numFixed += 1

			
		
		
		
class UnsubmitAction( Action ):

	def help( self ):
		return ( 'Removes all VALIDATED results during the COMPARISON phase.',
'''Options:
exfilter: modify only exercises matching the filter (used by an ILIKE '%<exfilter>%' operator)' 
userfilter: same but for usernames''' )

	
	def run( self, args ):
		if not self.connectDB( args ):
			return 1
		self.triggerDisabled = False
		self.forAllAssignments( args, self.onAssignment )
		if self.triggerDisabled:
			self.db.query( "CREATE TRIGGER result_on_store_trigger_before_update BEFORE UPDATE ON result FOR EACH ROW EXECUTE PROCEDURE  result_on_store_trigger_before_update_hook();" )
			self.commit()
		else:
			print "No matching assignments, nothing done"
		
	def onAssignment( self, exercise, assignment ):
	
		if self.db.query( "SELECT count(*) FROM result WHERE assignment = " + str( assignment['id'] ) + " AND type = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND step = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )[0]['count'] == 1:
			print "Unsubmitting exercise", exercise['name'], ", assignment", assignment['id'], "(user", assignment['username'], ")"
			if not self.triggerDisabled:
				# Disable the result tampering prevention trigger 
				self.db.query( "DROP TRIGGER result_on_store_trigger_before_update ON result;" )
				self.triggerDisabled = True
			
			# Save the VALIDATED result's related stuff
			minutiae = self.db.query( "SELECT * INTO TEMPORARY TABLE fix_db_minutiae_" + str( assignment['id'] ) + " FROM minutia WHERE assignment = " + str( assignment['id'] ) + " AND restype = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			annotations = self.db.query( "SELECT * INTO TEMPORARY TABLE fix_db_annotations_" + str( assignment['id'] ) + " FROM annotation WHERE assignment = " + str( assignment['id'] ) + " AND restype = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			observations = self.db.query( "SELECT * INTO TEMPORARY TABLE fix_db_observations_" + str( assignment['id'] ) + " FROM observation WHERE assignment = " + str( assignment['id'] ) + " AND restype = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			
			# Delete the CURRENT and VALIDATED results related stuff
			self.db.query( "DELETE FROM minutia WHERE assignment = " + str( assignment['id'] ) + " AND restype IN (SELECT ID FROM resulttype WHERE short = 'CURRENT' OR short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			self.db.query( "DELETE FROM annotation WHERE assignment = " + str( assignment['id'] ) + " AND restype IN (SELECT ID FROM resulttype WHERE short = 'CURRENT' OR short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			self.db.query( "DELETE FROM observation WHERE assignment = " + str( assignment['id'] ) + " AND restype IN (SELECT ID FROM resulttype WHERE short = 'CURRENT' OR short = 'VALIDATED') AND resstep = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			
			# Delete the CURRENT result
			self.db.query( "DELETE FROM result WHERE assignment = " + str( assignment['id'] ) + " AND type = (SELECT ID FROM resulttype WHERE short = 'CURRENT') AND step = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			
			# "Move" the VALIDATED to the CURRENT result
			self.db.query( "UPDATE result SET type = (SELECT ID FROM resulttype WHERE short = 'CURRENT') WHERE assignment = " + str( assignment['id'] ) + " AND type = (SELECT ID FROM resulttype WHERE short = 'VALIDATED') AND step = (SELECT ID FROM resultstep WHERE short = 'COMPARISON');" )
			
			# Update the saved data so it now refers to CURRENT results, and no longer VALIDATED
			self.db.query( "UPDATE fix_db_minutiae_" + str( assignment['id'] ) + " SET restype = (SELECT ID FROM resulttype WHERE short = 'CURRENT');" )
			self.db.query( "UPDATE fix_db_annotations_" + str( assignment['id'] ) + " SET restype = (SELECT ID FROM resulttype WHERE short = 'CURRENT');" )
			self.db.query( "UPDATE fix_db_observations_" + str( assignment['id'] ) + " SET restype = (SELECT ID FROM resulttype WHERE short = 'CURRENT');" )
			
			# Restore the (former VALIDATED, now) CURRENT result's related stuff
			self.db.query( "INSERT INTO annotation SELECT * FROM fix_db_annotations_" + str( assignment['id'] ) + ";" )
			self.db.query( "INSERT INTO minutia SELECT * FROM fix_db_minutiae_" + str( assignment['id'] ) + ";" )
			self.db.query( "INSERT INTO observation SELECT * FROM fix_db_observations_" + str( assignment['id'] ) + ";" )

			# Kill the temp tables

		
		
class HelpAction( Action ):
	def __init__( self, actions ):
		self.actions = actions
		
	def run( self, args ):
		if len( args ) < 1:
			for a in self.actions:
				self.helpOn( a )
				print ''
		else:
			self.helpOn( args[0] )
			
	def helpOn( self, name ):
		print '* ' + name
		action = self.actions[name]
		help = action.help()
		print help[0]
		if help[1]:
			print help[1]
		
	def help( self ):
		return ( 'Gets help about other actions', '' )
		
def main():
	
	actions = {
		'unsubmit': UnsubmitAction(),
		'fix_3_to_4_nij': Fix3To4NIJ(),
	}
	actions['help'] = HelpAction( actions )
	
	parser = optparse.OptionParser( usage = 'ACTION [options]' )
	parser.set_description( 'Performs various fixes, depending on ACTION. Action may be one of ' + ( ', ' ).join( ["'" + x + "'" for x in actions.keys()] ) + '.' )
	
	options, args = parser.parse_args()

	if len( args ) < 1:
		print 'ERROR: you must specify an action as 1st argument. Try --help'
		return 1

	actionName = args[0]
	if actionName not in actions:
		print 'ERROR: unknown action: ' + actionName + '. Try --help'
		return 1
		
	action = actions[actionName]
	
	ret = action.run( args[1:] )
	
	if ret is None:
		return 0
	elif type( ret ) is types.BooleanType:
		return 0 if ret else 1
	else:
		return ret
	
	return 0
	
if __name__ == '__main__':
	sys.exit( main() )
