"""Gopher protocol client interface."""
# gopher://uninformativ.de/0/gopher/RFCs/rfc1436.txt
#
# repo:
# https://bitbucket.org/kveroneau/pythonexperiments/src/281fe19d536d2329ca3cad7b
2aac1c03807be791/gopher/?at=default

import socket

# Recognized file types
A_TEXT       = '0'
A_MENU       = '1'
A_CSO        = '2'
A_ERROR      = '3'
A_MACBINHEX  = '4'
A_PCBINHEX   = '5'
A_UUENCODED  = '6'
A_INDEX      = '7'
A_TELNET     = '8'
A_BINARY     = '9'
A_DUPLICATE  = '+'
A_SOUND      = 's'
A_EVENT      = 'e'
A_CALENDAR   = 'c'
A_HTML       = 'h'
A_TN3270     = 'T'
A_MIME       = 'M'
A_IMAGE      = 'I'
A_WHOIS      = 'w'
A_QUERY      = 'q'
A_GIF        = 'g'
A_HTML       = 'h'          # HTML file
A_WWW        = 'w'          # WWW address
A_PLUS_IMAGE = ':'
A_PLUS_MOVIE = ';'
A_PLUS_SOUND = '<'

TEXT_TYPES = ['0', '1', '7']

class GopherType(object):
    text_only = True
    def __init__(self, data, gtype, client):
        self._data = data
        self._gtype = gtype
        self._client = client
    def get_data(self):
        return self._data
    def __str__(self):
        return self._data
    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self._gtype)

class TextFile(GopherType):
    def __str__(self):
        return '\n'.join(self._data)

class GopherMenu(GopherType):
    _menu = None
    def get_data(self):
        if self._menu is None:
            self._menu = []
            for item in self._data:
                gtype = item[0]
                entry = item[1:].split('\t')
                self._menu.append({'type':gtype, 'name':entry[0], 'selector':'%s%s' % (gtype,entry[1]), 'host':entry[2], 'port':entry[3]})
        return self._menu
    def name_list(self):
        names = []
        menu = self.get_data()
        for entry in menu:
            names.append(entry['name'])
        return names
    def display_names(self):
        print '\n'.join(self.name_list())
    def get_entry(self, index):
        entry = self.get_data()[index]
        if self._client._host == entry['host'] and self._client._port == entry['port']:
            return self._client.get_selector(entry['selector'])
        g = Gopher(entry['host'], entry['port'])
        return g.get_selector(entry['selector'])
    def __str__(self):
        return '\n'.join(self.name_list())

class Gopher(object):
    def __init__(self, host, port=70):
        self._host, self._port = host, int(port)
    def send_selector(self, selector, query=None):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((self._host, self._port))
        if query is not None:
            selector += '\t%s' % query
        sock.sendall('%s\r\n' % selector)
        sock.shutdown(1)
        return sock.makefile('rb')
    def path_to_selector(self, path):
        return path[2:]
    def get_textfile(self, selector, query=None):
        f = self.send_selector(selector, query)
        lines = []
        while True:
            line = f.readline()
            if not line:
                break
            if line[-2:] == '\r\n':
                line = line[:-2]
            elif line[-1:] in '\r\n':
                line = line[:-1]
            if line == '.':
                break
            if line[:2] == '..':
                line = line[1:]
            lines.append(line)
        return lines
    def get_binary(self, selector):
        f = self.send_selector(selector)
        return f.read()
    def get_selector(self, selector, query=None):
        gtype = selector[0]
        selector = selector[1:]
        if gtype in TEXT_TYPES:
            data = self.get_textfile(selector, query)
            if gtype == '0':
                return TextFile(data, gtype, self)
            else:
                return GopherMenu(data, gtype, self)
        return self.get_binary(selector)
    def get_root_menu(self):
        return self.get_selector('1')

def test():
    """Trivial test program."""
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('--host', dest='host', default='gopher.floodgap.com', help='Gopher hostname to connect to')
    parser.add_option('-p', '--port', type='int', dest='port', default=70, help='Gopher port to connect to')
    parser.add_option('-s', '--selector', dest='selector', default='0/gopher/proxy', help='Gopher selector to use')
    parser.add_option('-q', '--query', dest='query', help='Gopher search query to use.')
    options, args = parser.parse_args()
    if options.host != 'gopher.floodgap.com' and options.selector == '0/gopher/proxy':
        options.selector = '1'
    if len(args) == 2:
        options.host, options.selector = args[0:2]
    g = Gopher(options.host, options.port)
    data = g.get_selector(options.selector, options.query)
    print data

# Run the test when run as script
if __name__ == '__main__':
    test()