#!/usr/bin/env python3
"""Test for ee._cloud_api_utils."""

import json
from unittest import mock
import warnings

from google.oauth2 import credentials
from googleapiclient import discovery
import requests

import unittest
from ee import _cloud_api_utils
from ee import ee_exception


class CloudApiUtilsTest(unittest.TestCase):

  def test_build_cloud_resource(self):
    base = 'https://earthengine.basetest'
    path = '$discovery/rest?version=v1&prettyPrint=false'
    def check_build(api, unused_version, **kwargs):
      self.assertEqual('earthengine', api)
      self.assertEqual(base + '/' + path, kwargs.get('discoveryServiceUrl'))
      http = kwargs.get('http')
      self.assertIsNotNone(http)
      self.assertIsNone(http.credentials.quota_project_id)
      return mock.Mock()

    with mock.patch.object(discovery, 'build', side_effect=check_build) as run:
      session = requests.Session()
      cred_args = dict(token=None, refresh_token='rt', quota_project_id='qp')
      resource = _cloud_api_utils.build_cloud_resource(
          base, session, credentials=credentials.Credentials(**cred_args)
      )
      self.assertEqual(base, resource._baseUrl)
      run.assert_called_once()

  def test_convert_dict_simple(self):
    result = _cloud_api_utils._convert_dict({
        'x': 99,
        'y': 2
    }, {
        'x': 'a',
        'y': ('c', lambda x: x + 1)
    })
    self.assertEqual({'a': 99, 'c': 3}, result)

  def test_convert_dict_with_defaults(self):
    result = _cloud_api_utils._convert_dict(
        {
            'x': 99
        }, {'x': 'a'}, defaults={
            'q': 'r',
            'a': 'aaa'
        })
    self.assertEqual({'a': 99, 'q': 'r'}, result)

  def test_convert_dict_with_warnings(self):
    with warnings.catch_warnings(record=True) as w:
      result = _cloud_api_utils._convert_dict({
          'x': 1,
          'y': 2
      }, {
          'x': 'flan',
          'y': 'flan'
      })
      self.assertEqual({'flan': 2}, result)
      self.assertEqual('Multiple request parameters converted to flan',
                       str(w[0].message))

    with warnings.catch_warnings(record=True) as w:
      result = _cloud_api_utils._convert_dict({'x': 1, 'y': 2}, {'x': 'flan'})
      self.assertEqual({'flan': 1}, result)

    with warnings.catch_warnings(record=True) as w:
      result = _cloud_api_utils._convert_dict(
          {
              'x': 1,
              'y': 2
          }, {'x': 'flan'}, key_warnings=True)
      self.assertEqual({'flan': 1}, result)
      self.assertEqual('Unrecognized key y ignored', str(w[0].message))

  def test_convert_value(self):
    self.assertEqual('x', _cloud_api_utils._convert_value('a', {'a': 'x'}, 'z'))
    self.assertEqual('z', _cloud_api_utils._convert_value('b', {'a': 'x'}, 'z'))

  def test_convert_msec_to_timestamp(self):
    self.assertEqual('2018-07-31T20:46:28.888000Z',
                     _cloud_api_utils._convert_msec_to_timestamp(1533069988888))

  def test_convert_timestamp_to_msec(self):
    self.assertEqual(
        1533069988888,
        _cloud_api_utils._convert_timestamp_to_msec(
            '2018-07-31T20:46:28.888000Z'))
    self.assertEqual(
        1533069988000,
        _cloud_api_utils._convert_timestamp_to_msec(
            '2018-07-31T20:46:28Z'))

  def test_convert_bounding_box_to_geo_json(self):
    geo_json = _cloud_api_utils._convert_bounding_box_to_geo_json(
        [-105, 45, 10, 60])
    geo_json_parsed = json.loads(geo_json)
    self.assertEqual({
        'type':
            'Polygon',
        'coordinates': [[[-105, 45], [10, 45], [10, 60], [-105, 60], [-105, 45]]
                       ]
    }, geo_json_parsed)

  def test_convert_asset_type_for_create_asset(self):
    self.assertEqual(
        'IMAGE_COLLECTION',
        _cloud_api_utils.convert_asset_type_for_create_asset('ImageCollection'))
    self.assertEqual(
        'IMAGE_COLLECTION',
        _cloud_api_utils.convert_asset_type_for_create_asset(
            'IMAGE_COLLECTION'))
    self.assertEqual(
        'FOLDER',
        _cloud_api_utils.convert_asset_type_for_create_asset('Folder'))
    self.assertEqual(
        'FOLDER',
        _cloud_api_utils.convert_asset_type_for_create_asset('FOLDER'))

  def test_convert_asset_id_to_asset_name(self):
    self.assertEqual(
        'projects/earthengine-public/assets/path/to/asset',
        _cloud_api_utils.convert_asset_id_to_asset_name('path/to/asset'))
    self.assertEqual(
        'projects/earthengine-legacy/assets/users/path/to/asset',
        _cloud_api_utils.convert_asset_id_to_asset_name('users/path/to/asset'))
    self.assertEqual(
        'projects/earthengine-legacy/assets/projects/path/to/asset',
        _cloud_api_utils.convert_asset_id_to_asset_name(
            'projects/path/to/asset'))
    self.assertEqual(
        'projects/foobar/assets/baz',
        _cloud_api_utils.convert_asset_id_to_asset_name(
            'projects/foobar/assets/baz'))
    self.assertEqual(
        'projects/foobar/assets/',
        _cloud_api_utils.convert_asset_id_to_asset_name(
            'projects/foobar/assets'
        ),
    )
    self.assertEqual(
        'projects/foobar/assets/',
        _cloud_api_utils.convert_asset_id_to_asset_name(
            'projects/foobar/assets/'
        ),
    )

  def test_split_asset_name(self):
    parent, asset_id = _cloud_api_utils.split_asset_name(
        'projects/earthengine-legacy/assets/users/path/to/asset')
    self.assertEqual('projects/earthengine-legacy', parent)
    self.assertEqual('users/path/to/asset', asset_id)

  def test_convert_operation_name_to_task_id(self):
    self.assertEqual(
        'taskId',
        _cloud_api_utils.convert_operation_name_to_task_id(
            'operations/taskId'))
    self.assertEqual(
        'taskId',
        _cloud_api_utils.convert_operation_name_to_task_id(
            'projects/test/operations/taskId'))
    self.assertEqual(
        'taskId',
        _cloud_api_utils.convert_operation_name_to_task_id(
            'projects/operations/operations/taskId'))
    self.assertEqual(
        'taskId', _cloud_api_utils.convert_operation_name_to_task_id('taskId'))

  def test_convert_task_id_to_operation_name(self):
    self.assertEqual(
        'projects/earthengine-legacy/operations/taskId',
        _cloud_api_utils.convert_task_id_to_operation_name(
            'earthengine-legacy', 'taskId'
        ),
    )

  def test_encode_number_as_cloud_value(self):
    self.assertEqual({
        'constantValue': 112233
    }, _cloud_api_utils.encode_number_as_cloud_value(112233))
    # Large integer not representable as double.
    self.assertEqual({
        'integerValue': '112233445566778899'
    }, _cloud_api_utils.encode_number_as_cloud_value(112233445566778899))
    # Large integer representable as double.
    self.assertEqual({
        'constantValue': 3.402823669209385e+38
    }, _cloud_api_utils.encode_number_as_cloud_value(1 << 128))

  def test_convert_algorithms(self):
    result = _cloud_api_utils.convert_algorithms({
        'algorithms': [
            {
                'name': 'algorithms/algNoArgs',
                'description': 'desc',
                'returnType': 'ret'
            },
            {
                'name': 'algorithms/algNoDesc',
                'arguments': [
                    {
                        'argumentName': 'argNoDesc'
                    },
                    {
                        'argumentName': 'argOptional',
                        'description': 'descArg',
                        'type': 't',
                        'optional': True,
                        'defaultValue': [1, 2]
                    }]
            },
            {
                'name': 'algorithms/algHidden',
                'description': 'desc',
                'returnType': 'ret',
                'hidden': True
            },
            {
                'name': 'algorithms/algPreview',
                'description': 'desc',
                'returnType': 'ret',
                'preview': True
            },
            {
                'name': 'algorithms/algDeprecated',
                'description': 'desc',
                'returnType': 'ret',
                'deprecated': True,
                'deprecationReason': 'reason'
            }]
    })
    self.assertEqual({
        'algNoArgs': {
            'description': 'desc',
            'returns': 'ret',
            'args': []
        },
        'algNoDesc': {
            'description': '',
            'returns': '',
            'args': [
                {
                    'name': 'argNoDesc',
                    'type': '',
                    'description': ''
                },
                {
                    'name': 'argOptional',
                    'description': 'descArg',
                    'type': 't',
                    'optional': True,
                    'default': [1, 2]
                }]
        },
        'algHidden': {
            'description': 'desc',
            'returns': 'ret',
            'args': [],
            'hidden': True
        },
        'algPreview': {
            'description': 'desc',
            'returns': 'ret',
            'args': [],
            'preview': True
        },
        'algDeprecated': {
            'description': 'desc',
            'returns': 'ret',
            'args': [],
            'deprecated': 'reason'
        }
    }, result)

  def test_convert_empty_algorithms(self):
    result = _cloud_api_utils.convert_algorithms({})
    self.assertEqual({}, result)

  def test_convert_to_image_file_format(self):
    self.assertEqual('AUTO_JPEG_PNG',
                     _cloud_api_utils.convert_to_image_file_format(None))
    self.assertEqual('AUTO_JPEG_PNG',
                     _cloud_api_utils.convert_to_image_file_format('auto'))
    self.assertEqual('JPEG',
                     _cloud_api_utils.convert_to_image_file_format('jpg'))
    self.assertEqual('JPEG',
                     _cloud_api_utils.convert_to_image_file_format('jpeg'))
    self.assertEqual('PNG',
                     _cloud_api_utils.convert_to_image_file_format('png'))
    self.assertEqual('GEO_TIFF',
                     _cloud_api_utils.convert_to_image_file_format('GeoTIFF'))
    self.assertEqual('TF_RECORD_IMAGE',
                     _cloud_api_utils.convert_to_image_file_format('TFRecord'))
    self.assertEqual(
        'NPY', _cloud_api_utils.convert_to_image_file_format('numpy')
    )

  def test_convert_to_table_file_format(self):
    self.assertEqual('CSV',
                     _cloud_api_utils.convert_to_table_file_format('csv'))
    self.assertEqual('GEO_JSON',
                     _cloud_api_utils.convert_to_table_file_format('GeoJSON'))
    self.assertEqual('TF_RECORD_TABLE',
                     _cloud_api_utils.convert_to_table_file_format('TFRecord'))

  def test_convert_to_band_list(self):
    self.assertEqual([], _cloud_api_utils.convert_to_band_list(None))
    self.assertEqual(['a'], _cloud_api_utils.convert_to_band_list('a'))
    self.assertEqual(['a', 'b'], _cloud_api_utils.convert_to_band_list('a,b'))
    self.assertEqual(['a', 'b'],
                     _cloud_api_utils.convert_to_band_list(['a', 'b']))

  def test_convert_to_visualization_options(self):
    with self.assertRaises(ee_exception.EEException):
      _cloud_api_utils.convert_to_visualization_options({
          'min': '0,1',
          'max': '2'
      })
    with self.assertRaises(ee_exception.EEException):
      _cloud_api_utils.convert_to_visualization_options({
          'gain': '0,1',
          'bias': '2'
      })
    with self.assertRaises(ee_exception.EEException):
      _cloud_api_utils.convert_to_visualization_options({
          'gain': '0,1',
          'min': '0,1'
      })
    with self.assertRaises(ee_exception.EEException):
      _cloud_api_utils.convert_to_visualization_options({
          'gamma': '2,3'
      })
    with self.assertRaises(ee_exception.EEException):
      _cloud_api_utils.convert_to_visualization_options({'gamma': '0,1'})
    self.assertEqual({
        'ranges': [{'min': 0, 'max': 1}, {'min': 0, 'max': 2}],
        'gamma': {'value': 2.2}
    },
                     _cloud_api_utils.convert_to_visualization_options({
                         'max': '1,2',
                         'gamma': '2.2'
                     }))
    self.assertEqual({
        'ranges': [{'min': 0, 'max': 1}, {'min': -.5, 'max': 0}],
        'paletteColors': ['0', '1', '2']
    },
                     _cloud_api_utils.convert_to_visualization_options({
                         'gain': '2,4',
                         'bias': '0,2',
                         'palette': '0,1,2',
                     }))

  def test_convert_operation_to_task(self):
    self.assertEqual(
        {
            'start_timestamp_ms': 1538676004000,
            'state': 'COMPLETED',
            'description': 'Ingest image: "an/image"',
            'creation_timestamp_ms': 1538676001749,
            'id': '7T42Q7FH4KSIXQKGT6MJFBPX',
            'update_timestamp_ms': 1538676053218,
            'task_type': 'INGEST_IMAGE',
            'destination_uris': ['https://test.com'],
            'name': 'projects/test/operations/7T42Q7FH4KSIXQKGT6MJFBPX',
            'attempt': 42,
            'priority': 100,
        },
        _cloud_api_utils.convert_operation_to_task({
            'metadata': {
                'createTime': '2018-10-04T18:00:01.749999Z',
                'updateTime': '2018-10-04T18:00:53.218488Z',
                'description': 'Ingest image: "an/image"',
                'startTime': '2018-10-04T18:00:04Z',
                'state': 'SUCCEEDED',
                'endTime': '2018-10-04T18:00:53.218488Z',
                'type': 'INGEST_IMAGE',
                'destinationUris': ['https://test.com'],
                'attempt': 42,
                'priority': 100,
            },
            'done': True,
            'name': 'projects/test/operations/7T42Q7FH4KSIXQKGT6MJFBPX',
        }))

  def test_convert_iam_policy_to_acl(self):
    self.assertEqual({
        'owners': ['user:owner@owner.domain'],
        'writers': [],
        'readers': ['group:readerGroup', 'user:readerUser'],
        'all_users_can_read': True
    }, _cloud_api_utils.convert_iam_policy_to_acl({
        'bindings': [{
            'role': 'roles/owner',
            'members': ['user:owner@owner.domain']
        }, {
            'role': 'roles/viewer',
            'members': ['group:readerGroup', 'allUsers', 'user:readerUser']
        }]
    }))

  def test_convert_acl_to_iam_policy(self):
    self.assertEqual({
        'bindings': [{
            'role': 'roles/owner',
            'members': ['user:owner@owner.domain']
        }, {
            'role': 'roles/viewer',
            'members': ['group:readerGroup', 'user:readerUser', 'allUsers']
        }]
    }, _cloud_api_utils.convert_acl_to_iam_policy({
        'owners': ['user:owner@owner.domain'],
        'writers': [],
        'readers': ['group:readerGroup', 'user:readerUser'],
        'all_users_can_read': True
    }))

  def test_convert_to_grid_dimension(self):
    self.assertEqual({'width': 123, 'height': 123},
                     _cloud_api_utils.convert_to_grid_dimensions(123))
    self.assertEqual({'width': 123, 'height': 123},
                     _cloud_api_utils.convert_to_grid_dimensions(123))
    self.assertEqual({'width': 123, 'height': 234},
                     _cloud_api_utils.convert_to_grid_dimensions((123, 234)))

  def test_to_image_old_source(self):
    """Converts legacy keys in the source object."""
    old_sources = [{
        'primaryPath': 'path1',
        'additionalPaths': ['path2'],
        'affineTransform': {
            'scaleX': 1,
            'shearX': 2,
            'translateX': 3,
            'shearY': 4,
            'scaleY': 5,
            'translateY': 6,
        }
    }]
    expected = [{
        'uris': ['path1', 'path2'],
        'affineTransform': {
            'scaleX': 1,
            'shearX': 2,
            'translateX': 3,
            'shearY': 4,
            'scaleY': 5,
            'translateY': 6,
        }
    }]
    self.assertEqual(
        expected,
        _cloud_api_utils.convert_sources_to_one_platform_sources(old_sources))

  def test_to_image_one_platform_source(self):
    """Leaves new keys in the source object unchanged."""
    old_sources = [{
        'uris': ['path1'],
        'affineTransform': {
            'scaleX': 1,
            'shearX': 2,
            'translateX': 3,
            'shearY': 4,
            'scaleY': 5,
            'translateY': 6,
        }
    }]
    expected = [{
        'uris': ['path1'],
        'affineTransform': {
            'scaleX': 1,
            'shearX': 2,
            'translateX': 3,
            'shearY': 4,
            'scaleY': 5,
            'translateY': 6,
        }
    }]
    self.assertEqual(
        expected,
        _cloud_api_utils.convert_sources_to_one_platform_sources(old_sources))

  def test_to_shape_one_platform_source(self):
    old_sources = [{
        'charset':
            'UTF-8',
        'maxError':
            1,
        'maxVertices':
            100000,
        'primaryPath':
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.shp',
        'additionalPaths': [
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.dbf',
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.prj',
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.shx',
        ],
    }]
    expected = [{
        'charset':
            'UTF-8',
        'maxErrorMeters':
            1,
        'maxVertices':
            100000,
        'uris': [
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.shp',
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.dbf',
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.prj',
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/test.shx',
        ],
    }]
    self.assertEqual(
        expected,
        _cloud_api_utils.convert_sources_to_one_platform_sources(old_sources))

  def test_to_csv_one_platform_source(self):
    old_sources = [{
        'charset':
            'UTF-8',
        'maxError':
            1,
        'maxVertices':
            1000000,
        'geodesic':
            True,
        'primaryGeometryColumn':
            'geometry0',
        'xColumn':
            'x',
        'yColumn':
            'y',
        'primaryPath':
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/wildlife.csv',
    }]
    expected = [{
        'charset':
            'UTF-8',
        'maxErrorMeters':
            1,
        'maxVertices':
            1000000,
        'geodesic':
            True,
        'primaryGeometryColumn':
            'geometry0',
        'xColumn':
            'x',
        'yColumn':
            'y',
        'uris': [
            'gs://ee.google.com.a.appspot.com/qGc_ZVNWLKpokgLv/wildlife.csv',
        ],
    }]
    self.assertEqual(
        expected,
        _cloud_api_utils.convert_sources_to_one_platform_sources(old_sources))

  def test_convert_to_operation_state(self):
    def convert(state):
      return _cloud_api_utils.convert_to_operation_state(state)

    self.assertEqual('CANCELLED', convert('CANCELLED'))
    self.assertEqual('CANCELLING', convert('CANCEL_REQUESTED'))
    self.assertEqual('CANCELLING', convert('CANCELLING'))
    self.assertEqual('FAILED', convert('FAILED'))
    self.assertEqual('PENDING', convert('PENDING'))
    self.assertEqual('PENDING', convert('READY'))
    self.assertEqual('RUNNING', convert('RUNNING'))
    self.assertEqual('SUCCEEDED', convert('COMPLETED'))
    self.assertEqual('SUCCEEDED', convert('SUCCEEDED'))
    self.assertEqual('UNKNOWN', convert('random_string'))
    self.assertEqual('UNKNOWN', convert('UNKNOWN'))


if __name__ == '__main__':
  unittest.main()
