#imports
import googleapiclient.discovery
from apiclient.discovery import build
import ipywidgets as widgets
import re
import json
#we use pandas.DataFrame for printing arrays and dictionnary nicely
from pandas import DataFrame

"""
Explanation: gcloud.google.compute.instances
This notebook create, delete start and stop instances in google compute cloud service
End of explanation
"""


# %load getCredentialsFromFile.py


def getCredentials():
    from  oauth2client import file
    import httplib2
    import ipywidgets as widgets
    print("Getting the credentials from file...")
    storage = file.Storage("oauth2.dat")
    credentials=storage.get()
    if credentials is None or credentials.invalid:
        print( '❗')
        display(widgets.Valid(
            value=False,
            description='Credentials are ',
            disabled=False))
        display(widgets.HTML('go create a credential valid file here: <a target="_blank" href="cloud.google.auth.ipynb.ipynb">gcloud authorization notebook</a> and try again'))
    else:
        http_auth = credentials.authorize(httplib2.Http())
        print('✅ Ok')
        return credentials


credentials=getCredentials()

"""
Explanation: Load credentials
End of explanation
"""


compute_service = build('compute', 'v1', credentials=credentials)
resource_service = build('cloudresourcemanager', 'v1', credentials=credentials)

"""
Explanation: Create services
End of explanation
"""


# %load chooseProjectId.py
#projectId is the variable that will contains the projectId that will be used in the API calls
projectId=None

#list the existing projects 
projects=resource_service.projects().list().execute()
#we create a dictionaray name:projectId foe a dropdown list widget
projectsList={project['name']:project['projectId'] for project in projects['projects']}
projectsList['None']='invalid'

#the dropdownlist widget
projectWidget=widgets.Dropdown(options=projectsList,description='Choose your Project',value='invalid')
#a valid widget that get valid when a project is selected
projectIdValid=widgets.Valid(value=False,description='')
display(widgets.Box([projectWidget,projectIdValid]))

def projectValueChange(sender):
    if projectWidget.value!='invalid':
        #when a valid project is selected ,the gloabl variable projectId is set 
        projectIdValid.value=True
        projectIdValid.description=projectWidget.value
        global projectId
        projectId=projectWidget.value    
    else:
        projectIdValid.value=False
        projectIdValid.description=''
projectWidget.observe(projectValueChange, 'value')

# %load chooseZone.py
#zone is the variable that will contains the zone that will be used in the API calls
zone=None

#list the existing zones 
zones=compute_service.zones().list(project=projectId).execute()
#list that will contains the zones for a dropdown list
zonesList=[item['name'] for item in zones['items']]
zonesList.append('none')

#the dropdownlist widget
zoneWidget=widgets.Dropdown(options=zonesList,value='none',description='Choose your Zone:')
zoneValid=widgets.Valid(value=False,description='')
display(widgets.Box([zoneWidget,zoneValid]))

def zoneValueChange(sender):
    if zoneWidget.value!='none':
        #when a vail zone is slected, the variable zone is set
        zoneValid.value=True
        zoneValid.description=zoneWidget.value
        global zone
        zone=zoneWidget.value   
    else:
        zoneValid.value=False
        zoneValid.description=''
        
zoneWidget.observe(zoneValueChange, 'value')    

"""
Explanation: Choose projectId and zone
End of explanation
"""


image_response = compute_service.images().getFromFamily(
        project='debian-cloud', family='debian-8').execute()
source_disk_image = image_response['selfLink']

"""
Explanation: Create a new instance
- choosing the disk image
End of explanation
"""


machineType=None

machineTypes=compute_service.machineTypes().list(project=projectId,zone=zone).execute()

machineTypesList=[item['name'] for item in machineTypes['items']]
machineTypesList.append('none')

machineTypesWidget=widgets.Dropdown(options=machineTypesList,value='none',description='Choose your MachineType:')
machineTypesValid=widgets.Valid(value=False,description='')
display(widgets.Box([machineTypesWidget,machineTypesValid]))

def machineTypeValueChange(sender):
    if machineTypesWidget.value!='none':
        machineTypesValid.value=True
        machineTypesValid.description=machineTypesWidget.value
        global machineType
        machineType=machineTypesWidget.value  
    else:
        machineTypesValid.value=True
        machineTypesValid.description=''

machineTypesWidget.observe(machineTypeValueChange, 'value') 

"""
Explanation: - choosing the machineType
End of explanation
"""


instanceName=None

# instanceName have to validates this regexp
instanceNameControl=re.compile(r'^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$')

#the widgets
instanceNameWidget=widgets.Text(description="Name for the new instance:")
valid=widgets.Valid(value=False,description='',disabled=False)
display(widgets.Box([instanceNameWidget,valid]))

def instanceNameValueChange(sender):
    if instanceNameWidget.value!="":
        if instanceNameControl.match(instanceNameWidget.value):
            #when the entered text valid the regexp we set the 
            valid.value=True
            valid.description='OK'
            global instanceName
            instanceName=instanceNameWidget.value
        else:
            valid.value=False
            valid.description="The instance name has to verify the regexp '(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)'"
    else:
        valid.value=False
        valid.description=''


instanceNameWidget.observe(instanceNameValueChange, 'value')

"""
Explanation: - choose an instance name
End of explanation
"""


config= {'name':instanceName,
        'machineType': "zones/%(zone)s/machineTypes/%(machineType)s" %{'zone':zone,'machineType':machineType},
         'disks':[ 
            
             {
                 'boot':True,
                 'autoDelete':True,
                 'initializeParams':{
                     'sourceImage':source_disk_image
                 }
             }],
         'scheduling':
            {
             'preemptible': True
            },
         'networkInterfaces':[{
             'network':'global/networks/default',
             'accessConfigs': [
                    {'type':'ONE_TO_ONE_NAT','name':'ExternalNAT'}
                     ]
             }],
         'serviceAccounts':[{
             'email':'default',
             'scopes':[
                'https://www.googleapis.com/auth/devstorage.read_write',
                'https://www.googleapis.com/auth/logging.write'
            ]
         }],
         "labels": {
            "env": "test",
            "created-by": "jupyter-notebooks-cloud-google-compute-instances"
          },         
        }

#print(json.dumps(config, indent=2))

"""
Explanation: - creating the config for the new instance
The name, machineType and disk are set accordingly to the previous steps.
With scheduling.preemptible to true we choose a preemptible instance (cheaper ;-) )
You can adjust labels to your needs.
End of explanation
"""


#a progress widget will present the progress of the operation
progress=widgets.IntProgress(value=0,min=0,max=3,step=1,description=':',bar_style='warning')
display(progress)

#executing the insert operation
operation = compute_service.instances().insert(project=projectId,
                                  zone=zone,
                                  body=config
                                  ).execute()

def updateProgress(result,progress=progress):
    #updating the progress widget with the result of the operation
    if result['status']== 'PENDING':
        progress.value=1
        progress.bar_style='warning'
        progress.description=result['status']
    elif result['status']== 'RUNNING':
        progress.value=2
        progress.bar_style='info'
        progress.description=result['status']
    elif result['status']== 'DONE':
        progress.value=3
        if 'error' in result: 
            progress.description='Error'
            progress.bar_style='danger'
        else:
            progress.description=result['status']
            progress.bar_style='success'

import time 

#repeat until the result is DONE
while True:
    #obtain the status of the operation
    result=compute_service.zoneOperations().get(project=projectId,
                                              zone=zone,
                                              operation=operation['name']).execute()
    updateProgress(result)
    if result['status']== 'DONE':
        break
    time.sleep(.25) 


"""
Explanation: - executing the API call
End of explanation
"""


result = compute_service.instances().list(project=projectId, zone=zone).execute()

if 'items' in result.keys():
    display(DataFrame.from_dict({instance['name']:(instance['status'],'✅'if instance['status']=='RUNNING' else '✖'if instance['status']=='TERMINATED' else '❓')for instance in result['items']},orient='index'))
else:
    print("No instance found.")
    

"""
Explanation: Listing the instance and their status
End of explanation
"""


# getting the current instances list
instances=compute_service.instances().list(project=projectId,zone=zone).execute()
instancesList=[item['name'] for item in instances['items']]
# none is added for the dropdownlist
instancesList.append('none')

#building and displaying the widgets
instancesWidget=widgets.Dropdown(options=instancesList,value='none')
instancesValid=widgets.Valid(value=False,description='')
instanceAction=widgets.RadioButtons(
    options=[ 'Status','Start','Stop', 'Delete'],value='Status')
instanceExecute=widgets.ToggleButton(value=False,description='Execute',disabled=True)
display(widgets.Box([instancesWidget,instancesValid,instanceAction,instanceExecute]))

## execute an operation. 
def execute(operation):
    #exctract the method and the instancename form the operation
    instanceName=operation.uri.split('?')[0].split('/')[-1]
    methodId=operation.methodId.split('.')[-1]
    
    #some widgets (action + instance + progress)
    progress=widgets.IntProgress(value=0,min=0,max=3,step=1,description=':',bar_style='info')
    display(widgets.Box([widgets.Label(value=methodId+"ing"),widgets.Label(value=instanceName),progress]))
    
    #the dropdown and buttons are disabled when an operation is executing
    global instanceExecute
    global instancesWidget
    instancesWidget.disabled=True
    instanceExecute.disabled=True
    
    #execute the operation
    operation=operation.execute()
    #until the operation is not DONE, we update the progress bar
    while True:
        result=compute_service.zoneOperations().get(project=projectId,
                                              zone=zone,
                                              operation=operation['name']).execute()
        updateProgress(result,progress)
        if result['status']== 'DONE':
            if methodId==u'delete':
                #when the instance is deleted, it has to be remove from the dropdownlist
                global instancesList               
                instancesList.remove(instanceName)
                instancesWidget.options=instancesList
                instancesValid.value=False
            #the operation is completed, the dropwdown and buttons are enabled
            instancesWidget.disabled=False
            instanceExecute.disabled=False
            break
        time.sleep(0.1) 
        


def executeInstance(sender):
    #callback when the execute button is clicked    
    if instancesValid.value==True:
        # the correct operation is created and pass to the execute method
        if instanceAction.value=='Stop':
            execute(compute_service.instances().stop(project=projectId,
                                  zone=zone,
                                  instance=instancesWidget.value
                                  ))
        elif instanceAction.value=='Start':
            execute(compute_service.instances().start(project=projectId,
                                  zone=zone,
                                  instance=instancesWidget.value
                                  ))
        elif instanceAction.value=='Delete':
            execute(compute_service.instances().delete(project=projectId,
                                  zone=zone,
                                  instance=instancesWidget.value
                                  ))
        elif instanceAction.value=='Status': 
            instance=compute_service.instances().get(project=projectId,
                                  zone=zone,
                                  instance=instancesWidget.value).execute()
            display(widgets.Box([widgets.Label(value=instance['name']),
                                 widgets.Label(value=instance['status'])
                                ]))
def instancesValueChange(sender):
    #callback when an element is selected in the dropdown list 
    if instancesWidget.value!=None:
        #when the seleciton is correct the valid widget is valid
        instancesValid.value=True
        instanceExecute.disabled=False

#set up the callback on the widgets        
instancesWidget.observe(instancesValueChange, 'value')  
instanceExecute.observe(executeInstance,'value')

"""
Explanation: Start/Stop/Delete instances
End of explanation
"""


