#!/usr/bin/python2.6

import sys

from PySide import QtCore
from PySide import QtGui
from PySide import QtDeclarative
import os

# Comment the line below if you don't want to use OpenGL for QML rendering or if it is not supported
#from PySide import QtOpenGL

from math import floor, fmod
from time import time

from hashlib import sha1
from time import time
from base64 import b32decode
from struct import pack, unpack_from
import gconf
import hmac

import dbus
import dbus.mainloop.glib

from subprocess import call

class Host(QtCore.QObject):
	def __init__(self, root):
		QtCore.QObject.__init__(self)
		self._root = root

	def _get_root(self):
		return self._root

	rootChanged = QtCore.Signal()

	root = QtCore.Property(str, _get_root, notify=rootChanged)

class PasscodeGenerator(QtCore.QObject):
	def __init__(self, secret=str()):
		QtCore.QObject.__init__(self)
		self._fixed = len(secret) > 0
		if (self._fixed):
			self._secret = self._secrets = secret
			self._name = self._names = 'PIN'
		else:
			self._secrets = gconf.client_get_default().get_string('/apps/ControlPanel/PyGAuth/Secret') or str()
			self._secret = self._secrets.split(",")[0]
			self._names = gconf.client_get_default().get_string('/apps/ControlPanel/PyGAuth/Name') or str()
			self._name = self._names.split(",")[0]
		if self._names == "":
			self._name = "No accounts"
		self._index = 0
		self._pin = 'No secret'
		value = gconf.client_get_default().get_bool('/apps/ControlPanel/PyGAuth/Inverted')
		if value is None:
			value = False
		self._inverted = value
		self._timer = QtCore.QTimer(self)
		self._timer.setSingleShot(True)
		self._timer.timeout.connect(self.pinExpired)
		self.secretChanged.connect(self.updatePin)
		self.validityUpdated.connect(self.updatePin)
		self.indexUpdated.connect(self.changeIndex)
		#self.connect(self._timer, SIGNAL("timeout()"), self.pinExpired)
		#self.connect(self, SIGNAL("on_new_secret()"), self.updatePin)
		#self.connect(self, SIGNAL("on_new_validity()"), self.updatePin)
		self.pinExpired()

	def setFromURI(self, uri):
		validprefix = 'otpauth://totp/'
		if (uri[:len(validprefix)] != validprefix):
			return False
		uri = uri[len(validprefix):]
		name = uri[:uri.find('?')]
		params = uri[len(name)+1:].split('&')
		secret = None
		for p in params:
			s = p.split('=', 1)
			if s[0] == 'secret':
				secret = s[1]
		if secret is None:
			return False
		if len(self._secrets) == 0:
			self._secrets = secret
		else:
			self._secrets = secret + "," + self._secrets
		if len(self._names) == 0:
			self._names = name.translate(None, ",")
		else:
			self._names = name.translate(None, ",") + "," + self._names
		self.name = name
		self.secret = secret
		self.index = 0
		self.saveSecrets()
		self.saveNames()
		return True

	def _get_secret(self):
		return self._secret

	def _set_secret(self, secret):
		if (secret != self._secret):
			self._secret = secret
			self.secretChanged.emit()

	def _get_name(self):
		return self._name

	def _set_name(self, name):
		if (name != self._name):
			self._name = name
			self.nameChanged.emit()

	def _get_index(self):
		return self._index

	def _set_index(self, index):
		if (index != self._index and index >= 0 and index < len(self.names.split(","))):
			self._index = index
			self.indexUpdated.emit()

	def _get_pin(self):
		return self._pin

	def _get_validity(self):
		return 30 - fmod(time(), 30)

	def _get_names(self):
		return self._names

	def _get_inverted(self):
		return self._inverted

	def _set_inverted(self, inverted):
		if inverted != self.inverted:
			self._inverted = inverted
			self.invertedChanged.emit()

	@QtCore.Slot()
	def pinExpired(self):
		self.validityUpdated.emit()
		self._timer.setInterval(self._get_validity() * 1000)
		self._timer.start()

	@QtCore.Slot()
	def updatePin(self):
		if (self._secret is None or len(self._secret) == 0):
			return

		# The secret is recorded in base32
		secret = self._secret.upper().translate(None, "-= ")

		if (len(secret) * 5) % 8 == 0:
			result = b32decode(self._secret, True)
		else:
			alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
			buffer = 0
			next = 0
			bitsLeft = 0
			result = bytearray()
			for c in secret:
				buffer <<= 5
				buffer |= alphabet.index(c) & 31
				bitsLeft += 5
				if bitsLeft >= 8:
					result.append((buffer >> (bitsLeft - 8)) & 255)
					bitsLeft -= 8

		# The timer is every 30 seconds
		now = time()
		now = int( floor( now / 30 ) )

		# The value is an 8-byte big endian (Java) interval number
		value = pack("!q", now)

		# The hashing algorithm is HMACSHA1
		hm = hmac.new(str(result), value, sha1)

		# Get the resulting binary digest
		digest = hm.digest()

		# Truncate the hash
		offset = unpack_from("b", digest[-1])[0] & 0xf
		pin = (unpack_from("!i", digest, offset)[0] & 0x7FFFFFFF) % 10**6
		if (pin != self._pin):
			self._pin = str(pin).zfill(6)
			self.pinChanged.emit()

	@QtCore.Slot()
	def saveSecrets(self):
		gconf.client_get_default().set_string('/apps/ControlPanel/PyGAuth/Secret', self._secrets)

	@QtCore.Slot()
	def saveNames(self):
		gconf.client_get_default().set_string('/apps/ControlPanel/PyGAuth/Name', self._names)

	@QtCore.Slot()
	def changeIndex(self):
		self.secret = self._secrets.split(",")[self.index]
		self.name = self._names.split(",")[self.index]

	@QtCore.Slot()
	def removeCurrent(self):
		l = self._secrets.split(",")
		del l[self.index]
		self._secrets = ",".join(l)
		l = self._names.split(",")
		del l[self.index]
		self._names = ",".join(l)
		self.saveSecrets()
		self.saveNames()
		if self.index > 0:
			self.index = self.index - 1
		else:
			self.name = self.secret = ""
		if self._names == "":
			self.name = "No accounts"
			self._pin = "No secret"
			self.pinChanged.emit()

	@QtCore.Slot()
	def openSettings(self):
		#service = 'com.nokia.DuiControlPanel'
		#path = '/'
		#interface = 'com.nokia.DuiControlPanelIf'
		##method = 'appletPage'
		## Get the session bus
		#bus = dbus.SessionBus()
		#proxy = bus.get_object(service, path)
		#connection = dbus.Interface(proxy, interface)
		#connection.appletPage('GAuth')
		call(['dbus-send','--session','--dest=com.nokia.DuiControlPanel','--print-reply','/','com.nokia.DuiControlPanelIf.appletPage',"string:GAuth"])

	@QtCore.Slot()
	def readConfig(self):
		self._secrets = gconf.client_get_default().get_string('/apps/ControlPanel/PyGAuth/Secret') or str()
		self._names = gconf.client_get_default().get_string('/apps/ControlPanel/PyGAuth/Name') or str()
		if self.index >= len(self._names.split(",")):
			self.index = 0
		self.secret = self._secrets.split(",")[self.index]
		if self._names == "":
			self.name = "No accounts"
		else:
			self.name = self._names.split(",")[self.index]
		value = gconf.client_get_default().get_bool('/apps/ControlPanel/PyGAuth/Inverted')
		if value is None:
			value = True
		self.inverted = value

	secretChanged = QtCore.Signal()
	nameChanged = QtCore.Signal()
	pinChanged = QtCore.Signal()
	validityUpdated = QtCore.Signal()
	indexUpdated = QtCore.Signal()
	invertedChanged = QtCore.Signal()

	secret = QtCore.Property(str, _get_secret, _set_secret, notify=secretChanged)
	name = QtCore.Property(str, _get_name, _set_name, notify=nameChanged)
	pin = QtCore.Property(str, _get_pin, notify=pinChanged)
	validity = QtCore.Property(float, _get_validity, notify=validityUpdated)
	index = QtCore.Property(int, _get_index, _set_index, notify=indexUpdated)
	inverted = QtCore.Property(bool, _get_inverted, _set_inverted, notify=invertedChanged)

	names = QtCore.Property(str, _get_names)

def main():

    # Enable glib main loop support
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    app = QtGui.QApplication(sys.argv)
    view = QtDeclarative.QDeclarativeView()

    # Comment the two lines below if you don't want to use OpenGL for QML rendering or if it is not supported
    #glw = QtOpenGL.QGLWidget()
    #view.setViewport(glw)

    pcg = PasscodeGenerator()

    if (len(app.arguments()) == 2):
        pcg.setFromURI(app.arguments()[1])

    view.rootContext().setContextProperty('passcodeGenerator', pcg)

    if os.path.exists(os.path.join('qml', 'main.qml')):
	view.rootContext().setContextProperty('host', Host(os.getcwd()+'/'))
        view.setSource(os.path.join('qml','main.qml'))
    else:
	view.rootContext().setContextProperty('host', Host('/opt/pygauth/'))
        view.setSource('/opt/pygauth/qml/main.qml')

    view.showFullScreen()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
