import sys
import clr
clr.AddReferenceByPartialName('System.Drawing')
clr.AddReferenceByPartialName('System.Windows.Forms')

from Microsoft.Win32 import Registry

import System
from System import Drawing, DateTime
from System.IO import Path, Directory
from System.Timers import Timer
from System.Windows import Forms
from System.Windows.Forms import Application

import greader

class Setting(object):
    '''Descriptor for registry-backed settings'''

    def __init__(self, name, default=None):
        self.name = name
        self.default = default

    def __get__(self, instance, owner):
        value = instance.getValue(self.name)
        if value is None and self.default is not None:
            value = self.default
            instance.setValue(self.name, value)
        return value

    def __set__(self, instance, value):
        return instance.setValue(self.name, value)


class SettingsManager(object):
    '''Manages Setting descriptors'''

    email = Setting(u'email')
    password = Setting(u'password')
    interval = Setting(u'interval', default=60 * 60 * 1000)
    tooltipDuration = Setting(u'tooltipDuration', default=3000)
    maxUnreadCount = Setting(u'maxUnreadCount', default=8)
    appPath = Setting(u'appPath', default=Directory.GetCurrentDirectory())

    def __init__(self):
        self.root = Registry.CurrentUser.CreateSubKey(u'Software\\Slipgate\\GoogleReaderNotifier')

    def getValue(self, key):
        return self.root.GetValue(key)

    def setValue(self, key, value):
        self.root.SetValue(key, value)


class ConfigurationForm(Forms.Form):
    def __init__(self, settings):
        self.settings = settings

        self.controls = {}

        self.Text = u'Google Reader Notifier Options'
        self.AutoSize = True
        self.Size = Drawing.Size(10, 10)
        self.StartPosition = Forms.FormStartPosition.CenterScreen

        layout = Forms.FlowLayoutPanel()
        layout.AutoSize = True

        def PasswordTextBox():
            tb = Forms.TextBox()
            tb.UseSystemPasswordChar = True
            return tb

        def addLabelledControl(name, controlFactory, setting, setter, coerceFrom=unicode):
            label = Forms.Label()
            label.Text = name
            label.TextAlign = Drawing.ContentAlignment.MiddleLeft
            label.Size = Drawing.Size(200, 20)

            control = controlFactory()
            control.Size = Drawing.Size(200, 20)
            layout.SetFlowBreak(control, True)

            if setting is not None:
                control.Text = unicode(setting)
            self.controls[setter] = (control, coerceFrom)

            layout.Controls.Add(label)
            layout.Controls.Add(control)

            return control

        def _setEmail(value):
            settings.email = value
        addLabelledControl(u'Google Reader e-mail', Forms.TextBox, settings.email, _setEmail)

        def _setPassword(value):
            settings.password = value
        addLabelledControl(u'Google Reader password', PasswordTextBox, settings.password, _setPassword)

        def _setInterval(value):
            settings.interval = value
        addLabelledControl(u'Check interval (milliseconds)', Forms.TextBox, settings.interval, _setInterval, int)

        buttonLayout = Forms.FlowLayoutPanel()
        buttonLayout.AutoSize = True
        buttonLayout.FlowDirection = Forms.FlowDirection.RightToLeft
        buttonLayout.Dock = Forms.DockStyle.Bottom

        okButton = Forms.Button()
        okButton.Text = u'OK'
        okButton.DialogResult = Forms.DialogResult.OK
        okButton.Click += self.onOK
        self.AcceptButton = okButton

        cancelButton = Forms.Button()
        cancelButton.Text = u'Cancel'
        self.CancelButton = cancelButton

        buttonLayout.Controls.Add(cancelButton)
        buttonLayout.Controls.Add(okButton)

        self.Controls.Add(buttonLayout)
        self.Controls.Add(layout)

    def onOK(self, sender, e):
        for setter, (control, coerceFrom) in self.controls.iteritems():
            setter(coerceFrom(control.Text))


class Notifier(object):
    defaultNotifyText = u'Google Reader Notifier'

    def __init__(self):
        self.settings = settings = SettingsManager()
        self.icon = Drawing.Icon(Path.Combine(self.settings.appPath, u'greader.ico'))

        self.initMenu()
        self.initNotifyIcon()

        if settings.email is None or settings.password is None:
            self.showOptions()

        self.notifyIcon.Text = u'Connecting...'
        def _initDone():
            self.timer = timer = Timer(settings.interval)
            timer.Elapsed += self.timerElapsed
            timer.AutoReset = True
            timer.Enabled = True

            self.notifyIcon.Text = self.defaultNotifyText
            self.displayUnread()

        self.gr = gr = greader.GoogleReader()
        gr.connect(self.settings.email, self.settings.password).addCallback(_initDone)

    def initMenu(self):
        self.menuStrip = menuStrip = Forms.ContextMenuStrip()

        item = Forms.ToolStripMenuItem()
        item.Text = u'&Options'
        item.Click += self.onOptionMenu
        menuStrip.Items.Add(item)

        item = Forms.ToolStripMenuItem()
        item.Text = u'E&xit'
        item.Click += self.onExitMenu
        menuStrip.Items.Add(item)

    def initNotifyIcon(self):
        self.notifyIcon = notifyIcon = Forms.NotifyIcon()
        notifyIcon.Icon = self.icon
        notifyIcon.Text = self.defaultNotifyText
        notifyIcon.Visible = True
        notifyIcon.ContextMenuStrip = self.menuStrip
        notifyIcon.BalloonTipIcon = Forms.ToolTipIcon.Info

        notifyIcon.MouseUp += self.onClick
        notifyIcon.DoubleClick += self.onBalloonClick
        notifyIcon.BalloonTipClicked += self.onBalloonClick

    def timerElapsed(self, sender, e):
        self.displayUnread()

    def displayUnread(self):
        gr = self.gr
        self.notifyIcon.Text = u'Getting unread items...'

        def _displayUnread((unread, unreadCap)):
            unread = [u for u in unread if u.count > 0 and u.id in gr.subscriptions]
            unread.sort(key=lambda x: (x.count, gr.subscriptions[x.id]), reverse=True)
            if unread:
                text = []
                otherCount = 0
                totalUnread = 0
                maxUnreadCount = self.settings.maxUnreadCount
                capped = False

                for i, u in enumerate(unread):
                    localCapped = False
                    count = u.count
                    if count >= unreadCap:
                        localCapped = True
                        capped = True
                    totalUnread += count
                    if i >= maxUnreadCount:
                        otherCount += count
                    else:
                        text.append(u'%s (%d%s)' % (gr.subscriptions[u.id].title, count, (u'', u'+')[localCapped]))

                if otherCount > 0:
                    text.append(u'... %d others (%d)' % (len(unread) - maxUnreadCount, otherCount))

                notifyIcon = self.notifyIcon
                notifyIcon.BalloonTipTitle = u'%d%s unread items' % (totalUnread, (u'', u'+')[capped])
                notifyIcon.BalloonTipText = u'\n'.join(text)
                notifyIcon.ShowBalloonTip(self.settings.tooltipDuration)

            now = DateTime.Now
            self.notifyIcon.Text = u'%s (%s %s)' % (self.defaultNotifyText, now.ToLongDateString(), now.ToLongTimeString())
            self.resetTimer()

        self.gr.getUnread().addCallback(_displayUnread)

    def showOptions(self):
        ConfigurationForm(self.settings).ShowDialog()

    def resetTimer(self):
        self.timer.Enabled = False
        self.timer.Enabled = True

    def onClick(self, sender, e):
        if e.Button == Forms.MouseButtons.Left:
            self.displayUnread()

    def onBalloonClick(self, sender, e):
        try:
            System.Diagnostics.Process.Start(u'http://www.google.com/reader/')
        except:
            pass

    def onExitMenu(self, sender, e):
        self.notifyIcon.Visible = False
        Application.Exit()

    def onOptionMenu(self, sender, e):
        if self.showOptions() == Forms.DialogResult.OK:
            self.initGoogleReader()

def install():
    ipywPath = Path.Combine(Path.GetDirectoryName(sys.executable), u'ipyw.exe')
    ourPath = Path.GetFullPath(sys.argv[0])
    iconPath = Path.Combine(Path.GetDirectoryName(ourPath), u'greader.ico')

    settings = SettingsManager()
    settings.appPath = Path.GetDirectoryName(ourPath)

    # XXX: why the hell does this have "New" prefixed onto it?
    clr.AddReferenceByPartialName('Interop.NewIWshRuntimeLibrary')

    # XXX: you might want to sit down before reading the rest of this. seriously.
    from Interop.NewIWshRuntimeLibrary import WshShellClass
    shell = WshShellClass()
    linkTarget = Path.Combine(shell.SpecialFolders.Item(u'Startup')[0], u'GoogleReaderNotifier.lnk')
    link = shell.CreateShortcut(linkTarget)
    link.TargetPath = ipywPath
    link.Arguments = ourPath
    link.IconLocation = iconPath
    link.Save()

def main(args):
    if args and args[0] == '-install':
        install()
    else:
        Application.EnableVisualStyles()
        n = Notifier()
        Application.Run()

if __name__ == '__main__':
    main(sys.argv[1:])
