from wxPython.wx import *
import httplib, htmllib, formatter, urlparse, re, gzip, StringIO

def find_communicator(name):
    """Find and return a class object for given name.
    """
    if name in _communicators.keys():
        return _communicators[name]
    else:
        return None

class CommunicatorBase:

    """Base class for Communicators.

    """

    def __init__(self, params):
        """Initialize communicator and remember params.
        """
        self._params = params
        
    def getTopics(self):
        """Return list of topics, which is empty in this class.
        """
        return []

    def getSubTopics(self, label):
        """Return list of sub-topics, which is empty in this class.
        """
        return [] 

    def getThreadsForTopic(self, data):
        """Return thread-list for a topic, which is empty in this class.
        """
        return []
    
class TF2Communicator(CommunicatorBase):

    """Implements a '2ch.net' thread-float style BBS interface.

    """

    HEADER = {'User-Agent': 'Monazilla/1.00 (Monarch/dev)',
              'Accept-Encoding': 'gzip'}
    DATA = None
    PATH = "http://www.ff.iij4u.or.jp/~ch2/bbsmenu.html"
    HOST = "www.ff.iij4u.or.jp"
    LIST = "subject.txt"

    class _Servers:

        """Internal class which actually communicate with servers.

        """

        def __init__(self):
            """Initialize. Extend chain to superclass.
            """

        def get(self, path, data, header):
            """Get content via HTTP for path with data and header.
            """
            ret = None
            if path:
                try:
                    (scheme, location, objpath, param, query, fid) = \
                             urlparse.urlparse(path, 'http')
                    con = httplib.HTTPConnection(location)
                    con.request('GET', objpath, data, header)
                    response = con.getresponse()
                    ret = response.read()

                    if response.getheader('Content-Encoding', None)=='gzip':
                        gzfile = StringIO.StringIO(ret)
                        gzstream = gzip.GzipFile(fileobj=gzfile)
                        ret = gzstream.read()
                except:
                    ret = None
            # else
            return ret

    class _LogParser:

        """Internal class which parses thread log.

        """
        
        def __init__(self):
            """Initialize. Do nothing.
            """

        def parse(self, content):
            """Parse given content and return list of post data.
            """
            ret = []
            content = content.replace('\0', ' ')
            entries = content.splitlines()
            if not entries:
                return []
            # else
            cnt = 1
            for entry in entries:
                # name, mail, dateid, content, ext
                post = [cnt, '', '', '', '', '']
                values = entry.split('<>')
                for j in range(min(5, len(values))):
                    post[j+1] = values[j]
                cnt+=1
                ret.append(post)
            return ret

    class _ThreadListParser:

        """Internal class which parses '.dat' list.

        """

        def __init__(self):
            """Initialize. Build regex for scanning threads.
            """
            patternstr = '^([0-9]*\.dat)\<\>(.*)\(([0-9]*)\)$'
            self.rex = re.compile(patternstr, re.M)

        def parse(self, content):
            """Parse HTML-encoded list of threads and return the list.
            """
            ret = []
            entries = self.rex.findall(content)
            if entries:
                for entry in entries:
                    # tid, title, stat, update, alive, \
                    #    pri, focus, post, desc, data
                    tinfo = [0, 0, 0, 'N/A', 'N/A', 0, 0,
                             'NoTitle', 'NoDesc', None]
                    dn, tn, np = entry
                    tinfo[0] = int(dn[:-4])
                    tinfo[6] = int(np)
                    tinfo[7] = tn
                    tinfo[8] = tn
                    tinfo[9] = dn
                    ret.append(tinfo)
            return ret
        
    class _TopicListParser(htmllib.HTMLParser):

        """Internal class which parses BBSmenu.

        """

        def __init__(self, formatterobj=formatter.NullFormatter()):
            """Initialize. Create HTMLParser and nodecache.
            """
            htmllib.HTMLParser.__init__(self, formatterobj)
            self.text = None
            self.attr = None
            self.nodecache = []
            self.subnodecache = ['root']

        def clear(self):
            """Clear and reset nodecache.
            """
            self.reset()
            if self.nodecache:
                del self.nodecache
            self.nodecache = []
            if self.subnodecache:
                del self.subnodecache
            self.subnodecache = ['root']
        
        def handle_data(self, data):
            """(override) Accumulate content data.
            """
            if self.text is not None:
                self.text += data
    
        def start_b(self, attr):
            """(override) Register previous node and prepare new one.
            """
            self.nodecache.append(self.subnodecache)
            self.subnodecache = []
            self.text = ''

        def end_b(self):
            """(override) Register previous subnode and prepare new one.
            """
            self.subnodecache.append(self.text)
            self.text = ''

        def start_a(self, attr):
            """(override) Remember attributes for <a> tag.
            """
            tmpdic = {}
            for k, v in attr:
                tmpdic[k.upper()] = v
            if 'TARGET' not in tmpdic.keys():
                self.attr = tmpdic
            else:
                self.attr = None
            self.text = ''
                
        def end_a(self):
            """(override) Add HREF attribute to subnode cache.
            """
            if self.attr:
                self.subnodecache.append((self.text, self.attr['HREF']))
            self.text = ''
            self.attr = None

        def parse(self, content):
            """Feed content to the parser.
            """
            self.clear()
            self.feed(content)
            return self.nodecache
        
    def __init__(self, params):
        """Create server and perser objects.
        """
        CommunicatorBase.__init__(self, params)
        self.servers = self._Servers()
        self.topicparser = self._TopicListParser()
        self.threadparser = self._ThreadListParser()
        self.logparser = self._LogParser()
        self.currentpath = ''

    def getTopics(self):
        """Return list of topics for specified topic.
        """
        ret = []
        dobj = self.servers.get(self.PATH, self.DATA, self.HEADER)
        if dobj:
            self.treedata = self.topicparser.parse(dobj)
            for i in self.treedata:
                ret.append(i[0])
        else:
            ret = None
        return ret

    def getSubTopics(self, label):
        """Return list of sub-topics for specified topic.
        """
        for i in self.treedata:
            if i[0] == label:
                return i[1:]
            
    def getThreads(self, data=None):
        """Return list of threads for specified topic.
        """
        if data:
            self.currentpath = data
            sbjpath = data + self.LIST
            dobj = self.servers.get(sbjpath, self.DATA, self.HEADER)
            return self.threadparser.parse(dobj)
        return []

    def getThreadData(self, data=None):
        """Get posts from specified thread and return in HTML format.
        """
        ret = {}
        
        if data:
            fmt_title = '<font size=+2 color=red>%s</font><br>'
            fmt_post = '%d : <a href="mailto:%s"><font color=green>%s' \
                       '</font></a>%s: <br> %s<p>' 
            logpath = self.currentpath+'dat/'+data
            dobj = self.servers.get(logpath, self.DATA, self.HEADER)
            lflags = []
            for post in self.logparser.parse(dobj):
                num, name, mail, dateid, log, ext = post
                if ext.strip():
                    lflags.append(fmt_title %(ext))
                    ret['label'] = ext
                lflags.append(fmt_post %(num, mail, name, dateid, log))
            
            ret['log'] = ''.join(lflags)
            return ret
        # else
        return ret
            
_communicators = {
    # base class is needless to be accessed via find_communicator.
    # 'Base': (CommunicatorBase),
    
    'TF2': (TF2Communicator),
    }
