"Cloud service related functionality."

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import concurrent.futures
import copy
import time
import requests
import json
from datetime import datetime


OK = 0
ERROR = 1
CONNECT_ERROR = 2
AUTH_ERROR = 3
UPLOAD_ERROR = 4


class Logger(object):

    def register_trial(self, trial_id, trial_state):
        """Informs the logger that a new Trial is starting."""
        raise NotImplementedError
  
    def register_execution(self, execution_id, execution_state):
        """Informs the logger that a new Execution is starting."""
        raise NotImplementedError

    def report_trial_state(self, trial_id, trial_state):
          """Gives the logger information about trial status."""
          raise NotImplementedError

    def report_execution_state(self, execution_id, execution_state):
        """Gives the logger information about execution status."""
        raise NotImplementedError

    def report_tuner_state(self, tuner_state):
        """Gives the logger information about search status."""
        raise NotImplementedError

    def exit(self):
        raise NotImplementedError


def url_join(*parts):
    return '/'.join(map(lambda fragment: fragment.rstrip('/'), parts))



def send_to_backend(url, data, key):
    response = requests.post(
        url,
        headers={'X-AUTH': key},
        json=data)

    if not response.ok:
        try:
            response_json = response.json()
        except json.decoder.JSONDecodeError:
            warning('Cloud service down -- data not uploaded: %s' %
                    response.text)
            return CONNECT_ERROR

        if response_json['status'] == 'Unauthorized':
            warning('Invalid backend API key.')
            return AUTH_ERROR
        else:
            warning('Warning! Cloud service upload failed: %s' % response.text)
            return UPLOAD_ERROR
        return ERROR
    else:
        return OK



class CloudLogger(Logger):

    def __init__(self, api_key):
        self.api_key = api_key
        self.log_interval = 5

        self._base_url = (
            'https://us-central1-kerastuner-prod.cloudfunctions.net/api/')
        self._last_update = -1
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
        self._search_id = None
        self._async = True

    def register_tuner(self, tuner_state):
        data = {
            'tuner_state': tuner_state,
        }
        self._send_to_backend('register_tuner', data)

    def register_trial(self, trial_id, trial_state):
        """Informs the logger that a new Trial is starting."""
        data = {
            'trial_id': trial_id,
            'trial_state': trial_state,
        }
        self._send_to_backend('register_trial', data)

    def register_execution(self, execution_id, execution_state):
        """Informs the logger that a new Execution is starting."""
        data = {
            'execution_id': execution_id,
            'execution_state': execution_state,
        }
        self._send_to_backend('register_execution', data)

    def report_trial_state(self, trial_id, trial_state):
        """Gives the logger information about trial status."""
        data = {
            'trial_id': trial_id,
            'trial_state': trial_state,
        }
        self._send_to_backend('report_trial_state', data)

    def report_execution_state(self, execution_id, execution_state):
        """Gives the logger information about execution status."""
        data = {
            'execution_id': execution_id,
            'execution_state': execution_state,
        }
        self._send_to_backend('report_execution_state', data)

    def report_tuner_state(self, tuner_state):
        """Gives the logger information about search status."""
        data = {
            'tuner_state': tuner_state,
        }
        self._send_to_backend('report_tuner_state', data)

    def exit(self):
        """Makes sure that all cloud requests have been sent."""
        self._executor.shutdown(wait=True)
        # In case the user wants to do multiple hypertuning sessions,
        # we open another process pool.
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)

    def _send_to_backend(self, route, data):
        url = url_join(self._base_url, route)
        if self._async:
            self._executor.submit(send_to_backend,
                                  url,
                                  data,
                                  self.api_key)
        else:
            send_to_backend(url, data, self.api_key)

@@ -341,24 +341,166 @@
 r(object):%0A%0A
+    def register_tuner(self, tuner_state):%0A        %22%22%22Informs the logger that a new search is starting.%22%22%22%0A        raise NotImplementedError%0A%0A
     def regi
@@ -815,36 +815,36 @@
     def report_t
-rial
+uner
 _state(self, tri
@@ -837,38 +837,28 @@
 tate(self, t
-rial_id, trial
+uner
 _state):%0A   
@@ -850,26 +850,24 @@
 ner_state):%0A
-  
         %22%22%22G
@@ -892,37 +892,38 @@
 formation about 
-trial
+search
  status.%22%22%22%0A    
@@ -914,26 +914,24 @@
  status.%22%22%22%0A
-  
         rais
@@ -964,33 +964,29 @@
  def report_
-execution
+trial
 _state(self,
@@ -986,39 +986,31 @@
 e(self, 
-execution_id, execution
+trial_id, trial
 _state):
@@ -1052,33 +1052,29 @@
 ation about 
-execution
+trial
  status.%22%22%22%0A
@@ -1115,37 +1115,41 @@
 %0A    def report_
-tuner
+execution
 _state(self, tun
@@ -1137,37 +1137,55 @@
 ion_state(self, 
-tuner
+execution_id, execution
 _state):%0A       
@@ -1215,38 +1215,41 @@
 formation about 
-search
+execution
  status.%22%22%22%0A    

Add register_tuner to Logger interface