"""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()