# Thanks to http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI

import clr
clr.AddReferenceByPartialName('System.Xml')

from System import Uri, Array, Byte
from System.IO import StreamReader
from System.Net import HttpWebRequest, HttpRequestHeader, WebClient
from System.Text import Encoding
from System.Xml import XmlReaderSettings, XmlReader

class Subscription(object):
    id = None
    title = None
    firstitemmsec = None

    def __repr__(self):
        return u'<Subscription title=%r>' % (self.title,)


class Category(object):
    id = None
    label = None


class Unread(object):
    id = None
    count = None

    def __repr__(self):
        return u'<Unread count=%r id=%r>' % (self.count, self.id)


class Callback(object):
    def __init__(self):
        self.callbacks = []

    def addCallback(self, cb):
        self.callbacks.append(cb)
        return self

    def fire(self, *args):
        for callback in self.callbacks:
            args = [callback(*args)]


class GoogleReader(object):
    typeMap = {u'subscriptions': Subscription,
               u'categories': Category,
               u'unreadcounts': Unread}

    clientName = 'greadernotifier/0.0.1'

    def __init__(self):
        self.readerSettings = readerSettings = XmlReaderSettings()
        readerSettings.IgnoreComments = True
        readerSettings.IgnoreWhitespace = True

    def connect(self, email, password):
        data = Encoding.UTF8.GetBytes(u'service=reader&continue=http://www.google.com&source=%s&Email=%s&Passwd=%s' % (self.clientName, email, password))

        request = HttpWebRequest.Create(u'https://www.google.com/accounts/ClientLogin')
        request.Method = u'POST'
        request.ContentType = u'application/x-www-form-urlencoded'
        request.ContentLength = data.Length

        callback = Callback()

        def _writeRequest(result):
            request = result.AsyncState
            stream = request.EndGetRequestStream(result)
            stream.Write(data, 0, data.Length)
            stream.Close()
            request.BeginGetResponse(_readResponse, request)

        def _readResponse(result):
            request = result.AsyncState
            response = request.EndGetResponse(result)
            stream = response.GetResponseStream()
            reader = StreamReader(stream)
            self.serviceData = serviceData = dict(line.split(u'=', 1) for line in reader.ReadToEnd().strip('\n').split('\n'))
            stream.Close()
            self.initializeCookie()
            self.cacheSubscriptions().addCallback(lambda *args: callback.fire())

        request.BeginGetRequestStream(_writeRequest, request)
        return callback

    def initializeCookie(self):
        self.cookie = u'SID=%(SID)s;Domain=.google.com' % self.serviceData

    def parseValue(self, reader, typ=None):
        reader.Read()

        if reader.Name == u'number':
            return reader.ReadElementContentAsLong()
        elif reader.Name == u'string':
            return reader.ReadElementContentAsString()
        elif reader.Name == u'list':
            tn = reader.GetAttribute('name')
            reader.Read()
            reader.MoveToElement()

            l = []
            while not reader.EOF:
                if reader.Name == u'list' and not reader.IsStartElement():
                    break
                l.append(self.parseValue(reader.ReadSubtree(), self.typeMap[tn]))
                reader.Read()
            return l
        elif reader.Name == u'object':
            if typ is None:
                assert False

            obj = typ()

            reader.ReadStartElement(u'object')
            while not reader.EOF:
                if reader.Name == u'object' and not reader.IsStartElement():
                    break
                attrName = reader.GetAttribute(u'name')
                value = self.parseValue(reader.ReadSubtree(), None)
                if value is not None:
                    setattr(obj, attrName, value)

                if reader.IsEmptyElement:
                    reader.Read()
                else:
                    reader.ReadEndElement()

            reader.ReadEndElement()

            return obj

        print u'WARNING: %r was not parsed!' % (reader.Name,)
        reader.Skip()
        return None

    def _get(self, uri):
        def _callback(sender, e):
            return e.Result

        callback = Callback().addCallback(_callback)

        client = WebClient()
        client.Headers[HttpRequestHeader.Cookie] = self.cookie
        client.OpenReadCompleted += callback.fire
        client.OpenReadAsync(Uri(u'http://www.google.com/reader/api/0/' + uri))

        return callback

    def cacheSubscriptions(self):
        def _cache(subs):
            self.subscriptions = subscriptions = dict((sub.id, sub) for sub in subs)
        return self.getSubscriptions().addCallback(_cache)

    def getSubscriptions(self):
        def _readSubscriptions(stream):
            reader = XmlReader.Create(stream, self.readerSettings)
            reader.Read()
            reader.ReadStartElement(u'object')
            v = self.parseValue(reader.ReadSubtree())
            stream.Close()
            return v

        return self._get(u'subscription/list?output=xml').addCallback(_readSubscriptions)

    def getUnread(self):
        def _readUnread(stream):
            reader = XmlReader.Create(stream, self.readerSettings)
            reader.Read()
            reader.ReadStartElement('object')
            maxUnread = self.parseValue(reader.ReadSubtree())
            reader.ReadEndElement()
            v = self.parseValue(reader.ReadSubtree())
            stream.Close()
            return v, maxUnread

        return self._get(u'unread-count?all=true&output=xml').addCallback(_readUnread)

