"Functions for calling the Deep Learning Tools."

from arcgis.geoprocessing._support import _analysis_job, _analysis_job_results, \
     _analysis_job_status, _layer_input
import json as _json
import arcgis as _arcgis
from arcgis.raster._layer import ImageryLayer as _ImageryLayer
from arcgis.raster._util import _set_context, _id_generator
from .models import SingleShotDetector, UnetClassifier, FeatureClassifier, RetinaNet, PSPNetClassifier, EntityRecognizer, MaskRCNN
from ._data import prepare_data
from ._process_df import process_df, add_datepart

def _set_param(gis, params, param_name, input_param):
    if isinstance(input_param, str):
        if 'http:' in input_param or 'https:' in input_param:
            params[param_name] = _json.dumps({ 'url' : input_param })
        else:
            params[param_name] = _json.dumps({ 'uri' : input_param })

    elif isinstance(input_param, _arcgis.gis.Item):
        params[param_name] = _json.dumps({ "itemId" : input_param.itemid })

    elif isinstance(input_param, dict):
        params[param_name] =  input_param
    elif isinstance(input_param, Model):
        params[param_name] = input_param._model
    else:
        raise TypeError(input_param+" should be a string (service url) or Item")

    return


def _create_output_image_service(gis, output_name, task, folder = None):
    ok = gis.content.is_service_name_available(output_name, "Image Service")
    if not ok:
        raise RuntimeError("An Image Service by this name already exists: " + output_name)

    create_parameters = {
        "name": output_name,
        "description": "",
        "capabilities": "Image",
        "properties": {
            "path": "@",
            "description": "",
            "copyright": ""
        }
    }

    output_service = gis.content.create_service(output_name, create_params=create_parameters,
                                                service_type="imageService", folder = folder)
    description = "Image Service generated from running the " + task + " tool."
    item_properties = {
        "description": description,
        "tags": "Analysis Result, " + task,
        "snippet": "Analysis Image Service generated from " + task
    }
    output_service.update(item_properties)
    return output_service


def _create_output_feature_service(gis, output_name, output_service_name='Analysis feature service', task='GeoAnalytics', folder = None):
    ok = gis.content.is_service_name_available(output_name, 'Feature Service')
    if not ok:
        raise RuntimeError("A Feature Service by this name already exists: " + output_name)

    createParameters = {
        "currentVersion": 10.2,
            "serviceDescription": "",
            "hasVersionedData": False,
            "supportsDisconnectedEditing": False,
            "hasStaticData": True,
            "maxRecordCount": 2000,
            "supportedQueryFormats": "JSON",
            "capabilities": "Query",
            "description": "",
            "copyrightText": "",
            "allowGeometryUpdates": False,
            "syncEnabled": False,
            "editorTrackingInfo": {
                "enableEditorTracking": False,
                "enableOwnershipAccessControl": False,
                "allowOthersToUpdate": True,
                "allowOthersToDelete": True
                },
            "xssPreventionInfo": {
                "xssPreventionEnabled": True,
                "xssPreventionRule": "InputOnly",
                "xssInputRule": "rejectInvalid"
                },
            "tables": [],
            "name": output_service_name.replace(' ', '_')
    }

    output_service = gis.content.create_service(output_name, create_params=createParameters, service_type="featureService", folder =  folder)
    description = "Feature Service generated from running the " + task + " tool."
    item_properties = {
        "description" : description,
            "tags" : "Analysis Result, " + task,
            "snippet": output_service_name
    }
    output_service.update(item_properties)
    return output_service

def _set_output_raster(output_name, task, gis, output_properties =None):
    output_service = None
    output_raster = None
    
    task_name = task

    folder = None
    folderId = None

    if output_properties is not None:
        if "folder" in output_properties:
            folder = output_properties["folder"]
    if folder is not None:
        if isinstance(folder, dict):
            if "id" in folder:
                folderId = folder["id"]
                folder=folder["title"]
        else:
            owner = gis.properties.user.username
            folderId = gis._portal.get_folder_id(owner, folder)
        if folderId is None:
            folder_dict = gis.content.create_folder(folder, owner)
            folder = folder_dict["title"]
            folderId = folder_dict["id"]

    if output_name is None:
        output_name = str(task_name) + '_' + _id_generator()
        output_service = _create_output_image_service(gis, output_name, task, folder = folder)
        output_raster = {"serviceProperties": {"name" : output_service.name, "serviceUrl" : output_service.url}, "itemProperties": {"itemId" : output_service.itemid}}
    elif isinstance(output_name, str):
        output_service = _create_output_image_service(gis, output_name, task,folder = folder)
        output_raster = {"serviceProperties": {"name" : output_service.name, "serviceUrl" : output_service.url}, "itemProperties": {"itemId" : output_service.itemid}}
    elif isinstance(output_name, _arcgis.gis.Item):
        output_service = output_name
        output_raster = {"itemProperties":{"itemId":output_service.itemid}}
    else:
        raise TypeError("output_raster should be a string (service name) or Item") 

    if folderId is not None:
        output_raster["itemProperties"].update({"folderId":folderId})
    output_raster = _json.dumps(output_raster)
    return output_raster, output_service


def detect_objects(input_raster,
                   model,
                   model_arguments=None,
                   output_name=None,
                   run_nms=False,
                   confidence_score_field=None,
                   class_value_field=None,
                   max_overlap_ratio=0,
                   context=None,
                   process_all_raster_items=False,
                   *,
                   gis=None,
                   future=False,
                   **kwargs):

    """
    Function can be used to generate feature service that contains polygons on detected objects
    found in the imagery data using the designated deep learning model. Note that the deep learning
    library needs to be installed separately, in addition to the server's built in Python 3.x library.

    ====================================     ====================================================================
    **Argument**                             **Description**
    ------------------------------------     --------------------------------------------------------------------
    input_raster                             Required. raster layer that contains objects that needs to be detected.
    ------------------------------------     --------------------------------------------------------------------
    model                                    Required model object. 
    ------------------------------------     --------------------------------------------------------------------
    model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.
                                             
                                             eg: {"name1":"value1", "name2": "value2"}
    ------------------------------------     --------------------------------------------------------------------
    output_name                              Optional. If not provided, a Feature layer is created by the method and used as the output .
                                             You can pass in an existing Feature Service Item from your GIS to use that instead.
                                             Alternatively, you can pass in the name of the output Feature Service that should be created by this method
                                             to be used as the output for the tool.
                                             A RuntimeError is raised if a service by that name already exists
    ------------------------------------     --------------------------------------------------------------------
    run_nms                                  Optional bool. Default value is False. If set to True, runs the Non Maximum Suppression tool.
    ------------------------------------     --------------------------------------------------------------------
    confidence_score_field                   Optional string. The field in the feature class that contains the confidence scores as output by the object detection method.
                                             This parameter is required when you set the run_nms to True
    ------------------------------------     --------------------------------------------------------------------
    class_value_field                        Optional string. The class value field in the input feature class. 
                                             If not specified, the function will use the standard class value fields 
                                             Classvalue and Value. If these fields do not exist, all features will 
                                             be treated as the same object class.
                                             Set only if run_nms  is set to True
    ------------------------------------     --------------------------------------------------------------------
    max_overlap_ratio                        Optional integer. The maximum overlap ratio for two overlapping features. 
                                             Defined as the ratio of intersection area over union area. 
                                             Set only if run_nms  is set to True
    ------------------------------------     --------------------------------------------------------------------
    context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                             Dictionary can contain value for following keys:

                                             - cellSize - Set the output raster cell size, or resolution

                                             - extent - Sets the processing extent used by the function

                                             - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"

                                             - processorType - Sets the processor type. "CPU" or "GPU"

                                             Eg: {"processorType" : "CPU"}

                                             Setting context parameter will override the values set using arcgis.env 
                                             variable for this particular function.
    ------------------------------------     --------------------------------------------------------------------
    process_all_raster_items                 Optional bool. Specifies how all raster items in an image service will be processed.

                                              - False : all raster items in the image service will be mosaicked together and processed. This is the default.

                                              - True : all raster items in the image service will be processed as separate images.
    ------------------------------------     --------------------------------------------------------------------
    gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
    ------------------------------------     --------------------------------------------------------------------
    future                                   Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
    ====================================     ====================================================================

    :return:
        The output feature layer item containing the detected objects

    """


    #task = "DetectObjectsUsingDeepLearning"

    gis = _arcgis.env.active_gis if gis is None else gis
    return gis._tools.rasteranalysis.detect_objects_using_deep_learning(input_raster=input_raster,
                                                                        model=model,
                                                                        output_objects=output_name,
                                                                        model_arguments=model_arguments,
                                                                        run_nms=run_nms,
                                                                        confidence_score_field=confidence_score_field,
                                                                        class_value_field=class_value_field,
                                                                        max_overlap_ratio=max_overlap_ratio,
                                                                        context=context,
                                                                        process_all_raster_items=process_all_raster_items,
                                                                        future=future,
                                                                        **kwargs)

    """
    url = gis.properties.helperServices.rasterAnalytics.url
    gptool = _arcgis.gis._GISResource(url, gis)

    params = {}

    params["inputRaster"] = _layer_input(input_raster)

    if output_name is None:
        output_service_name = 'DetectObjectsUsingDeepLearning_' + _id_generator()
        output_name = output_service_name.replace(' ', '_')
    else:
        output_service_name = output_name.replace(' ', '_')

    folder = None
    folderId = None
    if kwargs is not None:
        if "folder" in kwargs:
                folder = kwargs["folder"]
        if folder is not None:
            if isinstance(folder, dict):
                if "id" in folder:
                    folderId = folder["id"]
                    folder=folder["title"]
            else:
                owner = gis.properties.user.username
                folderId = gis._portal.get_folder_id(owner, folder)
            if folderId is None:
                folder_dict = gis.content.create_folder(folder, owner)
                folder = folder_dict["title"]
                folderId = folder_dict["id"]

    output_service = _create_output_feature_service(gis, output_name, output_service_name, 'Detect Objects', folder)

    if folderId is not None:
        params["outputObjects"] = _json.dumps({"serviceProperties": {"name": output_service_name, "serviceUrl": output_service.url},
                                               "itemProperties": {"itemId": output_service.itemid}, "folderId":folderId})
    else:
        params["outputObjects"] = _json.dumps({"serviceProperties": {"name": output_service_name, "serviceUrl": output_service.url},
                                               "itemProperties": {"itemId": output_service.itemid}})

    if model is None:
        raise RuntimeError('model cannot be None')
    else:
        _set_param(gis, params, "model", model)

    if model_arguments:
        params["modelArguments"] = dict((str(k),str(v)) for k, v in model_arguments.items())

    if isinstance(run_nms, bool):
        if run_nms:
            params["runNMS"] = True

            if confidence_score_field is not None:
                params["confidenceScoreField"] = confidence_score_field

            if class_value_field is not None:
                params["classValueField"] = class_value_field
    
            if max_overlap_ratio is not None:
                params["maxOverlapRatio"] = max_overlap_ratio
        else:
            params["runNMS"] = False
    else:
        raise RuntimeError("run_nms value should be an instance of bool")
    
    _set_context(params, context)

    task_url, job_info, job_id = _analysis_job(gptool, task, params)

    job_info = _analysis_job_status(gptool, task_url, job_info)
    job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
    item_properties = {
        "properties": {
            "jobUrl": task_url + '/jobs/' + job_info['jobId'],
            "jobType": "GPServer",
            "jobId": job_info['jobId'],
            "jobStatus": "completed"
        }
    }
    output_service.update(item_properties)
    return output_service
    """

def classify_pixels(input_raster,
                    model,
                    model_arguments=None,
                    output_name=None,
                    context=None,
                    process_all_raster_items=False,
                    *,
                    gis=None,
                    future=False,
                    **kwargs):

    """
    Function to classify input imagery data using a deep learning model.
    Note that the deep learning library needs to be installed separately,
    in addition to the server's built in Python 3.x library.

    ====================================     ====================================================================
    **Argument**                             **Description**
    ------------------------------------     --------------------------------------------------------------------
    input_raster                             Required. raster layer that needs to be classified
    ------------------------------------     --------------------------------------------------------------------
    model                                    Required model object.
    ------------------------------------     --------------------------------------------------------------------
    model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.

                                             eg: {"name1":"value1", "name2": "value2"}

    ------------------------------------     --------------------------------------------------------------------
    output_name                              Optional. If not provided, an imagery layer is created by the method and used as the output .
                                             You can pass in an existing Image Service Item from your GIS to use that instead.
                                             Alternatively, you can pass in the name of the output Image Service that should be created by this method
                                             to be used as the output for the tool.
                                             A RuntimeError is raised if a service by that name already exists
    ------------------------------------     --------------------------------------------------------------------
    context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                               Dictionary can contain value for following keys:

                                               - outSR - (Output Spatial Reference) Saves the result in the specified spatial reference

                                               - snapRaster - Function will adjust the extent of output rasters so that they 
                                                 match the cell alignment of the specified snap raster.

                                               - cellSize - Set the output raster cell size, or resolution

                                               - extent - Sets the processing extent used by the function

                                               - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"

                                               - processorType - Sets the processor type. "CPU" or "GPU"

                                               Eg: {"outSR" : {spatial reference}}

                                               Setting context parameter will override the values set using arcgis.env 
                                               variable for this particular function.
    ------------------------------------     --------------------------------------------------------------------
    process_all_raster_items                 Optional bool. Specifies how all raster items in an image service will be processed.

                                              - False : all raster items in the image service will be mosaicked together and processed. This is the default.

                                              - True : all raster items in the image service will be processed as separate images.
    ------------------------------------     --------------------------------------------------------------------
    gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
    ------------------------------------     --------------------------------------------------------------------
    future                                   Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
    ====================================     ====================================================================

    :return:
        The classified imagery layer item

    """


    #task = "ClassifyPixelsUsingDeepLearning"

    gis = _arcgis.env.active_gis if gis is None else gis
    return gis._tools.rasteranalysis.classify_pixels_using_deep_learning(input_raster=input_raster,
                                                                        model=model,
                                                                        model_arguments=model_arguments,
                                                                        output_classified_raster=output_name,
                                                                        context=context,
                                                                        process_all_raster_items=process_all_raster_items,
                                                                        future=future,
                                                                        **kwargs)


    """
    url = gis.properties.helperServices.rasterAnalytics.url
    gptool = _arcgis.gis._GISResource(url, gis)

    output_service = None

    output_raster, output_service = _set_output_raster(output_name, task, gis, kwargs)

    params = {}

    params["outputClassifiedRaster"] = output_raster

    params["inputRaster"] = _layer_input(input_raster)

    if model is None:
        raise RuntimeError('model cannot be None')
    else:
        _set_param(gis, params, "model", model)

    if model_arguments:
        params["modelArguments"] = dict((str(k),str(v)) for k, v in model_arguments.items())

    _set_context(params, context)

    task_url, job_info, job_id = _analysis_job(gptool, task, params)

    job_info = _analysis_job_status(gptool, task_url, job_info)
    job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
    item_properties = {
        "properties": {
            "jobUrl": task_url + '/jobs/' + job_info['jobId'],
            "jobType": "GPServer",
            "jobId": job_info['jobId'],
            "jobStatus": "completed"
        }
    }
    output_service.update(item_properties)
    return output_service
    """

def export_training_data(input_raster,
                         input_class_data=None,
                         chip_format=None,
                         tile_size=None,
                         stride_size=None,
                         metadata_format=None,
                         classvalue_field=None,
                         buffer_radius=None,
                         output_location=None,
                         context=None,
                         input_mask_polygons=None,
                         rotation_angle=0,
                         reference_system="MAP_SPACE",
                         process_all_raster_items=False,
                         blacken_around_feature=False,
                         fix_chip_size=True,
                         *,
                         gis=None,
                         future=False,
                         **kwargs):

    """
    Function is designed to generate training sample image chips from the input imagery data with
    labeled vector data or classified images. The output of this service tool is the data store string
    where the output image chips, labels and metadata files are going to be stored.

    ====================================     ====================================================================
    **Argument**                             **Description**
    ------------------------------------     --------------------------------------------------------------------
    input_raster                             Required. Raster layer that needs to be exported for training
    ------------------------------------     --------------------------------------------------------------------
    input_class_data                         Labeled data, either a feature layer or image layer.
                                             Vector inputs should follow a training sample format as
                                             generated by the ArcGIS Pro Training Sample Manager.
                                             Raster inputs should follow a classified raster format as generated by the Classify Raster tool.
    ------------------------------------     --------------------------------------------------------------------
    chip_format                              Optional string. The raster format for the image chip outputs.

                                                - TIFF: TIFF format

                                                - PNG: PNG format

                                                - JPEG: JPEG format

                                                - MRF: MRF (Meta Raster Format)
    ------------------------------------     --------------------------------------------------------------------
    tile_size                                Optional dictionary. The size of the image chips.

                                                Example: {"x": 256, "y": 256}
    ------------------------------------     --------------------------------------------------------------------
    stride_size                              Optional dictionary. The distance to move in the X and Y when creating 
                                             the next image chip.
                                             When stride is equal to the tile size, there will be no overlap.
                                             When stride is equal to half of the tile size, there will be 50% overlap.

                                               Example: {"x": 128, "y": 128}
    ------------------------------------     --------------------------------------------------------------------
    metadata_format                          Optional string. The format of the output metadata labels. There are 4 options for output metadata labels for the training data,
                                               KITTI Rectangles, PASCAL VOCrectangles, Classified Tiles (a class map) and RCNN_Masks. If your input training sample data
                                               is a feature class layer such as building layer or standard classification training sample file,
                                               use the KITTI or PASCAL VOC rectangle option.

                                               The output metadata is a .txt file or .xml file containing the training sample data contained
                                               in the minimum bounding rectangle. The name of the metadata file matches the input source image
                                               name. If your input training sample data is a class map, use the Classified Tiles as your output metadata format option.

                                               - KITTI_rectangles: The metadata follows the same format as the Karlsruhe Institute of Technology and Toyota
                                                 Technological Institute (KITTI) Object Detection Evaluation dataset. The KITTI dataset is a vision benchmark suite.
                                                 This is the default.The label files are plain text files. All values, both numerical or strings, are separated by
                                                 spaces, and each row corresponds to one object.

                                               - PASCAL_VOC_rectangles: The metadata follows the same format as the Pattern Analysis, Statistical Modeling and
                                                 Computational Learning, Visual Object Classes (PASCAL_VOC) dataset. The PASCAL VOC dataset is a standardized
                                                 image data set for object class recognition.The label files are XML files and contain information about image name,
                                                 class value, and bounding box(es).

                                               - Classified_Tiles: This option will output one classified image chip per input image chip.
                                                 No other meta data for each image chip. Only the statistics output has more information on the
                                                 classes such as class names, class values, and output statistics.

                                               - RCNN_Masks: This option will output image chips that have a mask on the areas where the sample exists.
                                                 The model generates bounding boxes and segmentation masks for each instance of an object in the image.
                                                 It's based on Feature Pyramid Network (FPN) and a ResNet101 backbone.

                                               - Labeled_Tiles : This option will label each output tile with a specific class.
    ------------------------------------     --------------------------------------------------------------------
    classvalue_field                          Optional string. Specifies the field which contains the class values. If no field is specified,
                                              the system will look for a 'value' or 'classvalue' field. If this feature does
                                              not contain a class field, the system will presume all records belong the 1 class.
    ------------------------------------     --------------------------------------------------------------------
    buffer_radius                            Optional integer. Specifies a radius for point feature classes to specify training sample area.
    ------------------------------------     --------------------------------------------------------------------
    output_location                          This is the output location for training sample data.
                                               It can be the server data store path or a shared file system path.

                                               Example:

                                               Server datastore path -
                                                ``/fileShares/deeplearning/rooftoptrainingsamples``
                                                ``/rasterStores/rasterstorename/rooftoptrainingsamples``
                                                ``/cloudStores/cloudstorename/rooftoptrainingsamples``

                                               File share path - 
                                                ``\\\\servername\\deeplearning\\rooftoptrainingsamples``
    ------------------------------------     --------------------------------------------------------------------
    context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                                Dictionary can contain value for following keys:

                                                - exportAllTiles - Choose if the image chips with overlapped labeled data will be exported.
                                                    True - Export all the image chips, including those that do not overlap labeled data. 
                                                    False - Export only the image chips that overlap the labelled data. This is the default.

                                                - startIndex - Allows you to set the start index for the sequence of image chips.
                                                    This lets you append more image chips to an existing sequence. The default value is 0.

                                                - cellSize - cell size can be set using this key in context parameter

                                                - extent - Sets the processing extent used by the function

                                                Setting context parameter will override the values set using arcgis.env 
                                                variable for this particular function.(cellSize, extent)

                                                eg: {"exportAllTiles" : False, "startIndex": 0 }
    ------------------------------------     --------------------------------------------------------------------
    input_mask_polygons                       Optional feature layer. The feature layer that delineates the area where 
                                               image chips will be created.
                                               Only image chips that fall completely within the polygons will be created.
    ------------------------------------     --------------------------------------------------------------------
    rotation_angle                           Optional float. The rotation angle that will be used to generate additional 
                                               image chips.

                                               An image chip will be generated with a rotation angle of 0, which 
                                               means no rotation. It will then be rotated at the specified angle to 
                                               create an additional image chip. The same training samples will be 
                                               captured at multiple angles in multiple image chips for data augmentation.
                                               The default rotation angle is 0.
    ------------------------------------     --------------------------------------------------------------------
    reference_system                         Optional string. Specifies the type of reference system to be used to interpret 
                                             the input image. The reference system specified should match the reference system 
                                             used to train the deep learning model. 

                                             - MAP_SPACE : The input image is in a map-based coordinate system. This is the default.

                                             - IMAGE_SPACE : The input image is in image space, viewed from the direction of the sensor 
                                               that captured the image, and rotated such that the tops of buildings and trees point upward in the image.

                                             - PIXEL_SPACE : The input image is in image space, with no rotation and no distortion. 
    ------------------------------------     --------------------------------------------------------------------
    process_all_raster_items                 Optional bool. Specifies how all raster items in an image service will be processed.

                                              - False : all raster items in the image service will be mosaicked together and processed. This is the default.

                                              - True : all raster items in the image service will be processed as separate images.
    ------------------------------------     --------------------------------------------------------------------
    blacken_around_feature                   Optional bool. 
                                             
                                             Specifies whether to blacken the pixels around each object or feature in each image tile.

                                             This parameter only applies when the metadata format is set to Labeled_Tiles and an input feature class or classified raster has been specified.

                                             - False : Pixels surrounding objects or features will not be blackened. This is the default.

                                             - True : Pixels surrounding objects or features will be blackened.

    ------------------------------------     --------------------------------------------------------------------
    fix_chip_size                            Optional bool. Specifies whether to crop the exported tiles such that they are all the same size.

                                             This parameter only applies when the metadata format is set to Labeled_Tiles and an input feature class or classified raster has been specified.

                                             - True : Exported tiles will be the same size and will center on the feature. This is the default.

                                             - False : Exported tiles will be cropped such that the bounding geometry surrounds only the feature in the tile.
    ------------------------------------     --------------------------------------------------------------------
    gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
    ------------------------------------     --------------------------------------------------------------------
    future                                   Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
    ====================================     ====================================================================

    :return:
        Output string containing the location of the exported training data

    """

    #task = "ExportTrainingDataforDeepLearning"

    gis = _arcgis.env.active_gis if gis is None else gis
    return gis._tools.rasteranalysis.export_training_data_for_deep_learning(input_raster=input_raster,
                                                                            input_class_data=input_class_data,
                                                                            chip_format=chip_format,
                                                                            tile_size=tile_size,
                                                                            stride_size=stride_size,
                                                                            metadata_format=metadata_format,
                                                                            class_value_field=classvalue_field,
                                                                            buffer_radius=buffer_radius,
                                                                            output_location=output_location,
                                                                            input_mask_polygons=input_mask_polygons,
                                                                            rotation_angle=rotation_angle,
                                                                            reference_system=reference_system,
                                                                            process_all_raster_items=process_all_raster_items,
                                                                            blacken_around_feature=blacken_around_feature,
                                                                            fix_chip_size=fix_chip_size,
                                                                            context=context,
                                                                            future=future,
                                                                            **kwargs)

    """
    url = gis.properties.helperServices.rasterAnalytics.url
    gptool = _arcgis.gis._GISResource(url, gis)

    params = {}

    if output_location:
        params["outputLocation"] = output_location
    else:
        raise RuntimeError("output_location cannot be None")

    if input_raster:
        params["inputRaster"] = _layer_input(input_raster)
    else:
        raise RuntimeError("input_raster cannot be None")

    if input_class_data:
        params["inputClassData"] = _layer_input(input_class_data)

    if chip_format is not None:
        chipFormatAllowedValues = ['TIFF', 'PNG', 'JPEG','MRF']
        if not chip_format in chipFormatAllowedValues:
            raise RuntimeError('chip_format can only be one of the following: '+ str(chipFormatAllowedValues))
        params["chipFormat"] = chip_format

    if tile_size:
        params["tileSize"] = tile_size

    if stride_size:
        params["strideSize"] = stride_size

    if metadata_format is not None:
        metadataFormatAllowedValues = ['KITTI_rectangles', 'PASCAL_VOC_rectangles', 'Classified_Tiles', 'RCNN_Masks', 'Labeled_Tiles']
        if not metadata_format in metadataFormatAllowedValues:
            raise RuntimeError('metadata_format can only be one of the following: '+ str(metadataFormatAllowedValues))

        params['metadataFormat'] = metadata_format

    if buffer_radius is not None:
        params["bufferRadius"]= buffer_radius

    if classvalue_field is not None:
        params["classValueField"]= classvalue_field

    if input_mask_polygons:
        params["inputMaskPolygons"] = _layer_input(input_mask_polygons)

    if rotation_angle:
        params["rotationAngle"] = rotation_angle

    _set_context(params, context)

    task_url, job_info, job_id = _analysis_job(gptool, task, params)

    job_info = _analysis_job_status(gptool, task_url, job_info)
    job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
    item_properties = {
        "properties": {
            "jobUrl": task_url + '/jobs/' + job_info['jobId'],
            "jobType": "GPServer",
            "jobId": job_info['jobId'],
            "jobStatus": "completed"
        }
    }
    return job_values["outLocation"]["uri"]

    """


def list_models(*,
                gis=None,
                future=False,
                **kwargs):
    """
    Function is used to list all the installed deep learning models.

    ==================     ====================================================================
    **Argument**           **Description**
    ------------------     --------------------------------------------------------------------
    gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
    ------------------     --------------------------------------------------------------------
    future                 Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
    ==================     ====================================================================

    :return:
        list of deep learning models installed

    """



    #task = "ListDeepLearningModels"

    gis = _arcgis.env.active_gis if gis is None else gis
    return gis._tools.rasteranalysis.list_deep_learning_models(future=future,
                                                               **kwargs)
    """
    url = gis.properties.helperServices.rasterAnalytics.url
    gptool = _arcgis.gis._GISResource(url, gis)
    params = {}
    task_url, job_info, job_id = _analysis_job(gptool, task, params)

    job_info = _analysis_job_status(gptool, task_url, job_info)
    job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
    item_properties = {
        "properties": {
            "jobUrl": task_url + '/jobs/' + job_info['jobId'],
            "jobType": "GPServer",
            "jobId": job_info['jobId'],
            "jobStatus": "completed"
        }
    }

    output_model_list = []
    if isinstance(job_values["deepLearningModels"], list) and job_values["deepLearningModels"] is not None:
        for element in job_values["deepLearningModels"]:
            if isinstance(element,dict):
                if "id" in element.keys():
                    item = gis.content.get(element["id"])
                    output_model_list.append(Model(item))
    return output_model_list
    """

def classify_objects(input_raster,
                     model,
                     model_arguments=None,
                     input_features=None,
                     class_label_field=None,
                     process_all_raster_items=False,
                     output_name=None,
                     context=None,
                     *,
                     gis=None,
                     future=False,
                     **kwargs):

    """
    Function can be used to output feature service with assigned class label for each feature based on
    information from overlapped imagery data using the designated deep learning model. 

    ====================================     ====================================================================
    **Argument**                             **Description**
    ------------------------------------     --------------------------------------------------------------------
    input_raster                             Required. raster layer that contains objects that needs to be classified.
    ------------------------------------     --------------------------------------------------------------------
    model                                    Required model object.
    ------------------------------------     --------------------------------------------------------------------
    model_arguments                          Optional dictionary. Name-value pairs of arguments and their values that can be customized by the clients.
                                             
                                             eg: {"name1":"value1", "name2": "value2"}
    ------------------------------------     --------------------------------------------------------------------
    input_features                           Optional feature layer.
                                             The point, line, or polygon input feature layer that identifies the location of each object to be 
                                             classified and labelled. Each row in the input feature layer represents a single object.

                                             If no input feature layer is specified, the function assumes that each input image contains a single object 
                                             to be classified. If the input image or images use a spatial reference, the output from the function is a 
                                             feature layer, where the extent of each image is used as the bounding geometry for each labelled 
                                             feature layer. If the input image or images are not spatially referenced, the output from the function 
                                             is a table containing the image ID values and the class labels for each image.
    ------------------------------------     --------------------------------------------------------------------
    class_label_field                        Optional str. The name of the field that will contain the classification label in the output feature layer.

                                             If no field name is specified, a new field called ClassLabel will be generated in the output feature layer.

                                             Example:
                                                "ClassLabel"
    ------------------------------------     --------------------------------------------------------------------
    process_all_raster_items                 Optional bool. 

                                             If set to False, all raster items in the image service will be mosaicked together and processed. This is the default.

                                             If set to True, all raster items in the image service will be processed as separate images.
    ------------------------------------     --------------------------------------------------------------------
    output_name                              Optional. If not provided, a Feature layer is created by the method and used as the output .
                                             You can pass in an existing Feature Service Item from your GIS to use that instead.
                                             Alternatively, you can pass in the name of the output Feature Service that should be created by this method
                                             to be used as the output for the tool.
                                             A RuntimeError is raised if a service by that name already exists
    ------------------------------------     --------------------------------------------------------------------
    context                                  Optional dictionary. Context contains additional settings that affect task execution.
                                             Dictionary can contain value for following keys:

                                             - cellSize - Set the output raster cell size, or resolution

                                             - extent - Sets the processing extent used by the function

                                             - parallelProcessingFactor - Sets the parallel processing factor. Default is "80%"

                                             - processorType - Sets the processor type. "CPU" or "GPU"

                                             Eg: {"processorType" : "CPU"}

                                             Setting context parameter will override the values set using arcgis.env 
                                             variable for this particular function.
    ------------------------------------     --------------------------------------------------------------------
    gis                                      Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
    ====================================     ====================================================================

    :return:
        The output feature layer item containing the classified objects

    """

    gis = _arcgis.env.active_gis if gis is None else gis
    return gis._tools.rasteranalysis.classify_objects_using_deep_learning(input_raster=input_raster,
                                                                          input_features=input_features,
                                                                          output_feature_class=output_name,
                                                                          model=model,
                                                                          model_arguments=model_arguments,
                                                                          class_label_field=class_label_field,
                                                                          process_all_raster_items=process_all_raster_items,
                                                                          context=context,
                                                                          future=future,
                                                                          **kwargs)

class Model:
    def __init__(self, model = None):
        self._model_package = False
        if isinstance(model, _arcgis.gis.Item):
            self._model = _json.dumps({ "itemId" : model.itemid })
            self._model_package = True
            self.item = model

    def _repr_html_(self):
        if self._model_package:
            if  hasattr(self,"item"):
                self.item._repr_html_()
        else:
            self.__repr__()

    def __repr__(self):
        if self._model_package:
            model = _json.loads(self._model)
            if "url" in model.keys():
                return '<Model:%s>' % self._model
            return '<Model Title:%s owner:%s>' %(self.item.title, self.item.owner)

        else:
            try:
                return '<Model:%s>' % self._model
            except:
                return '<empty Model>'


    def from_json(self, model):
        """
        Function is used to initialise Model object from model definition JSON
        
        eg usage:

        model = Model()

        model.from_json({"Framework" :"TensorFlow",
                        "ModelConfiguration":"DeepLab",
                        "InferenceFunction":"``[functions]System\\DeepLearning\\ImageClassifier.py``",
                        "ModelFile":"``\\\\folder_path_of_pb_file\\frozen_inference_graph.pb``",
                        "ExtractBands":[0,1,2],
                        "ImageWidth":513,
                        "ImageHeight":513,
                        "Classes": [ { "Value":0, "Name":"Evergreen Forest", "Color":[0, 51, 0] },
                                     { "Value":1, "Name":"Grassland/Herbaceous", "Color":[241, 185, 137] },
                                     { "Value":2, "Name":"Bare Land", "Color":[236, 236, 0] },
                                     { "Value":3, "Name":"Open Water", "Color":[0, 0, 117] },
                                     { "Value":4, "Name":"Scrub/Shrub", "Color":[102, 102, 0] },
                                     { "Value":5, "Name":"Impervious Surface", "Color":[236, 236, 236] } ] })

        """
        if isinstance(model, dict):
            self._model = model
            self._model_package = False

    def from_model_path(self, model):
        """
        Function is used to initialise Model object from url of model package or path of model definition file
        eg usage:

        model = Model()

        model.from_model_path("https://xxxportal.esri.com/sharing/rest/content/items/<itemId>")

        or
        model = Model()

        model.from_model_path(``"\\\\sharedstorage\\sharefolder\\findtrees.emd"``)

        """
        if 'http:' in model or 'https:' in model:
            self._model = _json.dumps({ 'url' : model })
            self._model_package = True
        else:
            self._model = _json.dumps({ 'uri' : model })
            self._model_package = False


    def install(self,
                *,
                gis=None,
                future=False,
                **kwargs):

        """
        Function is used to install the uploaded model package (*.dlpk). Optionally after inferencing
        the necessary information using the model, the model can be uninstalled by uninstall_model()


        ==================     ====================================================================
        **Argument**           **Description**
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------     --------------------------------------------------------------------
        future                 Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ==================     ====================================================================

        :return:
            Path where model is installed

        """
        if self._model_package is False:
            raise RuntimeError("model object should be created from a portal item or a portal url")

        if self._model is None:
            raise RuntimeError("For install/uninstall model object should be created from a portal item or portal url")

        #task = "InstallDeepLearningModel"

        gis = _arcgis.env.active_gis if gis is None else gis
        return gis._tools.rasteranalysis.install_deep_learning_model(model_package=self._model,
                                                                     future=future,
                                                                     **kwargs)

        """
        url = gis.properties.helperServices.rasterAnalytics.url
        gptool = _arcgis.gis._GISResource(url, gis)

        params = {}

        if self._model is None:
            raise RuntimeError("For install/uninstall model object should be created from a portal item or portal url")
        else:
            params["modelPackage"] = self._model

        task_url, job_info, job_id = _analysis_job(gptool, task, params)

        job_info = _analysis_job_status(gptool, task_url, job_info)
        job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
        item_properties = {
            "properties": {
                "jobUrl": task_url + '/jobs/' + job_info['jobId'],
                "jobType": "GPServer",
                "jobId": job_info['jobId'],
                "jobStatus": "completed"
            }
        }

        return job_values["installSucceed"]
        """


    def query_info(self,
                   *,
                   gis=None,
                   future=False,
                   **kwargs):
        """
        Function is used to extract the deep learning model specific settings from the model package item or model definition file.

        ==================     ====================================================================
        **Argument**           **Description**
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------     --------------------------------------------------------------------
        future                 Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ==================     ====================================================================

        :return:
           The key model information in dictionary format that describes what the settings are essential for this type of deep learning model.
        """


        #task = "QueryDeepLearningModelInfo"

        gis = _arcgis.env.active_gis if gis is None else gis
        if self._model is None:
            raise RuntimeError('model cannot be None')

        return gis._tools.rasteranalysis.query_deep_learning_model_info(model=self._model,
                                                                        future=future,
                                                                        **kwargs)

        """
        url = gis.properties.helperServices.rasterAnalytics.url
        gptool = _arcgis.gis._GISResource(url, gis)

        params = {}

        if self._model is None:
            raise RuntimeError('model cannot be None')
        else:
            params["model"] = self._model

        task_url, job_info, job_id = _analysis_job(gptool, task, params)

        job_info = _analysis_job_status(gptool, task_url, job_info)
        job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
        item_properties = {
            "properties": {
                "jobUrl": task_url + '/jobs/' + job_info['jobId'],
                "jobType": "GPServer",
                "jobId": job_info['jobId'],
                "jobStatus": "completed"
            }
        }
        output = job_values["outModelInfo"]
        print(output)
        try:
            dict_output =  _json.loads(output["modelInfo"])
            return dict_output
        except:
            return output
        """

    def uninstall(self,
                  *,
                  gis=None,
                  future=False,
                  **kwargs):

        """
        Function is used to uninstall the uploaded model package that was installed using the install_model()
        This function will delete the named deep learning model from the server but not the portal item.

        ==================     ====================================================================
        **Argument**           **Description**
        ------------------     --------------------------------------------------------------------
        gis                    Optional GIS. The GIS on which this tool runs. If not specified, the active GIS is used.
        ------------------     --------------------------------------------------------------------
        future                 Keyword only parameter. Optional boolean. If True, the result will be a GPJob object and results will be returned asynchronously.
        ==================     ====================================================================

        :return:
            itemId of the uninstalled model package item

        """
        if self._model_package is False:
            raise RuntimeError("For install/uninstall model object should be created from a portal item or a portal url")

        #task = "UninstallDeepLearningModel"

        gis = _arcgis.env.active_gis if gis is None else gis

        if self._model is None:
            raise RuntimeError('model_package cannot be None')

        return gis._tools.rasteranalysis.uninstall_deep_learning_model(model_item_id=self._model,
                                                                       future=future,
                                                                       **kwargs)

        """
        url = gis.properties.helperServices.rasterAnalytics.url
        gptool = _arcgis.gis._GISResource(url, gis)

        params = {}

        if self._model is None:
            raise RuntimeError('model_package cannot be None')
        else:
            params["modelItemId"] = self._model

        task_url, job_info, job_id = _analysis_job(gptool, task, params)

        job_info = _analysis_job_status(gptool, task_url, job_info)
        job_values = _analysis_job_results(gptool, task_url, job_info, job_id)
        item_properties = {
            "properties": {
                "jobUrl": task_url + '/jobs/' + job_info['jobId'],
                "jobType": "GPServer",
                "jobId": job_info['jobId'],
                "jobStatus": "completed"
            }
        }

        return job_values["uninstallSucceed"]
        """
