#!/usr/bin/python3
import enum, socket, traceback, subprocess, curses
from curses.textpad import Textbox, rectangle

class MenuType(str, enum.Enum):
   DOC = '0'
   MENU = '1'
   QUERY = '7'
   BIN = '9'
   HTML = 'h'
   INFO = 'i'
   SOUND = 's'
   IMAGE = 'I'
   GIF = 'g'

ESC_WHITE = '\033[1;37m'
ESC_CYAN  = '\033[1;36m'
ESC_RESET = '\033[0m'
ESC_RED   = '\033[1;31m'
ESC_GREEN = '\033[1;32m'
ESC_YELLO = '\033[1;33m'
ESC_BLUE  = '\033[1;34m'

class Fetcher(object):
   def __init__(self):
       self.mode = MenuType.MENU
       self.host = 'ake.crabdance.com'
       self.port = 70
       self.path = ''

       self.history = []

   def go(self, mode=None, host=None, port=None, path=None, query=None):
       self.history.append((
           self.mode, self.host, self.port, self.path
       ))
       if mode is not None:
           self.mode = mode
       if host is not None:
           self.host = host
       if port is not None:
           self.port = int(port)
       if path is not None:
           self.path = path
       if mode == MenuType.QUERY:
           self.path += '\t{}'.format(query)

   def back(self):
       try:
           mode, host, port, path = self.history.pop()
           self.mode = mode
           self.host = host
           self.port = port
           self.path = path
       except:
           print('No entries in history')

   def fetch(self):
       try:
           s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
           s.connect((self.host, self.port))
           s.sendall('{}\r\n'.format(self.path).encode('utf-8'))
           resp = b''
           while True:
               chunk = s.recv(1024)
               if not chunk:
                   break
               resp += chunk
           return resp
       except:
           traceback_exc()
           print('Failed to fetch data')
           return None

class Navigator(Fetcher):
   def __init__(self):
       super().__init__()
       self.links = []
       self.buffer = []
   def goto(self, num):
       if num <= 0 or num > len(self.links):
           print('\tWrong link number')
       else:
           self.go(*self.links[num - 1])
   def render(self):
       self.buffer = []
       if self.mode == MenuType.BIN:
           print('Enter filename or leave empty to ignore')
           filename = input('Save as: ')
           if filename == '':
               return
       elif self.mode == MenuType.HTML:
           if self.path.lower().startswith('url:'):
               subprocess.call(['/usr/bin/xdg-open', self.path[4:]])
           else:
               subprocess.call(['/usr/bin/lynx', 'gopher://{}:{}/0{}'.format(self.host, self.port, self.path)])
           self.back()
           return

       data = self.fetch()
       if self.mode == MenuType.DOC:
           self.buffer = data.decode('utf-8').split('\n')
       elif self.mode == MenuType.BIN:
           with open(filename, 'wb') as f:
               f.write(data)
       elif self.mode == MenuType.MENU:
           self.links = []
           items = [item.split('\t') for item in data.decode('utf-8').split('\n')]
           for item in items:
               if len(item) < 4:
                   continue
               itype = item[0][0]
               itext = item[0][1:]
               if itype == MenuType.INFO:
                   self.buffer.append((None, itext))
               elif itype == MenuType.DOC:
                   self.links.append((itype, item[2], item[3], item[1]))
                   self.buffer.append((
                       len(self.links),
                       itext
                   ))
               elif itype == MenuType.MENU:
                   self.links.append((itype, item[2], item[3], item[1]))
                   self.buffer.append((
                       len(self.links),
                       itext
                   ))
               elif itype == MenuType.BIN or itype == MenuType.SOUND or itype == MenuType.IMAGE or itype == MenuType.GIF:
                   self.links.append((itype, item[2], item[3], item[1]))
                   self.buffer.append((
                       len(self.links),
                       itext
                   ))
               elif itype == MenuType.QUERY:
                   self.links.append((itype, item[2], item[3], item[1]))
                   self.buffer.append((
                       len(self.links),
                       itext
                   ))
               elif itype == MenuType.HTML:
                   self.links.append((itype, item[2], item[3], item[1]))
                   self.buffer.append((
                       len(self.links),
                       itext
                   ))
               else:
                   self.buffer.append('\tUnknown type: {}'.format(itype))
       else:
           self.buffer.append('\tNot yet implemented')

def display_help():
   print('''Omsk subway -- commandline gopher navigator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Commands:

exit       -- exit program
help       -- view this help
back       -- return to previous menu
index      -- return to server index
@<host>    -- request server's index
<selector> -- request menu by selector on current
             server
''')

def main(stdscr):
   navi.render()
   start = 0
   link = 0
   key = None
   while True:
       stdscr.clear()
       stdscr.addstr(0, 0, 'start: {}, key: {}, url: gopher://{}:{}/{}{}'.format(start, key, navi.host, navi.port, navi.mode, navi.path))
       stdscr.refresh()
       for i in range(min(start + curses.LINES - 1, len(navi.buffer) - start)):
           if navi.mode == MenuType.MENU:
               if navi.buffer[i + start][0] == None:
                   text = '\t' + navi.buffer[i + start][1]
               elif navi.buffer[i + start][0] == link + 1:
                   text = '[*]\t' + navi.buffer[i + start][1]
               else:
                   text = '[ ]\t' + navi.buffer[i + start][1]
           else:
               text = navi.buffer[i + start]
           try:
               stdscr.addstr(i + 1, 0, text)
           except:
               pass
       stdscr.move(curses.LINES - 1, 0)
       stdscr.refresh()
       key = stdscr.getkey()
       if key == 'KEY_PPAGE':
           start = max(0, start - curses.LINES - 1)
       elif key == 'KEY_NPAGE':
           start = start + curses.LINES - 1
       elif key == '\n':
           if navi.links[link][0] == MenuType.QUERY:
               stdscr.move(curses.LINES - 1, 0)
               curses.echo()
               s = stdscr.getstr(0, 0, curses.COLS).decode('utf-8')
               navi.go(*navi.links[link], query=s)
           else:
               navi.goto(link + 1)
           link = 0
           start = 0
           navi.render()
       elif key == 'b':
           navi.back()
           link = 0
           start = 0
           navi.render()
       elif key == 'KEY_UP':
           link = (link - 1) % len(navi.links)
       elif key == 'KEY_DOWN':
           link = (link + 1) % len(navi.links)
       elif key == 'q':
           break

if __name__ == '__main__':
   navi = Navigator()
   curses.wrapper(main)