text: import os.path
  import webbrowser
  import re
  import json
  from . import api, msg, utils, reactor, shared as G, event_emitter
  from .handlers import account, credentials
  from .. import editor
  from ..common.exc_fmt import str_e
  
  
  class FlooUI(event_emitter.EventEmitter):
      def __init__(self):
          super(FlooUI, self).__init__()
          self.agent = None
  
      def _make_agent(self, context, owner, workspace, auth, join_action):
          """@returns new Agent()"""
          raise NotImplemented()
  
      def user_y_or_n(self, context, prompt, affirmation_txt, cb):
          """@returns True/False"""
          raise NotImplemented()
  
      def user_select(self, context, prompt, choices_big, choices_small, cb):
          """@returns (choice, index)"""
          raise NotImplemented()
  
      def user_charfield(self, context, prompt, initial, cb):
          """@returns String"""
          raise NotImplemented()
  
      def user_dir(self, context, prompt, initial, cb):
          """@returns a String directory (probably not expanded)"""
          raise NotImplemented()
  
      def get_a_window(self, abs_path, cb):
          """opens a project in a window or something"""
          raise NotImplemented()
  
      @utils.inlined_callbacks
      def link_account(self, context, host, cb):
          prompt = 'No credentials found in ~/.floorc.json for %s. Would you like to sign in? (opens a browser)' % host
          yes = yield self.user_y_or_n, context, prompt, 'Sign in'
          if not yes:
              return
  
          agent = credentials.RequestCredentialsHandler()
          if not agent:
              self.error_message('''A configuration error occured earlier. Please go to %s and sign up to use this plugin.
      We're really sorry. This should never happen.''' % host)
              return
  
          agent.once('end', cb)
  
          try:
              reactor.reactor.connect(agent, host, G.DEFAULT_PORT, True)
          except Exception as e:
              print((str_e(e)))
  
      @utils.inlined_callbacks
      def create_or_link_account(self, context, host, force, cb):
          if host != "floobits.com":
              self.link_account(context, host, cb)
              return
          disable_account_creation = utils.get_persistent_data().get('disable_account_creation')
          if disable_account_creation and not force:
              print('We could not automatically create or link your floobits account. Please go to floobits.com and sign up to use this plugin.')
              return
  
          if not G.EXPERT_MODE:
              editor.message_dialog('Thank you for installing the Floobits plugin!\n\nLet\'s set up your editor to work with Floobits.')
  
          choices = [
              'Sign in to Floobits',
              'Automatically create a Floobits account',
              'Cancel (see https://floobits.com/help/floorc)'
          ]
  
          (choice, index) = yield self.user_select, context, 'You need an account to use Floobits! Do you want to:', choices, None
  
          if index == -1 or index == 2:
              d = utils.get_persistent_data()
              if not d.get('disable_account_creation'):
                  d['disable_account_creation'] = True
                  utils.update_persistent_data(d)
                  # TODO: this instruction is only useful for Sublime Text
                  editor.message_dialog('''You can set up a Floobits account at any time under:\n\nTools -> Floobits -> Set up''')
              cb(None)
              return
  
          agent = None
          if index == 0:
              agent = credentials.RequestCredentialsHandler()
          else:
              agent = account.CreateAccountHandler()
  
          agent.once('end', cb)
  
          try:
              reactor.reactor.connect(agent, host, G.DEFAULT_PORT, True)
          except Exception as e:
              print((str_e(e)))
  
      def open_workspace(self):
          if not self.agent:
              return
          try:
              webbrowser.open(self.agent.workspace_url, new=2, autoraise=True)
          except Exception as e:
              msg.error("Couldn't open a browser: %s" % (str_e(e)))
  
      def open_workspace_settings(self):
          if not self.agent:
              return
          try:
              webbrowser.open(self.agent.workspace_url + '/settings', new=2, autoraise=True)
          except Exception as e:
              msg.error("Couldn't open a browser: %s" % (str_e(e)))
  
      def pinocchio(self, host=None):
          floorc = utils.load_floorc_json()
          auth = floorc.get('AUTH', {}).get(host or G.DEFAULT_HOST, {})
          username = auth.get('username')
          secret = auth.get('secret')
          if not (username and secret):
              return self.error_message('You don\'t seem to have a Floobits account of any sort')
          webbrowser.open('https://%s/%s/pinocchio/%s' % (G.DEFAULT_HOST, username, secret))
  
      def prejoin_workspace(self, workspace_url, dir_to_share, api_args):
          try:
              result = utils.parse_url(workspace_url)
          except Exception as e:
              msg.error(str_e(e))
              return False
  
          host = result.get('host')
          if not api.get_basic_auth(host):
              raise ValueError('No auth credentials for %s. Please add a username and secret for %s in your ~/.floorc.json' % (host, host))
  
          try:
              w = api.get_workspace_by_url(workspace_url)
          except Exception as e:
              editor.error_message('Error opening url %s: %s' % (workspace_url, str_e(e)))
              return False
  
          if w.code >= 400:
              try:
                  d = utils.get_persistent_data()
                  try:
                      del d['workspaces'][result['owner']][result['name']]
                  except Exception:
                      pass
                  try:
                      del d['recent_workspaces'][workspace_url]
                  except Exception:
                      pass
                  utils.update_persistent_data(d)
              except Exception as e:
                  msg.debug(str_e(e))
              return False
  
          msg.debug('workspace: ', json.dumps(w.body))
          anon_perms = w.body.get('perms', {}).get('AnonymousUser', [])
          msg.debug('api args: ', api_args)
          new_anon_perms = api_args.get('perms', {}).get('AnonymousUser', [])
          # TODO: prompt/alert user if going from private to public
          if set(anon_perms) != set(new_anon_perms):
              msg.debug(str(anon_perms), str(new_anon_perms))
              w.body['perms']['AnonymousUser'] = new_anon_perms
              response = api.update_workspace(workspace_url, w.body)
              msg.debug(str(response.body))
          utils.add_workspace_to_persistent_json(w.body['owner'], w.body['name'], workspace_url, dir_to_share)
          return result
  
      @utils.inlined_callbacks
      def remote_connect(self, context, host, owner, workspace, d, join_action=utils.JOIN_ACTION.PROMPT):
          G.PROJECT_PATH = os.path.realpath(d)
          try:
              utils.mkdir(os.path.dirname(G.PROJECT_PATH))
          except Exception as e:
              msg.error("Couldn't create directory", G.PROJECT_PATH, str_e(e))
              return
  
          auth = G.AUTH.get(host)
          if not auth:
              success = yield self.link_account, context, host
              if not success:
                  return
              auth = G.AUTH.get(host)
              if not auth:
                  msg.error("Something went really wrong.")
                  return
  
          try:
              res = api.get_workspace(host, owner, workspace)
              if res.code == 404:
                  msg.error("The workspace https://%s/%s/%s does not exist" % (host, owner, workspace))
                  return
          except Exception as e:
              message = 'Error getting workspace https://%s/%s/%s: %s' % (host, owner, workspace, str_e(e))
              msg.error(message)
              editor.error_message(message)
              return
  
          if self.agent:
              try:
                  self.agent.stop()
              except Exception:
                  pass
  
          G.WORKSPACE_WINDOW = yield self.get_a_window, d
          self.agent = self._make_agent(context, owner, workspace, auth, join_action)
          self.emit("agent", self.agent)
          reactor.reactor.connect(self.agent, host, G.DEFAULT_PORT, True)
          url = self.agent.workspace_url
          utils.add_workspace_to_persistent_json(owner, workspace, url, d)
          utils.update_recent_workspaces(url)
  
      @utils.inlined_callbacks
      def create_workspace(self, context, host, owner, name, api_args, dir_to_share):
          prompt = 'Workspace name: '
  
          api_args['name'] = name
          api_args['owner'] = owner
  
          while True:
              new_name = yield self.user_charfield, context, prompt, name
              name = new_name or name
              try:
                  api_args['name'] = name
                  r = api.create_workspace(host, api_args)
              except Exception as e:
                  msg.error('Unable to create workspace ', str_e(e))
                  editor.error_message('Unable to create workspace: %s' % str_e(e))
                  return
  
              if r.code < 400:
                  workspace_url = 'https://%s/%s/%s' % (host, owner, name)
                  msg.log('Created workspace ', workspace_url)
                  self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.UPLOAD)
                  return
  
              msg.error('Unable to create workspace: ', r.body)
  
              if r.code not in (400, 402, 409):
                  try:
                      r.body = r.body['detail']
                  except Exception:
                      pass
                  editor.error_message('Unable to create workspace: %s' % r.body)
                  return
  
              if r.code == 402:
                  try:
                      r.body = r.body['detail']
                  except Exception:
                      pass
  
                  yes = yield self.user_y_or_n, context, '%s Open billing settings?' % r.body, "Yes"
                  if yes:
                      webbrowser.open('https://%s/%s/settings#billing' % (host, owner))
                  return
  
              if r.code == 400:
                  # TODO: strip leading dots/dashes/etc
                  name = re.sub('[^A-Za-z0-9_\-\.]', '_', name)
                  prompt = 'Workspace names may only contain [A-Za-z0-9_\-\.]. Choose another name: '
                  continue
  
              yes = yield self.user_y_or_n, context, 'Workspace %s/%s already exists. Overwrite?' % (owner, name), 'Yes'
              if yes:
                  # TODO: this doesn't set permissions on the workspace correctly
                  self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.PROMPT)
                  return
  
              prompt = 'Workspace %s/%s already exists. Choose new name: ' % (owner, name)
  
      def join_workspace_by_url(self, context, workspace_url, possible_dirs=None):
          try:
              d = utils.parse_url(workspace_url)
          except Exception as e:
              return editor.error_message(str_e(e))
  
          return self.join_workspace(context, d['host'], d['workspace'], d['owner'], possible_dirs)
  
      @utils.inlined_callbacks
      def follow_user(self, context, cb=None):
          users = self.agent.workspace_info.get('users')
          userNames = set()
          me = self.agent.get_username_by_id(self.agent.workspace_info['user_id'])
          for user in list(users.values()):
              username = user['username']
              if username == me:
                  continue
              if user['client'] == 'flootty':
                  continue
              if 'highlight' not in user['perms']:
                  continue
              userNames.add(username)
          if not userNames:
              editor.error_message("There are no other users that can be followed at this time. " +
                                   "NOTE: you can only follow users who have highlight permission.")
              cb and cb()
              return
          userNames = list(userNames)
          userNames.sort()
          small = [(x in G.FOLLOW_USERS) and "unfollow" or "follow" for x in userNames]
          selected_user, index = yield self.user_select, context, "select a user to follow", list(userNames), small
  
          if not selected_user:
              cb and cb()
              return
  
          if selected_user in G.FOLLOW_USERS:
              G.FOLLOW_USERS.remove(selected_user)
              cb and cb()
              return
  
          G.FOLLOW_USERS.add(selected_user)
          G.AGENT.highlight(user=selected_user)
          cb and cb()
          return
  
      @utils.inlined_callbacks
      def join_workspace(self, context, host, name, owner, possible_dirs=None):
          utils.reload_settings()
  
          # legacy urls in emacs...
          if owner and owner[:2] == "r/":
              owner = owner[2:]
  
          if not utils.can_auth():
              success = yield self.create_or_link_account, context, host, False
              if not success:
                  return
              utils.reload_settings()
  
          possible_dirs = possible_dirs or []
          for d in possible_dirs:
              info = utils.read_floo_file(d)
              if not info:
                  continue
              try:
                  parsed_url = utils.parse_url(info['url'])
              except Exception:
                  parsed_url = None
  
              if parsed_url and parsed_url['host'] == host and parsed_url['workspace'] == name and parsed_url['owner'] == owner:
                  self.remote_connect(context, host, owner, name, d)
                  return
  
          try:
              d = utils.get_persistent_data()['workspaces'][owner][name]['path']
          except Exception:
              d = ''
  
          if d and os.path.isdir(d):
              self.remote_connect(context, host, owner, name, d)
              return
  
          # TODO: make per-host settings fully general
          host_share_dir = G.AUTH.get(host, {}).get('share_dir')
          d = d or os.path.join(host_share_dir or G.SHARE_DIR or G.BASE_DIR, owner, name)
          join_action = utils.JOIN_ACTION.PROMPT
          while True:
              d = yield self.user_dir, context, 'Save workspace files to: ', d
              if not d:
                  return
              d = os.path.realpath(os.path.expanduser(d))
              if not os.path.isdir(d):
                  y_or_n = yield self.user_y_or_n, context, '%s is not a directory. Create it? ' % d, "Create Directory"
                  if not y_or_n:
                      return
                  utils.mkdir(d)
                  if not os.path.isdir(d):
                      msg.error("Couldn't create directory", d)
                      continue
                  join_action = utils.JOIN_ACTION.DOWNLOAD
              if os.path.isdir(d):
                  self.remote_connect(context, host, owner, name, d, join_action)
                  return
  
      @utils.inlined_callbacks
      def prompt_share_dir(self, context, ask_about_dir, api_args):
          dir_to_share = yield self.user_dir, context, 'Directory to share: ', ask_about_dir
          if not dir_to_share:
              return
          self.share_dir(context, dir_to_share, api_args)
  
      @utils.inlined_callbacks
      def share_dir(self, context, dir_to_share, api_args):
          utils.reload_settings()
          if not utils.can_auth():
              success = yield self.create_or_link_account, context, G.DEFAULT_HOST, False
              if not success:
                  return
              utils.reload_settings()
  
          dir_to_share = os.path.expanduser(dir_to_share)
          dir_to_share = os.path.realpath(dir_to_share)
          dir_to_share = utils.unfuck_path(dir_to_share)
  
          if os.path.isfile(dir_to_share):
              dir_to_share = os.path.dirname(dir_to_share)
  
          workspace_name = os.path.basename(dir_to_share)
          msg.debug('', workspace_name, dir_to_share)
  
          if os.path.isfile(dir_to_share):
              dir_to_share = os.path.dirname(dir_to_share)
  
          try:
              utils.mkdir(dir_to_share)
          except Exception:
              msg.error("The directory", dir_to_share, "doesn't exist and I can't create it.")
              return
  
          info = utils.read_floo_file(dir_to_share)
  
          def prejoin(workspace_url):
              try:
                  return self.prejoin_workspace(workspace_url, dir_to_share, api_args)
              except ValueError:
                  pass
  
          workspace_url = info.get('url')
          if workspace_url:
              parsed_url = prejoin(workspace_url)
              if parsed_url:
                  self.remote_connect(context, parsed_url['host'], parsed_url['owner'], parsed_url['workspace'], dir_to_share)
                  return
  
          parsed_url = utils.get_workspace_by_path(dir_to_share, prejoin)
          if parsed_url:
              self.remote_connect(context, parsed_url['host'], parsed_url['owner'], parsed_url['workspace'], dir_to_share)
              return
  
          host = yield self._get_host, context
          if not host:
              return
  
          try:
              r = api.get_orgs_can_admin(host)
          except IOError as e:
              editor.error_message('Error getting org list: %s' % str_e(e))
              return
  
          choices = [G.AUTH[host]['username']]
          if r.code >= 400:
              editor.error_message('Error getting org list: %s' % r.body)
          elif r.body:
              choices += [org['name'] for org in r.body]
  
          if len(choices) == 1:
              owner = choices[0]
          else:
              little = ['Create workspace owned by %s' % s for s in choices]
              (owner, index) = yield self.user_select, context, 'Create workspace owned by', choices, little
  
          if not owner:
              return
  
          self.create_workspace(context, host, owner, workspace_name, api_args, dir_to_share)
  
      @utils.inlined_callbacks
      def _get_host(self, context, cb):
          if not G.AUTH:
              msg.warn('no auth')
              return
  
          hosts = list(G.AUTH.keys())
          if len(hosts) == 1:
              host = hosts[0]
          else:
              little = ["%s on %s" % (a['username'], h) for h, a in list(G.AUTH.items())]
              (host, index) = yield self.user_select, context, 'Which Floobits account should be used?', hosts, little
              if not host:
                  cb(None)
                  return
          cb(host)
  
      @utils.inlined_callbacks
      def delete_workspace(self, context, cb):
          host = yield self._get_host, context
          if not host:
              cb()
              return
  
          api_url = 'https://%s/api/workspaces/can/admin' % (host)
  
          try:
              r = api.api_request(host, api_url)
          except IOError as e:
              editor.error_message('Error getting workspaces can admin %s' % str_e(e))
              cb()
              return
  
          if r.code >= 400:
              editor.error_message('Error getting workspace list: %s' % r.body)
              cb()
              return
  
          choices = ['%s/%s' % (workspace['owner'], workspace['name']) for workspace in r.body]
          (workspace, index) = yield self.user_select, context, 'Select workpace to delete', choices, []
  
          if not workspace:
              cb()
              return
  
          if G.EXPERT_MODE:
              yes = True
          else:
              yes = yield self.user_y_or_n, context, 'Really delete %s?' % workspace, 'Yes'
  
          if not yes:
              cb()
              return
  
          workspace = r.body[index]
  
          try:
              api.delete_workspace(host, workspace['owner'], workspace['name'])
          except IOError as e:
              editor.error_message('Error deleting workspace' % str_e(e))
          cb()
  