import json
import httplib
import urlparse
import time

TIMEOUT = 30

# TODO: News-> look list to get last news... (and not the first of the list)

class SlapOSMasterCommunicator(object):
  """
  Communication with slapos Master using Hateoas.
  
  collection: collection of data (hosting_subscription, instance, software_release...)
  hosting_subscription: result of a request
  instance(s): instance(s) related to an hosting_subscription

  usage: ex:
    # Try to reuse same communicator, because initilization step may takes a lot of time
    # due to listing of all instances (alive or not) related to the specified slapOS account.
    communicator = SlapOSMasterCommunicator()
    
    # Print news related to 'TestScalability_21423104630420' all instances
    instance_link_list =  communicator._getRelatedInstanceLink('TestScalability_21423104630420')
    for instance_link in instance_link_list:
      news = communicator.getNewsFromInstanceLink(instance_link)
      print news['news']
  """
  def __init__(self, certificate_path, key_path, log,
                     url):
    # Create connection
    api_scheme, api_netloc, api_path, api_query, api_fragment = urlparse.urlsplit(url)
    self.log = log
    self.certificate_path = certificate_path
    self.key_path = key_path
    self.url = url
    self.connection = self._getConnection(self.certificate_path, self.key_path, self.url)
    # Get master
    master_link = {'href':api_path,'type':"application/vnd.slapos.org.hal+json; class=slapos.org.master"}
    master = self._curl(master_link)
    self.person_link = master['_links']['http://slapos.org/reg/me']
    # Get person related to specified key/certificate provided
    person = self._curl(self.person_link)
    self.personnal_collection_link = person['_links']['http://slapos.org/reg/hosting_subscription']
    # Get collection (of hosting subscriptions)
    collection = self._curl(self.personnal_collection_link)
    # XXX: This part may be extremly long (because here no hosting subscriptions
    # has been visited)
    self.hosting_subcriptions_dict = {}
    self.visited_hosting_subcriptions_link_list = []
    self.log("SlapOSMasterCommunicator will read all hosting subscriptions entries, "
             "it may take several time...")
    self._update_hosting_subscription_informations()
    
  def _getConnection(self,certificate_path, key_path, url):
    api_scheme, api_netloc, api_path, api_query, api_fragment = urlparse.urlsplit(url)
    #self.log("HTTPS Connection with: %s, cert=%s, key=%s" %(api_netloc,key_path,certificate_path))
    return httplib.HTTPSConnection(api_netloc, key_file=key_path, cert_file=certificate_path, timeout=TIMEOUT)

  def _curl(self, link):
    """
    'link' must look like : {'href':url,'type':content_type}
    """
    # Set timeout
    import socket
    socket.setdefaulttimeout(1.0*TIMEOUT)
    
    api_scheme, api_netloc, api_path, api_query, api_fragment = urlparse.urlsplit(link['href'])
    max_retry = 10
    # Try to use existing conection
    try:
      self.connection.request(method='GET', url=api_path, headers={'Accept': link['type']}, body="")
      response = self.connection.getresponse()
      return json.loads(response.read())
    # Create and use new connection
    except:
      retry = 0
      # (re)Try several time to use new connection
      while retry < max_retry:
        try:
          self.connection = self._getConnection(self.certificate_path, self.key_path, self.url)
          self.connection.request(method='GET', url=api_path, headers={'Accept': link['type']}, body="")
          response = self.connection.getresponse()
          return json.loads(response.read())
        except:
          self.log("SlapOSMasterCommunicator: Connection failed..")
          retry += 1
          time.sleep(10)
    self.log("SlapOSMasterCommunicator: All connection attempts failed after %d try.." %max_retry)
    raise ValueError("SlapOSMasterCommunicator: Impossible to use connection")
        
  def _update_hosting_subscription_informations(self):
    """
    Add all not already visited hosting_subcription
    # Visit all hosting subscriptions and fill a dict containing all
    # new hosting subscriptions. ( like: {hs1_title:hs1_link, hs2_title:hs2_link, ..} )
    # and a list of visited hosting_subsciption ( like: [hs1_link, hs2_link, ..] )
    """
    collection = self._curl(self.personnal_collection_link)
    # For each hosting_subcription present in the collection
    for hosting_subscription_link in collection['_links']['item']:
      if hosting_subscription_link not in self.visited_hosting_subcriptions_link_list:
        hosting_subscription = self._curl(hosting_subscription_link)
        self.hosting_subcriptions_dict.update({hosting_subscription['title']:hosting_subscription_link})
        self.visited_hosting_subcriptions_link_list.append(hosting_subscription_link)
  
  def _getRelatedInstanceLink(self, hosting_subscription_title):
    """
    Return a list of all related instance_url from an hosting_subscription_title
    """
    # Update informations
    self._update_hosting_subscription_informations()
    # Get specified hosting_subscription
    hosting_subscription_link = self.hosting_subcriptions_dict[hosting_subscription_title]
    hosting_subscription = self._curl(hosting_subscription_link)
    assert(hosting_subscription_title == hosting_subscription['title'])
    # Get instance collection related to this hosting_subscription
    instance_collection_link = hosting_subscription['_links']['http://slapos.org/reg/instance']
    instance_collection = self._curl(instance_collection_link)
    related_instance_link_list = []
    # For each instance present in the collection
    for instance in instance_collection['_links']['item']:
      related_instance_link_list.append(instance)
    return related_instance_link_list

  def getNewsFromInstanceLink(self, instance_link):
      instance = self._curl(instance_link)
      news_link = instance['_links']['http://slapos.org/reg/news']
      return self._curl(news_link)

  def isHostingSubsciptionStatusEqualTo(self, hosting_subscription_title, excepted_news_text):
    """
    Return True if all related instance state are equal to status,
    or False if not or if there is are no related instances.
    """
    related_instance_link_list = _getRelatedInstanceLink(hosting_subscription_title)
    # For each instance present in the collection
    for instance_link in related_instance_link_list:
      news = self.getNewsFromInstanceLink(instance_link)
      if excepted_news_text != news['news'][0]['text']:
        return False
    return len(related_instance_link_list) > 0

  def isInstanceReady(self, instance_link, status):
    """
    Return True if instance status and instance news text ~looks corresponding.
    ( use the matching of 'correctly' and 'Instance' and status )
    """
    # XXX: SlapOS Master doesn't store any "news" about slave instances. Assume true.
    if self._curl(instance_link)['slave']:
      return True
    text = self.getNewsFromInstanceLink(instance_link)['news'][0]['text']
    return ('Instance' in text) and ('correctly' in text) and (status in text)

  # check if provided 'status' = status
  def isHostingSubscriptionReady(self, hosting_subscription_title, status):
    """
    Return True if all instance status and instance news text ~looks corresponding.
    ( use the matching of 'correctly' and 'Instance' and status ).
    """
    instance_link_list = self._getRelatedInstanceLink(hosting_subscription_title)
    for instance_link in instance_link_list:
      if not self.isInstanceReady(instance_link, status):
        return False
    return len(instance_link_list) > 0
    
  def isRegisteredHostingSubscription(self, hosting_subscription_title):
    """
    Return True if the specified hosting_subscription is present on SlapOSMaster
    """
    self._update_hosting_subscription_informations()
    if self.hosting_subcriptions_dict.get(hosting_subscription_title):
      return True
    return False

  def getHostingSubscriptionDict(self):
    """
    Return the dict of hosting subcription.
    """
    return self.hosting_subcriptions_dict

  def getHostingSubscriptionInformationDict(self, title):
    """
    Return a dict with informations about Hosting subscription
    """
    related_instance_link_list = self._getRelatedInstanceLink(title)
    related_instance_link = None
    # Get root instance
    for link in related_instance_link_list:
      instance = self._curl(link)
      if title == instance['title']:
        related_instance_link = link
        break
    # Return information dict
    if related_instance_link:
      related_instance = self._curl(related_instance_link)
      return {
        'title': related_instance['title'],
        'status': related_instance['status'],
        'software_url': related_instance['_links']['http://slapos.org/reg/release'],
        'software_type': related_instance['software_type'],
        'computer_guid': related_instance['sla']['computer_guid']
      }
    else:
      return None