from __future__ import print_function, division
import os
import re
import datetime
import sys
from os.path import join, isdir, isfile, dirname, abspath
import pandas as pd
import yaml
import psycopg2 as db
from nilmtk.measurement import measurement_columns
from nilmtk.measurement import LEVEL_NAMES
from nilmtk.datastore import Key
from nilm_metadata import convert_yaml_to_hdf5
from nilmtk.utils import get_module_directory
import shutil
import tempfile

"""
MANUAL:

dataport is a large dataset hosted in a remote SQL database. This
file provides a function to download the dataset and save it to disk
as NILMTK-DF. Since downloading the entire dataset will likely take >
24 hours, this function provides some options to allow you to download
only a subset of the data.

'''''''''''''''' Previous Version '''''''''''''''''''''
For example, to only load house 26 for April 2014:
    from nilmtk.dataset_converters.dataport.download_dataport
    import download_dataport
    download_dataport(
        'username',
        'password',
        '/path/output_filename.h5',
        periods_to_load={26: ('2014-04-01', '2014-05-01')}
    )
'''''''''''''''' Previous Version '''''''''''''''''''''

'''''''''''''''' New Version '''''''''''''''''''''

    from nilmtk.dataset_converters.dataport.download_dataport
    import download_dataport,
            _dataport_dataframe_to_hdf,
            view_database_tables,
            view_buildings,
            view_data_window

    # see all available tables in the dataport database.
    view_database_tables(
        'username',
        'password',
        'database_schema'   # university or commercial
    )

    # show the list of all available buildings
    view_buildings(
        'username',
        'password',
        'database_schema',  # university or commercial
        'table_name'        # for example 'electricity_egauge_15min', 'electricity_egauge_hours'
    )

    # view data collection window of selected buildings
    view_data_window(
        'username',
        'password',
        'database_schema',  # university or commercial
        'table_name',       # for example 'electricity_egauge_15min','electricity_egauge_hours'
        [18,26,43,44]       # data collection window of building 18,26,43 and 44 respectively
    )

    # download the dataset.
    For example, loading electricity_egauge_hours from 2018-11-17 to
    2019-12-17 of building 26
    download_dataport(
        'username',
        'password',
        '/path/output_filename.h5',
        'university',
        'electricity_egauge_hours',
        periods_to_load={ 26: ('2018-11-17', '2019-12-17')})

'''''''''''''''' New Version '''''''''''''''''''''

REQUIREMENTS:

On Ubuntu:
* sudo apt-get install libpq-dev
* sudo pip install psycopg2

TODO:
* intelligently handle queries that fail due to network
* integrate 'grid' (use - gen) and 'gen'

"""
feed_mapping = {
                'use': {},
                'air1': {'type': 'air conditioner'},
                'air2': {'type': 'air conditioner'},
                'air3': {'type': 'air conditioner'},
                'airwindowunit1': {'type': 'air conditioner'},
                'aquarium1': {'type': 'appliance'},
                'bathroom1': {'type': 'sockets', 'room': 'bathroom'},
                'bathroom2': {'type': 'sockets', 'room': 'bathroom'},
                'bedroom1': {'type': 'sockets', 'room': 'bedroom'},
                'bedroom2': {'type': 'sockets', 'room': 'bedroom'},
                'bedroom3': {'type': 'sockets', 'room': 'bedroom'},
                'bedroom4': {'type': 'sockets', 'room': 'bedroom'},
                'bedroom5': {'type': 'sockets', 'room': 'bedroom'},
                'car1': {'type': 'electric vehicle'},
                'clotheswasher1': {'type': 'washing machine'},
                'clotheswasher_dryg1': {'type': 'washer dryer'},
                'diningroom1': {'type': 'sockets', 'room': 'dining room'},
                'diningroom2': {'type': 'sockets', 'room': 'dining room'},
                'dishwasher1': {'type': 'dish washer'},
                'disposal1': {'type': 'waste disposal unit'},
                'drye1': {'type': 'spin dryer'},
                'dryg1': {'type': 'spin dryer'},
                'freezer1': {'type': 'freezer'},
                'furnace1': {'type': 'electric furnace'},
                'furnace2': {'type': 'electric furnace'},
                'garage1': {'type': 'sockets', 'room': 'dining room'},
                'garage2': {'type': 'sockets', 'room': 'dining room'},
                'gen': {},
                'grid': {},
                'heater1': {'type': 'electric space heater'},
                'housefan1': {'type': 'electric space heater'},
                'icemaker1': {'type': 'appliance'},
                'jacuzzi1': {'type': 'electric hot tub heater'},
                'kitchen1': {'type': 'sockets', 'room': 'kitchen'},
                'kitchen2': {'type': 'sockets', 'room': 'kitchen'},
                'kitchenapp1': {'type': 'sockets', 'room': 'kitchen'},
                'kitchenapp2': {'type': 'sockets', 'room': 'kitchen'},
                'lights_plugs1': {'type': 'light'},
                'lights_plugs2': {'type': 'light'},
                'lights_plugs3': {'type': 'light'},
                'lights_plugs4': {'type': 'light'},
                'lights_plugs5': {'type': 'light'},
                'lights_plugs6': {'type': 'light'},
                'livingroom1': {'type': 'sockets', 'room': 'living room'},
                'livingroom2': {'type': 'sockets', 'room': 'living room'},
                'microwave1': {'type': 'microwave'},
                'office1': {'type': 'sockets', 'room': 'office'},
                'outsidelights_plugs1': {'type': 'sockets', 'room': 'outside'},
                'outsidelights_plugs2': {'type': 'sockets', 'room': 'outside'},
                'oven1': {'type': 'oven'},
                'oven2': {'type': 'oven'},
                'pool1': {'type': 'electric swimming pool heater'},
                'pool2': {'type': 'electric swimming pool heater'},
                'poollight1': {'type': 'light'},
                'poolpump1': {'type': 'electric swimming pool heater'},
                'pump1': {'type': 'appliance'},
                'range1': {'type': 'stove'},
                'refrigerator1': {'type': 'fridge'},
                'refrigerator2': {'type': 'fridge'},
                'security1': {'type': 'security alarm'},
                'shed1': {'type': 'sockets', 'room': 'shed'},
                'sprinkler1': {'type': 'appliance'},
                'unknown1': {'type': 'unknown'},
                'unknown2': {'type': 'unknown'},
                'unknown3': {'type': 'unknown'},
                'unknown4': {'type': 'unknown'},
                'utilityroom1': {'type': 'sockets', 'room': 'utility room'},
                'venthood1': {'type': 'appliance'},
                'waterheater1': {'type': 'electric water heating appliance'},
                'waterheater2': {'type': 'electric water heating appliance'},
                'winecooler1': {'type': 'appliance'},
                }

feed_ignore = ['gen', 'grid']

def database_assert(database_table):
    assert (
            database_table == 'electricity_egauge_15min' or
            database_table == 'electricity_egauge_hours' or
            database_table == 'electricity_egauge_minutes' or
            database_table == 'electricity_egauge_seconds'
            ), "Table not compatible with NILMTK"

def view_database_tables(
    database_username,
    database_password,
    database_schema
):
    database_host = 'dataport.pecanstreet.org'
    database_port = '5434'
    database_name = 'postgres'

    try:
        conn = db.connect('host=' + database_host +
                          ' port=' + database_port +
                          ' dbname=' + database_name +
                          ' user=' + database_username +
                          ' password=' + database_password)
    except:
        print('Could not connect to remote database')
        raise

    # Loading university schemas
    sql_query = ("SELECT table_name" +
                 " FROM information_schema.views" +
                 " WHERE table_schema ='" + database_schema + "'" +
                 " ORDER BY table_name")
    database_tables = pd.read_sql(sql_query, conn)['table_name'].tolist()

    df = pd.DataFrame({database_schema: database_tables})
    print(df)
    conn.close()

def view_buildings(
    database_username,
    database_password,
    database_schema,
    database_table
):

    database_assert(database_table)
    database_host = 'dataport.pecanstreet.org'
    database_port = '5434'
    database_name = 'postgres'

    # try to connect to database
    try:
        conn = db.connect('host=' + database_host +
                          ' port=' + database_port +
                          ' dbname=' + database_name +
                          ' user=' + database_username +
                          ' password=' + database_password)
    except:
        print('Could not connect to remote database')
        raise

    # select all buildings for the database_table
    sql_query = ('SELECT DISTINCT dataid' +
                 ' FROM university.metadata' +
                 ' WHERE' + database_table +
                 ' ORDER BY dataid')

    buildings_in_table = pd.read_sql(sql_query, conn)['dataid'].tolist()
    print(buildings_in_table)
    conn.close()

def view_data_window(
                     database_username,
                     database_password,
                     database_schema,
                     database_table,
                     building_no=None):

    database_assert(database_table)
    database_host = 'dataport.pecanstreet.org'
    database_port = '5434'
    database_name = 'postgres'

    # try to connect to database
    try:
        conn = db.connect('host=' + database_host +
                          ' port=' + database_port +
                          ' dbname=' + database_name +
                          ' user=' + database_username +
                          ' password=' + database_password)
    except:
        print('Could not connect to remote database')
        raise

    # select all buildings for the database_table
    sql_query = ('SELECT DISTINCT dataid' +
                 ' FROM university.metadata' +
                 ' WHERE' + database_table +
                 ' ORDER BY dataid')

    if(not (building_no)):
        print(" Please provide the list of building numbers ")
    else:
        for each_building in building_no:
            sql_query = ('SELECT MIN(egauge_min_time) AS minlocalminute,' +
                         ' MAX(egauge_max_time) AS maxlocalminute' +
                         ' FROM university.metadata' +
                         ' WHERE dataid=' + str(each_building))

            timestamps = pd.read_sql(sql_query, conn)
            first_timestamp_in_table = timestamps['minlocalminute'][0]
            last_timestamp_in_table = timestamps['maxlocalminute'][0]
            print(str(each_building),
                  "\t\t", first_timestamp_in_table,
                  "\t\t", last_timestamp_in_table)
        print("Done loading all the buildings!!")

    conn.close()

def download_dataport(database_username,
                      database_password, hdf_filename,
                      database_schema='university',
                      user_selected_table='electricity_egauge_minutes',
                      periods_to_load=None):
    """
    Downloads data from dataport database into an HDF5 file.

    Parameters
    ----------
    hdf_filename : str
        Output HDF filename.  If file exists already then will be deleted.
    database_username, database_password, database_schema,user_selected_table, hdf_filename : str
    periods_to_load : dict of tuples, optional
       Key of dict is the building number (int).
       Values are (<start date>, <end date>)
       e.g. ("2013-04-01", None) or ("2013-04-01", "2013-08-01")
       defaults to all buildings and all date ranges
    """

    database_assert(user_selected_table)
    # dataport database settings
    database_host = 'dataport.pecanstreet.org'
    database_port = '5434'
    database_name = 'postgres'

    # try to connect to database
    try:
        conn = db.connect('host=' + database_host +
                          ' port=' + database_port +
                          ' dbname=' + database_name +
                          ' user=' + database_username +
                          ' password=' + database_password)
    except:
        print('Could not connect to remote database')
        raise

    # map user_selected_table and timestamp column
    timestamp_map = {"electricity_egauge_15min": "local_15min",
                     "electricity_egauge_hours": "localhour",
                     "electricity_egauge_minutes": "localminute",
                     "electricity_egauge_seconds": "localminute"}

    # set up a new HDF5 datastore (overwrites existing store)
    store = pd.HDFStore(hdf_filename, 'w', complevel=9, complib='zlib')

    # Create a temporary metadata dir, remove existing building
    # yaml files in module dir (if any)
    original_metadata_dir = join(get_module_directory(),
                                 'dataset_converters',
                                 'dataport',
                                 'metadata')
    tmp_dir = tempfile.mkdtemp()
    metadata_dir = join(tmp_dir, 'metadata')
    shutil.copytree(original_metadata_dir, metadata_dir)
    print("Using temporary dir for metadata:", metadata_dir)

    for f in os.listdir(metadata_dir):
        if re.search('^building', f):
            os.remove(join(metadata_dir, f))

    """
    TODO:
    The section below can be altered or removed,
    since the restructured Dataport
    now has only one electricity_egauge_minutes table.
    """
    # get tables in database schema
    sql_query = ("SELECT table_name" +
                 " FROM information_schema.views" +
                 " WHERE table_schema ='" + database_schema + "'" +
                 " ORDER BY table_name")
    database_tables = pd.read_sql(sql_query, conn)['table_name'].tolist()
    database_tables = [t for t in database_tables if user_selected_table in t]
    # if user has specified buildings
    if periods_to_load:
        buildings_to_load = list(periods_to_load.keys())
    else:
        # get buildings present in all tables
        sql_query = ''
        for table in database_tables:
            sql_query = (sql_query + '(SELECT DISTINCT dataid' +
                         ' FROM "' + database_schema + '".' + table +
                         ') UNION ')
        sql_query = sql_query[:-7]
        sql_query = (sql_query + ' ORDER BY dataid')
        buildings_to_load = pd.read_sql(sql_query, conn)['dataid'].tolist()

    # for each user specified building or all buildings in database
    for building_id in buildings_to_load:
        print("Loading building {:d} @ {}"
              .format(building_id, datetime.datetime.now()))
        sys.stdout.flush()

        # create new list of chunks for concatenating later
        dataframe_list = []

        # for each table of 1 month data
        for database_table in database_tables:
            print("  Loading table {:s}".format(database_table))
            sys.stdout.flush()

            # get buildings present in electricity_egauge_minutes table
            sql_query = ('SELECT DISTINCT dataid' +
                         ' FROM university.metadata' +
                         ' WHERE egauge_min_time IS NOT NULL' +
                         ' ORDER BY dataid')

            buildings_in_table = pd.read_sql(sql_query,
                                             conn)['dataid'].tolist()
            if building_id in buildings_in_table:
                # get first and last timestamps for this
                # house in electricity_egauge_minutes table
                sql_query = ('SELECT MIN(egauge_min_time) AS minlocalminute,' +
                             ' MAX(egauge_max_time) AS maxlocalminute' +
                             ' FROM university.metadata' +
                             ' WHERE dataid=' + str(building_id))

                range = pd.read_sql(sql_query, conn)

                first_timestamp_in_table = range['minlocalminute'][0]
                last_timestamp_in_table = range['maxlocalminute'][0]

                # get requested start and end and localize them
                requested_start = None
                requested_end = None
                database_timezone = 'US/Central'
                if periods_to_load:
                    if periods_to_load[building_id][0]:
                        requested_start = pd.Timestamp(periods_to_load[building_id][0])
                        requested_start = requested_start.tz_localize(database_timezone)
                    if periods_to_load[building_id][1]:
                        requested_end = pd.Timestamp(periods_to_load[building_id][1])
                        requested_end = requested_end.tz_localize(database_timezone)

                # check user start is not after end
                if requested_start > requested_end:
                    print('requested end is before requested start')
                    sys.stdout.flush()
                else:
                    # clip data to smallest range
                    if requested_start:
                        start = max(requested_start, first_timestamp_in_table)
                    else:
                        start = first_timestamp_in_table
                    if requested_end:
                        end = min(requested_end, last_timestamp_in_table)
                    else:
                        end = last_timestamp_in_table

                    # download data in chunks
                    chunk_start = start
                    chunk_size = datetime.timedelta(10)  # 1 day
                    while chunk_start < end:
                        chunk_end = chunk_start + chunk_size
                        if chunk_end > end:
                            chunk_end = end
                        # subtract 1 second so end is exclusive
                        chunk_end = chunk_end - datetime.timedelta(0, 1)

                        # query power data for all channels
                        format = '%Y-%m-%d %H:%M:%S'
                        sql_query = ('SELECT *' +
                                     ' FROM "' + database_schema + '".' + user_selected_table +
                                     ' WHERE dataid=' + str(building_id) +
                                     'and "' + timestamp_map[user_selected_table] + '" between ' +
                                     "'" + chunk_start.strftime(format) + "'" +
                                     " and " +
                                     "'" + chunk_end.strftime(format) +
                                     "' ORDER BY "+timestamp_map[user_selected_table]
                                     )
                        chunk_dataframe = pd.read_sql(sql_query, conn)
                        # nilmtk requires building indices to start at 1
                        nilmtk_building_id = buildings_to_load.index(building_id) + 1
                        # convert to nilmtk-df and save to disk
                        nilmtk_dataframe = _dataport_dataframe_to_hdf(
                            chunk_dataframe, store,
                            nilmtk_building_id,
                            building_id,
                            timestamp_map[user_selected_table],
                            metadata_dir
                        )

                        # print progress
                        print('    ' + str(chunk_start) + ' -> ' +
                              str(chunk_end) + ': ' +
                              str(len(chunk_dataframe.index)) + ' rows')
                        sys.stdout.flush()

                        # append all chunks into list for csv writing
                        # dataframe_list.append(chunk_dataframe)

                        # move on to next chunk
                        chunk_start = chunk_start + chunk_size

        # saves all chunks in list to csv
        # if len(dataframe_list) > 0:
            # dataframe_concat = pd.concat(dataframe_list)
            # dataframe_concat.to_csv(output_directory + str(building_id) + '.csv')

    store.close()
    conn.close()

    # write yaml to hdf5
    # dataset.yaml and meter_devices.yaml are static, building<x>.yaml are dynamic
    convert_yaml_to_hdf5(metadata_dir, hdf_filename)

    # remote the temporary dir when finished
    shutil.rmtree(tmp_dir)

def _dataport_dataframe_to_hdf(dataport_dataframe,
                               store,
                               nilmtk_building_id,
                               dataport_building_id,
                               timestamp_name,
                               metadata_dir):
    local_dataframe = dataport_dataframe.copy()

    # remove timezone information to avoid append errors
    local_dataframe[timestamp_name] = pd.DatetimeIndex([i.replace(tzinfo=None)
                                                       for i in local_dataframe[timestamp_name]])
    # set timestamp as frame index
    local_dataframe = local_dataframe.set_index(timestamp_name)

    # set timezone
    local_dataframe = local_dataframe.tz_localize('US/Central')
    # remove timestamp column from dataframe
    feeds_dataframe = local_dataframe.drop('dataid', axis=1)
    # Column names for dataframe
    column_names = [('power', 'active')]
    # convert from kW to W
    feeds_dataframe = feeds_dataframe.mul(1000)
    # building metadata
    building_metadata = {}
    building_metadata['instance'] = nilmtk_building_id
    building_metadata['original_name'] = int(dataport_building_id)  # use python int
    building_metadata['elec_meters'] = {}
    building_metadata['appliances'] = []

    # initialise dict of instances of each appliance type
    instance_counter = {}

    meter_id = 1
    for column in feeds_dataframe.columns:
        if feeds_dataframe[column].notnull().sum() > 0 and not column in feed_ignore:

            # convert timeseries into dataframe
            feed_dataframe = pd.DataFrame(feeds_dataframe[column])

            # set column names
            feed_dataframe.columns = pd.MultiIndex.from_tuples(column_names)

            # Modify the column labels to reflect the power measurements recorded.
            feed_dataframe.columns.set_names(LEVEL_NAMES, inplace=True)

            key = Key(building=nilmtk_building_id, meter=meter_id)

            # store dataframe
            store.put(str(key), feed_dataframe, format='table', append=True)
            store.flush()

            # elec_meter metadata
            if column == 'use':
                meter_metadata = {'device_model': 'eGauge',
                                  'site_meter': True}
            else:
                meter_metadata = {'device_model': 'eGauge',
                                  'submeter_of': 0}
            building_metadata['elec_meters'][meter_id] = meter_metadata
            # appliance metadata
            if column != 'use':
                # original name and meter id
                appliance_metadata = {'original_name': column,
                                      'meters': [meter_id]}
                # appliance type and room if available
                appliance_metadata.update(feed_mapping[column])
                # appliance instance number
                if instance_counter.get(appliance_metadata['type']) == None:
                    instance_counter[appliance_metadata['type']] = 0
                instance_counter[appliance_metadata['type']] += 1
                appliance_metadata['instance'] = instance_counter[appliance_metadata['type']]
                building_metadata['appliances'].append(appliance_metadata)

            meter_id += 1

    # write building yaml to file
    building = 'building{:d}'.format(nilmtk_building_id)
    yaml_full_filename = join(metadata_dir, building + '.yaml')
    with open(yaml_full_filename, 'w') as outfile:
        outfile.write(yaml.dump(building_metadata))

    return 0


from unittest import TestCase

from nba_data.data.box_scores import GameBoxScore

class TestBoxScore(TestCase):
    def test_instantiation(self):
        test_box_score = GameBoxScore(game_id="bae", player_box_scores=[], team_box_scores=[])
        self.assertIsNotNone(test_box_score)
        self.assertEqual(test_box_score.game_id, "bae")
        self.assertEqual(test_box_score.player_box_scores, [])
        self.assertEqual(test_box_score.team_box_scores, [])


#!/usr/bin/env python
import urllib
from decimal import Decimal
from getpass import getpass

import click
from stellar_base import exceptions
from stellar_base.address import Address
from stellar_base.builder import Builder
from stellar_base.keypair import Keypair

from config import configs
from validate import validate

@click.command()
@click.argument('target_address')
@click.argument('amount')
@click.option('--network', default='TESTNET', type=click.Choice(['TESTNET', 'PUBLIC']))
@click.option('--source_secret', prompt=True, hide_input=True)
def payment(target_address: str, amount: str, network, source_secret):
    config = configs[network]
    src_address = Keypair.from_seed(source_secret).address().decode()
    builder = Builder(secret=source_secret, horizon_uri=config['HORIZON_URL'], network=network)
    builder.append_payment_op(destination=target_address, asset_code='HOT',
            asset_issuer=config['ISSUER_HOT'], amount=amount)
    builder.sign()
    print("###############   TX   #################")
    print('Payment {} HOT from {} to {}'.format(amount, src_address, target_address))
    print('Network: {}'.format(network))
    print('Sequence: {}'.format(builder.sequence))
    print('Hash: {}'.format(builder.hash()))
    print("#########################################")
    click.confirm('Correct?', abort=True)
    print('Submitting...')
    builder.submit()
    print('success')
    return True

if __name__ == '__main__':
    payment()


#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Convience file system related operations."""

import os
import shutil
import sys
import tempfile

import platform
import time

def AtomicWriteFile(data, filename):
  """Write a file atomically.

  NOTE: Not atomic on Windows!
  Args:
    data: String to write to the file.
    filename: Filename to write.
  """
  filename = os.path.abspath(filename)
  handle, temp_file = tempfile.mkstemp(
      prefix='atomic_write', suffix='.tmp',
      dir=os.path.dirname(filename))
  fh = os.fdopen(handle, 'wb')
  fh.write(data)
  fh.close()
  # Window's can't move into place atomically, delete first.
  if sys.platform in ['win32', 'cygwin']:
    try:
      os.remove(filename)
    except OSError:
      pass
  os.rename(temp_file, filename)

def WriteFile(data, filename):
  """Write a file in one step.

  Args:
    data: String to write to the file.
    filename: Filename to write.
  """
  fh = open(filename, 'wb')
  fh.write(data)
  fh.close()

def ReadFile(filename):
  """Read a file in one step.

  Args:
    filename: Filename to read.
  Returns:
    String containing complete file.
  """
  fh = open(filename, 'rb')
  data = fh.read()
  fh.close()
  return data

class ExecutableNotFound(Exception):
  pass

def Which(command, paths=None, require_executable=True):
  """Find the absolute path of a command in the current PATH.

  Args:
    command: Command name to look for.
    paths: Optional paths to search.
  Returns:
    Absolute path of the command (first one found),
    or default to a bare command if nothing is found.
  """
  if paths is None:
    paths = os.environ.get('PATH', '').split(os.pathsep)
  exe_suffixes = ['']
  if sys.platform == 'win32':
    exe_suffixes += ['.exe']
  for p in paths:
    np = os.path.abspath(os.path.join(p, command))
    for suffix in exe_suffixes:
      full_path = np + suffix
      if (os.path.isfile(full_path) and
          (not require_executable or os.access(full_path, os.X_OK))):
        return full_path
  raise ExecutableNotFound('Unable to find: ' + command)

def MakeDirectoryIfAbsent(path):
  """Create a directory if it doesn't already exist.

  Args:
    path: Directory to create.
  """
  if not os.path.isdir(path):
    os.makedirs(path)

def MakeParentDirectoryIfAbsent(path):
  """Creates a directory for the parent if it doesn't already exist.

  Args:
    path: Path of child where parent directory should be created for.
  """
  MakeDirectoryIfAbsent(os.path.dirname(path))

def RemoveDirectoryIfPresent(path):
  """Remove a directory if it exists.

  Args:
    path: Directory to remove.
  """
  # On Windows, attempts to remove read-only files get Error 5. This
  # error handler fixes the permissions and retries the removal.
  def onerror_readonly(func, path, exc_info):
    import stat
    if not os.access(path, os.W_OK):
      os.chmod(path, stat.S_IWUSR)
      func(path)

  if os.path.exists(path):
    shutil.rmtree(path, onerror=onerror_readonly)

def CopyTree(src, dst):
  """Recursively copy the items in the src directory to the dst directory.

  Unlike shutil.copytree, the destination directory and any subdirectories and
  files may exist. Existing directories are left untouched, and existing files
  are removed and copied from the source using shutil.copy2. It is also not
  symlink-aware.

  Args:
    src: Source. Must be an existing directory.
    dst: Destination directory. If it exists, must be a directory. Otherwise it
         will be created, along with parent directories.
  """
  if not os.path.isdir(dst):
    os.makedirs(dst)
  for root, dirs, files in os.walk(src):
    relroot = os.path.relpath(root, src)
    dstroot = os.path.join(dst, relroot)
    for d in dirs:
      dstdir = os.path.join(dstroot, d)
      if not os.path.isdir(dstdir):
        os.mkdir(dstdir)
    for f in files:
      dstfile = os.path.join(dstroot, f)
      if os.path.isfile(dstfile):
        os.remove(dstfile)
      shutil.copy2(os.path.join(root, f), dstfile)

def MoveAndMergeDirTree(src_dir, dest_dir):
  """Moves everything from a source directory to a destination directory.

  This is different from shutil's move implementation in that it only operates
  on directories, and if the destination directory exists, it will move the
  contents into the directory and merge any existing directories.

  Args:
    src_dir: Source directory which files should be moved from.
    dest_dir: Destination directory where files should be moved and merged to.
  """
  if not os.path.isdir(src_dir):
    raise OSError('MoveAndMergeDirTree can only operate on directories.')

  if not os.path.exists(dest_dir):
    # Simply move the directory over if destination doesn't exist.
    MakeParentDirectoryIfAbsent(dest_dir)
    os.rename(src_dir, dest_dir)
  else:
    # Merge each item if destination directory exists.
    for dir_item in os.listdir(src_dir):
      source_item = os.path.join(src_dir, dir_item)
      destination_item = os.path.join(dest_dir, dir_item)
      if os.path.exists(destination_item):
        if os.path.isdir(destination_item) and os.path.isdir(source_item):
          # Merge the sub-directories together if they are both directories.
          MoveAndMergeDirTree(source_item, destination_item)
        elif os.path.isfile(destination_item) and os.path.isfile(source_item):
          # Overwrite the file if they are both files.
          os.unlink(destination_item)
          os.rename(source_item, destination_item)
        else:
          raise OSError('Cannot move directory tree, mismatching types.'
                        ' Source - %s. Destination - %s' %
                        (source_item, destination_item))
      else:
        os.rename(source_item, destination_item)

    # Remove the directory once all the contents have been moved
    os.rmdir(src_dir)

def Retry(op, *args):
  # Windows seems to be prone to having commands that delete files or
  # directories fail.  We currently do not have a complete understanding why,
  # and as a workaround we simply retry the command a few times.
  # It appears that file locks are hanging around longer than they should.  This
  # may be a secondary effect of processes hanging around longer than they
  # should.  This may be because when we kill a browser sel_ldr does not exit
  # immediately, etc.
  # Virus checkers can also accidently prevent files from being deleted, but
  # that shouldn't be a problem on the bots.
  if platform.IsWindows():
    count = 0
    while True:
      try:
        op(*args)
        break
      except Exception:
        sys.stdout.write('FAILED: %s %s\n' % (op.__name__, repr(args)))
        count += 1
        if count < 5:
          sys.stdout.write('RETRY: %s %s\n' % (op.__name__, repr(args)))
          time.sleep(pow(2, count))
        else:
          # Don't mask the exception.
          raise
  else:
    op(*args)

def MoveDirCleanly(src, dst):
  RemoveDir(dst)
  MoveDir(src, dst)

def MoveDir(src, dst):
  Retry(shutil.move, src, dst)

def RemoveDir(path):
  if os.path.exists(path):
    Retry(shutil.rmtree, path)

def RemoveFile(path):
  if os.path.exists(path):
    Retry(os.unlink, path)


__author__ = "Thomas Spycher, Philipp Spinnler"
__copyright__ = "Copyright 2013, Zerodine GmbH (zerodine.com) "
__credits__ = ["Thomas Spycher", "Philipp Spinnler"]
__license__ = "Apache-2.0"
__maintainer__ = "Thomas Spycher"
__email__ = "me@tspycher.com"
__status__ = "Development"

from indexcontroller import IndexController
from lookupcontroller import LookupController
from addcontroller import AddController
from reconcontroller import ReconController


class Solution:
    def myAtoi(self, string: str) -> int:

        string = string.strip()
        ans = 0
        sign = "+"
        index = 0

        if not string:
            return 0

        if string[0] != "+" and string[0] != "-" and string[0].isdigit() is False:
            return 0

        if string[0] == "-":
            sign = "-"
            index += 1
        elif string[0] == "+":
            index += 1

        while index < len(string):

            if string[index].isdigit() is False:
                ans = ans if sign == "+" else - ans

                if ans < -2 ** 31:
                    return -2 ** 31
                elif ans > 2 ** 31 - 1:
                    return 2 ** 31 - 1
                else:
                    return ans

            ans = 10 * ans + int(string[index])
            index += 1

        ans = ans if sign == "+" else -ans

        if ans < -2 ** 31:
            return -2 ** 31
        elif ans > 2 ** 31 - 1:
            return 2 ** 31 - 1
        else:
            return ans


"""Command-line tool to find out where a particular chip or board resides.

The ``spalloc-where-is`` command allows you to query boards by coordinate, by
physical location, by chip or by job. In response to a query, a standard set of
information is displayed as shown in the example below::

    $ spalloc-where-is --job-chip 24 14, 3
                     Machine: my-machine
           Physical Location: Cabinet 2, Frame 4, Board 7
            Board Coordinate: (3, 4, 0)
    Machine Chip Coordinates: (38, 51)
    Coordinates within board: (2, 3)
             Job using board: 24
      Coordinates within job: (14, 3)

In this example we ask, 'where is chip (14, 3) in job 24'? We discover that:

* The chip is the machine named 'my-machine' on the board in cabinet 2, frame
  4, board 7.
* This board's logical board coordinates are (3, 4, 0). These logical
  coordinates may be used to specifically request this board from Spalloc in
  the future.
* If 'my-machine' were booted as a single large machine, the chip we queried
  would be chip (38, 51). This may be useful for cross-referencing with
  diagrams produced by SpiNNer_.
* The chip in question is chip (2, 3) its board. This may be useful when
  reporting faulty chips/replacing boards..
* The job currently running on the board has ID 24. Obviously in this example
  we already knew this but this may be useful when querying by board.
* Finally, we're told that the queried chip has the coordinates (14, 3) in the
  machine allocated to job 24. Again, this information may be more useful when
  querying by board.

.. _SpiNNer: https://github.com/SpiNNakerManchester/SpiNNer

To query by logical board coordinate::

    spalloc-where-is --board MACHINE X Y Z

To query by physical board location::

    spalloc-where-is --physical MACHINE CABINET FRAME BOARD

To query by chip coordinate (as if the machine were booted as one large
machine)::

    spalloc-where-is --chip MACHINE X Y

To query by chip coordinate of chips allocated to a job::

    spalloc-where-is --job-chip JOB_ID X Y
"""
import sys
import argparse

from collections import OrderedDict

from spalloc import config
from spalloc import __version__, ProtocolClient, ProtocolTimeoutError
from spalloc.term import render_definitions

# The acceptable range of server version numbers
VERSION_RANGE_START = (0, 3, 0)
VERSION_RANGE_STOP = (2, 0, 0)

def main(argv=None):
    cfg = config.read_config()

    parser = argparse.ArgumentParser(
        description="Find out the location (physical or logical) "
                    "of a chip or board.")

    parser.add_argument("--version", "-V", action="version",
                        version=__version__)

    control_args = parser.add_mutually_exclusive_group(required=True)
    control_args.add_argument("--board", "-b", "--logical", "-l", nargs=4,
                              metavar=("MACHINE", "X", "Y", "Z"),
                              help="specify the logical board coordinate")
    control_args.add_argument("--physical", "-p", nargs=4,
                              metavar=("MACHINE", "CABINET", "FRAME", "BOARD"),
                              help="specify a board's physical location")
    control_args.add_argument("--chip", "-c", nargs=3,
                              metavar=("MACHINE", "X", "Y"),
                              help="specify a board by chip coordinates (as "
                                   "if the whole machine is being used)")
    control_args.add_argument("--job-chip", "-j", nargs=3,
                              metavar=("JOB_ID", "X", "Y"),
                              help="specify the chip coordinates of a chip "
                                   "within a job's boards")

    server_args = parser.add_argument_group("spalloc server arguments")

    server_args.add_argument("--hostname", "-H", default=cfg["hostname"],
                             help="hostname or IP of the spalloc server "
                                  "(default: %(default)s)")
    server_args.add_argument("--port", "-P", default=cfg["port"],
                             type=int,
                             help="port number of the spalloc server "
                                  "(default: %(default)s)")
    server_args.add_argument("--timeout", default=cfg["timeout"],
                             type=float, metavar="SECONDS",
                             help="seconds to wait for a response "
                                  "from the server (default: %(default)s)")

    args = parser.parse_args(argv)

    # Fail if server not specified
    if args.hostname is None:
        parser.error("--hostname of spalloc server must be specified")

    client = ProtocolClient(args.hostname, args.port)
    try:
        # Connect to server and ensure compatible version
        client.connect()
        version = tuple(
            map(int, client.version(timeout=args.timeout).split(".")))
        if not (VERSION_RANGE_START <= version < VERSION_RANGE_STOP):
            sys.stderr.write("Incompatible server version ({}).\n".format(
                ".".join(map(str, version))))
            return 2

        # Work out what the user asked for
        try:
            show_board_chip = False
            if args.board:
                machine, x, y, z = args.board
                where_is_kwargs = {
                    "machine": machine,
                    "x": int(x),
                    "y": int(y),
                    "z": int(z),
                }
            elif args.physical:
                machine, c, f, b = args.physical
                where_is_kwargs = {
                    "machine": machine,
                    "cabinet": int(c),
                    "frame": int(f),
                    "board": int(b),
                }
            elif args.chip:
                machine, x, y = args.chip
                where_is_kwargs = {
                    "machine": machine,
                    "chip_x": int(x),
                    "chip_y": int(y),
                }
                show_board_chip = True
            elif args.job_chip:
                job_id, x, y = args.job_chip
                where_is_kwargs = {
                    "job_id": int(job_id),
                    "chip_x": int(x),
                    "chip_y": int(y),
                }
                show_board_chip = True
        except ValueError as e:
            parser.error("Error: {}".format(e))

        # Ask the server
        location = client.where_is(**where_is_kwargs)
        if location is None:
            sys.stderr.write("No boards at the specified location.\n")
            return 4
        else:
            out = OrderedDict()
            out["Machine"] = location["machine"]
            out["Physical location"] = "Cabinet {}, Frame {}, Board {}".format(
                *location["physical"])
            out["Board coordinate"] = tuple(location["logical"])
            out["Machine chip coordinates"] = tuple(location["chip"])
            if show_board_chip:
                out["Coordinates within board"] = tuple(location["board_chip"])
            out["Job using board"] = location["job_id"]
            if location["job_id"]:
                out["Coordinates within job"] = tuple(location["job_chip"])

            print(render_definitions(out))
            return 0

    except (IOError, OSError, ProtocolTimeoutError) as e:
        sys.stderr.write("Error communicating with server: {}\n".format(e))
        return 1
    finally:
        client.close()

if __name__ == "__main__":  # pragma: no cover
    sys.exit(main())


# [1081] 不同字符的最小子序列

# https://leetcode-cn.com/problems/smallest-subsequence-of-distinct-characters/description/

# * algorithms
# * Medium (53.88%)
# * Total Accepted:    6.7K
# * Total Submissions: 12.5K
# * Testcase Example:  '"bcabc"'

# 返回字符串 text 中按字典序排列最小的子序列，该子序列包含 text 中所有不同字符一次。

#

# 示例 1：

# 输入："cdadabcc"
# 输出："adbc"

# 示例 2：

# 输入："abcd"
# 输出："abcd"

# 示例 3：

# 输入："ecbacba"
# 输出："eacb"

# 示例 4：

# 输入："leetcode"
# 输出："letcod"

#

# 提示：

# 	1 <= text.length <= 1000
# 	text 由小写英文字母组成

#

# 注意：本题目与 316 https://leetcode-cn.com/problems/remove-duplicate-letters/ 相同

import collections

class Solution(object):
    def smallestSubsequence(self, text):
        seen = set()
        stack = []
        # 记录每个字母还可以删除几次，也可以保存每个字符最右边的位置用于判断
        remain_counter = collections.Counter(text)
        for c in text:
            # 每个字母只能出现一次，之前出现过的，现在没有出现过的意义，这是一个单调递增的栈
            if c not in seen:
                # 栈顶太大了，而且后面还有
                while stack and stack[-1] > c and remain_counter[stack[-1]] > 0:
                    seen.discard(stack.pop())
                stack.append(c)
                seen.add(c)
            remain_counter[c] -= 1
        return "".join(stack)


# coding: utf-8
#
# Copyright 2021 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Unit tests for jobs.transforms.config_validation."""

from __future__ import absolute_import  # pylint: disable=import-only-modules
from __future__ import unicode_literals  # pylint: disable=import-only-modules

from core.domain import platform_parameter_domain as parameter_domain
from core.platform import models
from jobs import job_test_utils
from jobs.transforms import config_validation
from jobs.types import base_validation_errors

import apache_beam as beam

(base_models, config_models) = models.Registry.import_models(
    [models.NAMES.base_model, models.NAMES.config])

class ValidateConfigPropertySnapshotMetadataModelTests(
        job_test_utils.PipelinedTestBase):

    def test_validate_change_domain_implemented(self):
        invalid_commit_cmd_model = (
            config_models.ConfigPropertySnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{
                    'cmd': base_models.VersionedModel.CMD_DELETE_COMMIT}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation.ValidateConfigPropertySnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [])

    def test_config_property_change_object_with_missing_cmd(self):
        invalid_commit_cmd_model = (
            config_models.ConfigPropertySnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{'invalid': 'data'}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation.ValidateConfigPropertySnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                {'invalid': 'data'},
                'Missing cmd key in change dict')
        ])

    def test_config_property_change_object_with_invalid_cmd(self):
        invalid_commit_cmd_model = (
            config_models.ConfigPropertySnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{'cmd': 'invalid'}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation.ValidateConfigPropertySnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                {'cmd': 'invalid'},
                'Command invalid is not allowed')
        ])

    def test_config_property_change_object_with_missing_attribute_in_cmd(self):
        invalid_commit_cmd_model = (
            config_models.ConfigPropertySnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{'cmd': 'change_property_value'}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation.ValidateConfigPropertySnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                {'cmd': 'change_property_value'},
                'The following required attributes are missing: '
                'new_value')
        ])

    def test_config_property_change_object_with_extra_attribute_in_cmd(self):
        commit_dict = {
            'cmd': 'change_property_value',
            'new_value': 'new_value',
            'invalid': 'invalid'
        }
        invalid_commit_cmd_model = (
            config_models.ConfigPropertySnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[commit_dict])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation.ValidateConfigPropertySnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                commit_dict,
                'The following extra attributes are present: invalid')
        ])

class ValidatePlatformParameterSnapshotMetadataModelTests(
        job_test_utils.PipelinedTestBase):

    CMD_EDIT_RULES = parameter_domain.PlatformParameterChange.CMD_EDIT_RULES

    def test_validate_change_domain_implemented(self):
        invalid_commit_cmd_model = (
            config_models.PlatformParameterSnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{
                    'cmd': base_models.VersionedModel.CMD_DELETE_COMMIT}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation
                .ValidatePlatformParameterSnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [])

    def test_param_change_object_with_missing_cmd_raises_exception(self):
        invalid_commit_cmd_model = (
            config_models.PlatformParameterSnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{'invalid': 'data'}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation
                .ValidatePlatformParameterSnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                {'invalid': 'data'},
                'Missing cmd key in change dict')
        ])

    def test_param_change_object_with_invalid_cmd_raises_exception(self):
        invalid_commit_cmd_model = (
            config_models.PlatformParameterSnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{'cmd': 'invalid'}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation
                .ValidatePlatformParameterSnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                {'cmd': 'invalid'},
                'Command invalid is not allowed')
        ])

    def test_param_change_object_missing_attribute_in_cmd_raises_exception(
            self):
        invalid_commit_cmd_model = (
            config_models.PlatformParameterSnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[{'cmd': self.CMD_EDIT_RULES}])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation
                .ValidatePlatformParameterSnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                {'cmd': self.CMD_EDIT_RULES},
                'The following required attributes are missing: new_rules')
        ])

    def test_param_change_object_with_extra_attribute_in_cmd_raises_exception(
            self):
        commit_dict = {
            'cmd': self.CMD_EDIT_RULES,
            'new_rules': [],
            'invalid': 'invalid'
        }
        invalid_commit_cmd_model = (
            config_models.PlatformParameterSnapshotMetadataModel(
                id='model_id-1',
                created_on=self.YEAR_AGO,
                last_updated=self.NOW,
                committer_id='committer_id',
                commit_type='create',
                commit_cmds=[commit_dict])
        )

        output = (
            self.pipeline
            | beam.Create([invalid_commit_cmd_model])
            | beam.ParDo(
                config_validation
                .ValidatePlatformParameterSnapshotMetadataModel())
        )

        self.assert_pcoll_equal(output, [
            base_validation_errors.CommitCmdsValidateError(
                invalid_commit_cmd_model,
                commit_dict,
                'The following extra attributes are present: invalid')
        ])


#!/usr/bin/python
# Copyright (c) 2017, 2020 Oracle and/or its affiliates.
# This software is made available to you under the terms of the GPL 3.0 license or the Apache 2.0 license.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Apache License v2.0
# See LICENSE.TXT for details.
# GENERATED FILE - DO NOT EDIT - MANUAL CHANGES WILL BE OVERWRITTEN

from __future__ import absolute_import, division, print_function

__metaclass__ = type

ANSIBLE_METADATA = {
    "metadata_version": "1.1",
    "status": ["preview"],
    "supported_by": "community",
}

DOCUMENTATION = """
---
module: oci_database_autonomous_database_facts
short_description: Fetches details about one or multiple AutonomousDatabase resources in Oracle Cloud Infrastructure
description:
    - Fetches details about one or multiple AutonomousDatabase resources in Oracle Cloud Infrastructure
    - Gets a list of Autonomous Databases based on the query parameters specified.
    - If I(autonomous_database_id) is specified, the details of a single AutonomousDatabase will be returned.
version_added: "2.9"
author: Oracle (@oracle)
options:
    autonomous_database_id:
        description:
            - The database L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm).
            - Required to get a specific autonomous_database.
        type: str
        aliases: ["id"]
    compartment_id:
        description:
            - The compartment L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm).
            - Required to list multiple autonomous_databases.
        type: str
    autonomous_container_database_id:
        description:
            - The Autonomous Container Database L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm).
        type: str
    sort_by:
        description:
            - The field to sort by.  You can provide one sort order (`sortOrder`).  Default order for TIMECREATED is descending.  Default order for DISPLAYNAME
              is ascending. The DISPLAYNAME sort order is case sensitive.
            - "**Note:** If you do not include the availability domain filter, the resources are grouped by availability domain, then sorted."
        type: str
        choices:
            - "TIMECREATED"
            - "DISPLAYNAME"
    sort_order:
        description:
            - The sort order to use, either ascending (`ASC`) or descending (`DESC`).
        type: str
        choices:
            - "ASC"
            - "DESC"
    infrastructure_type:
        description:
            - A filter to return only resources that match the given Infrastructure Type.
        type: str
        choices:
            - "CLOUD"
            - "CLOUD_AT_CUSTOMER"
    lifecycle_state:
        description:
            - A filter to return only resources that match the given lifecycle state exactly.
        type: str
        choices:
            - "PROVISIONING"
            - "AVAILABLE"
            - "STOPPING"
            - "STOPPED"
            - "STARTING"
            - "TERMINATING"
            - "TERMINATED"
            - "UNAVAILABLE"
            - "RESTORE_IN_PROGRESS"
            - "RESTORE_FAILED"
            - "BACKUP_IN_PROGRESS"
            - "SCALE_IN_PROGRESS"
            - "AVAILABLE_NEEDS_ATTENTION"
            - "UPDATING"
            - "MAINTENANCE_IN_PROGRESS"
            - "RESTARTING"
            - "RECREATING"
            - "ROLE_CHANGE_IN_PROGRESS"
            - "UPGRADING"
    db_workload:
        description:
            - A filter to return only autonomous database resources that match the specified workload type.
        type: str
        choices:
            - "OLTP"
            - "DW"
            - "AJD"
    db_version:
        description:
            - A filter to return only autonomous database resources that match the specified dbVersion.
        type: str
    is_free_tier:
        description:
            - Filter on the value of the resource's 'isFreeTier' property. A value of `true` returns only Always Free resources.
              A value of `false` excludes Always Free resources from the returned results. Omitting this parameter returns both Always Free and paid resources.
        type: bool
    display_name:
        description:
            - A filter to return only resources that match the entire display name given. The match is not case sensitive.
        type: str
        aliases: ["name"]
    is_refreshable_clone:
        description:
            - Filter on the value of the resource's 'isRefreshableClone' property. A value of `true` returns only refreshable clones.
              A value of `false` excludes refreshable clones from the returned results. Omitting this parameter returns both refreshable clones and databases
              that are not refreshable clones.
        type: bool
    is_data_guard_enabled:
        description:
            - A filter to return only resources that have Data Guard enabled.
        type: bool
extends_documentation_fragment: [ oracle.oci.oracle ]
"""

EXAMPLES = """
- name: List autonomous_databases
  oci_database_autonomous_database_facts:
    compartment_id: ocid1.compartment.oc1..xxxxxxEXAMPLExxxxxx

- name: Get a specific autonomous_database
  oci_database_autonomous_database_facts:
    autonomous_database_id: ocid1.autonomousdatabase.oc1..xxxxxxEXAMPLExxxxxx

"""

RETURN = """
autonomous_databases:
    description:
        - List of AutonomousDatabase resources
    returned: on success
    type: complex
    contains:
        id:
            description:
                - The L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the Autonomous Database.
            returned: on success
            type: string
            sample: ocid1.resource.oc1..xxxxxxEXAMPLExxxxxx
        compartment_id:
            description:
                - The L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the compartment.
            returned: on success
            type: string
            sample: ocid1.compartment.oc1..xxxxxxEXAMPLExxxxxx
        lifecycle_state:
            description:
                - The current state of the Autonomous Database.
            returned: on success
            type: string
            sample: PROVISIONING
        lifecycle_details:
            description:
                - Information about the current lifecycle state.
            returned: on success
            type: string
            sample: lifecycle_details_example
        db_name:
            description:
                - The database name.
            returned: on success
            type: string
            sample: db_name_example
        is_free_tier:
            description:
                - Indicates if this is an Always Free resource. The default value is false. Note that Always Free Autonomous Databases have 1 CPU and 20GB of
                  memory. For Always Free databases, memory and CPU cannot be scaled.
            returned: on success
            type: bool
            sample: true
        system_tags:
            description:
                - System tags for this resource. Each key is predefined and scoped to a namespace.
                  For more information, see L(Resource Tags,https://docs.cloud.oracle.com/Content/General/Concepts/resourcetags.htm).
            returned: on success
            type: dict
            sample: {}
        time_reclamation_of_free_autonomous_database:
            description:
                - The date and time the Always Free database will be stopped because of inactivity. If this time is reached without any database activity, the
                  database will automatically be put into the STOPPED state.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        time_deletion_of_free_autonomous_database:
            description:
                - The date and time the Always Free database will be automatically deleted because of inactivity. If the database is in the STOPPED state and
                  without activity until this time, it will be deleted.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        backup_config:
            description:
                - ""
            returned: on success
            type: complex
            contains:
                manual_backup_bucket_name:
                    description:
                        - Name of L(Object Storage,https://docs.cloud.oracle.com/Content/Object/Concepts/objectstorageoverview.htm) bucket to use for storing
                          manual backups.
                    returned: on success
                    type: string
                    sample: manual_backup_bucket_name_example
                manual_backup_type:
                    description:
                        - The manual backup destination type.
                    returned: on success
                    type: string
                    sample: NONE
        cpu_core_count:
            description:
                - The number of OCPU cores to be made available to the database.
            returned: on success
            type: int
            sample: 56
        data_storage_size_in_tbs:
            description:
                - The quantity of data in the database, in terabytes.
            returned: on success
            type: int
            sample: 56
        infrastructure_type:
            description:
                - The infrastructure type this resource belongs to.
            returned: on success
            type: string
            sample: CLOUD
        is_dedicated:
            description:
                - True if the database uses L(dedicated Exadata infrastructure,https://docs.cloud.oracle.com/Content/Database/Concepts/adbddoverview.htm).
            returned: on success
            type: bool
            sample: true
        autonomous_container_database_id:
            description:
                - The Autonomous Container Database L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm).
            returned: on success
            type: string
            sample: ocid1.autonomouscontainerdatabase.oc1..xxxxxxEXAMPLExxxxxx
        time_created:
            description:
                - The date and time the Autonomous Database was created.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        display_name:
            description:
                - The user-friendly name for the Autonomous Database. The name does not have to be unique.
            returned: on success
            type: string
            sample: display_name_example
        service_console_url:
            description:
                - The URL of the Service Console for the Autonomous Database.
            returned: on success
            type: string
            sample: service_console_url_example
        connection_strings:
            description:
                - The connection string used to connect to the Autonomous Database. The username for the Service Console is ADMIN. Use the password you entered
                  when creating the Autonomous Database for the password value.
            returned: on success
            type: complex
            contains:
                high:
                    description:
                        - The High database service provides the highest level of resources to each SQL statement resulting in the highest performance, but
                          supports the fewest number of concurrent SQL statements.
                    returned: on success
                    type: string
                    sample: high_example
                medium:
                    description:
                        - The Medium database service provides a lower level of resources to each SQL statement potentially resulting a lower level of
                          performance, but supports more concurrent SQL statements.
                    returned: on success
                    type: string
                    sample: medium_example
                low:
                    description:
                        - The Low database service provides the least level of resources to each SQL statement, but supports the most number of concurrent SQL
                          statements.
                    returned: on success
                    type: string
                    sample: low_example
                dedicated:
                    description:
                        - The database service provides the least level of resources to each SQL statement, but supports the most number of concurrent SQL
                          statements.
                    returned: on success
                    type: string
                    sample: dedicated_example
                all_connection_strings:
                    description:
                        - Returns all connection strings that can be used to connect to the Autonomous Database.
                          For more information, please see L(Predefined Database Service Names for Autonomous Transaction
                          Processing,https://docs.oracle.com/en/cloud/paas/atp-cloud/atpug/connect-predefined.html#GUID-9747539B-FD46-44F1-8FF8-F5AC650F15BE)
                    returned: on success
                    type: dict
                    sample: {}
        connection_urls:
            description:
                - ""
            returned: on success
            type: complex
            contains:
                sql_dev_web_url:
                    description:
                        - Oracle SQL Developer Web URL.
                    returned: on success
                    type: string
                    sample: sql_dev_web_url_example
                apex_url:
                    description:
                        - Oracle Application Express (APEX) URL.
                    returned: on success
                    type: string
                    sample: apex_url_example
                machine_learning_user_management_url:
                    description:
                        - Oracle Machine Learning user management URL.
                    returned: on success
                    type: string
                    sample: machine_learning_user_management_url_example
        license_model:
            description:
                - The Oracle license model that applies to the Oracle Autonomous Database. Bring your own license (BYOL) allows you to apply your current on-
                  premises Oracle software licenses to equivalent, highly automated Oracle PaaS and IaaS services in the cloud.
                  License Included allows you to subscribe to new Oracle Database software licenses and the Database service.
                  Note that when provisioning an Autonomous Database on L(dedicated Exadata
                  infrastructure,https://docs.cloud.oracle.com/Content/Database/Concepts/adbddoverview.htm), this attribute must be null because the attribute
                  is already set at the
                  Autonomous Exadata Infrastructure level. When using L(shared Exadata
                  infrastructure,https://docs.cloud.oracle.com/Content/Database/Concepts/adboverview.htm#AEI), if a value is not specified, the system will
                  supply the value of `BRING_YOUR_OWN_LICENSE`.
            returned: on success
            type: string
            sample: LICENSE_INCLUDED
        used_data_storage_size_in_tbs:
            description:
                - The amount of storage that has been used, in terabytes.
            returned: on success
            type: int
            sample: 56
        freeform_tags:
            description:
                - Free-form tags for this resource. Each tag is a simple key-value pair with no predefined name, type, or namespace.
                  For more information, see L(Resource Tags,https://docs.cloud.oracle.com/Content/General/Concepts/resourcetags.htm).
                - "Example: `{\\"Department\\": \\"Finance\\"}`"
            returned: on success
            type: dict
            sample: {'Department': 'Finance'}
        defined_tags:
            description:
                - Defined tags for this resource. Each key is predefined and scoped to a namespace.
                  For more information, see L(Resource Tags,https://docs.cloud.oracle.com/Content/General/Concepts/resourcetags.htm).
            returned: on success
            type: dict
            sample: {'Operations': {'CostCenter': 'US'}}
        subnet_id:
            description:
                - The L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the subnet the resource is associated with.
                - "**Subnet Restrictions:**
                  - For bare metal DB systems and for single node virtual machine DB systems, do not use a subnet that overlaps with 192.168.16.16/28.
                  - For Exadata and virtual machine 2-node RAC systems, do not use a subnet that overlaps with 192.168.128.0/20.
                  - For Autonomous Database, setting this will disable public secure access to the database."
                - These subnets are used by the Oracle Clusterware private interconnect on the database instance.
                  Specifying an overlapping subnet will cause the private interconnect to malfunction.
                  This restriction applies to both the client subnet and the backup subnet.
            returned: on success
            type: string
            sample: ocid1.subnet.oc1..xxxxxxEXAMPLExxxxxx
        nsg_ids:
            description:
                - "A list of the L(OCIDs,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the network security groups (NSGs) that this
                  resource belongs to. Setting this to an empty array after the list is created removes the resource from all NSGs. For more information about
                  NSGs, see L(Security Rules,https://docs.cloud.oracle.com/Content/Network/Concepts/securityrules.htm).
                  **NsgIds restrictions:**
                  - Autonomous Databases with private access require at least 1 Network Security Group (NSG). The nsgIds array cannot be empty."
            returned: on success
            type: list
            sample: []
        private_endpoint:
            description:
                - The private endpoint for the resource.
            returned: on success
            type: string
            sample: private_endpoint_example
        private_endpoint_label:
            description:
                - The private endpoint label for the resource. Setting this to an empty string, after the private endpoint database gets created, will change
                  the same private endpoint database to the public endpoint database.
            returned: on success
            type: string
            sample: private_endpoint_label_example
        private_endpoint_ip:
            description:
                - The private endpoint Ip address for the resource.
            returned: on success
            type: string
            sample: private_endpoint_ip_example
        db_version:
            description:
                - A valid Oracle Database version for Autonomous Database.
            returned: on success
            type: string
            sample: db_version_example
        is_preview:
            description:
                - Indicates if the Autonomous Database version is a preview version.
            returned: on success
            type: bool
            sample: true
        db_workload:
            description:
                - "The Autonomous Database workload type. The following values are valid:"
                - "- OLTP - indicates an Autonomous Transaction Processing database
                  - DW - indicates an Autonomous Data Warehouse database
                  - AJD - indicates an Autonomous JSON Database"
            returned: on success
            type: string
            sample: OLTP
        is_access_control_enabled:
            description:
                - Indicates if the database-level access control is enabled.
                  If disabled, database access is defined by the network security rules.
                  If enabled, database access is restricted to the IP addresses defined by the rules specified with the `whitelistedIps` property. While
                  specifying `whitelistedIps` rules is optional,
                   if database-level access control is enabled and no rules are specified, the database will become inaccessible. The rules can be added later
                   using the `UpdateAutonomousDatabase` API operation or edit option in console.
                  When creating a database clone, the desired access control setting should be specified. By default, database-level access control will be
                  disabled for the clone.
                - This property is applicable only to Autonomous Databases on the Exadata Cloud@Customer platform.
            returned: on success
            type: bool
            sample: true
        whitelisted_ips:
            description:
                - The client IP access control list (ACL). This feature is available for autonomous databases on L(shared Exadata
                  infrastructure,https://docs.cloud.oracle.com/Content/Database/Concepts/adboverview.htm#AEI) and on Exadata Cloud@Customer.
                  Only clients connecting from an IP address included in the ACL may access the Autonomous Database instance.
                - "For shared Exadata infrastructure, this is an array of CIDR (Classless Inter-Domain Routing) notations for a subnet or VCN OCID.
                  Use a semicolon (;) as a deliminator between the VCN-specific subnets or IPs.
                  Example: `[\\"1.1.1.1\\",\\"1.1.1.0/24\\",\\"ocid1.vcn.oc1.sea.<unique_id>\\",\\"ocid1.vcn.oc1.sea.<unique_id1>;1.1.1.1\\",\\"ocid1.vcn.oc1.se
                  a.<unique_id2>;1.1.0.0/16\\"]`
                  For Exadata Cloud@Customer, this is an array of IP addresses or CIDR (Classless Inter-Domain Routing) notations.
                  Example: `[\\"1.1.1.1\\",\\"1.1.1.0/24\\",\\"1.1.2.25\\"]`"
                - For an update operation, if you want to delete all the IPs in the ACL, use an array with a single empty string entry.
            returned: on success
            type: list
            sample: []
        is_auto_scaling_enabled:
            description:
                - Indicates if auto scaling is enabled for the Autonomous Database CPU core count.
            returned: on success
            type: bool
            sample: true
        data_safe_status:
            description:
                - Status of the Data Safe registration for this Autonomous Database.
            returned: on success
            type: string
            sample: REGISTERING
        operations_insights_status:
            description:
                - Status of Operations Insights for this Autonomous Database.
            returned: on success
            type: string
            sample: ENABLING
        time_maintenance_begin:
            description:
                - The date and time when maintenance will begin.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        time_maintenance_end:
            description:
                - The date and time when maintenance will end.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        is_refreshable_clone:
            description:
                - Indicates whether the Autonomous Database is a refreshable clone.
            returned: on success
            type: bool
            sample: true
        time_of_last_refresh:
            description:
                - The date and time when last refresh happened.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        time_of_last_refresh_point:
            description:
                - The refresh point timestamp (UTC). The refresh point is the time to which the database was most recently refreshed. Data created after the
                  refresh point is not included in the refresh.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        time_of_next_refresh:
            description:
                - The date and time of next refresh.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        open_mode:
            description:
                - The `DATABASE OPEN` mode. You can open the database in `READ_ONLY` or `READ_WRITE` mode.
            returned: on success
            type: string
            sample: READ_ONLY
        refreshable_status:
            description:
                - The refresh status of the clone. REFRESHING indicates that the clone is currently being refreshed with data from the source Autonomous
                  Database.
            returned: on success
            type: string
            sample: REFRESHING
        refreshable_mode:
            description:
                - The refresh mode of the clone. AUTOMATIC indicates that the clone is automatically being refreshed with data from the source Autonomous
                  Database.
            returned: on success
            type: string
            sample: AUTOMATIC
        source_id:
            description:
                - The L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the source Autonomous Database that was cloned to create
                  the current Autonomous Database.
            returned: on success
            type: string
            sample: ocid1.source.oc1..xxxxxxEXAMPLExxxxxx
        permission_level:
            description:
                - The Autonomous Database permission level. Restricted mode allows access only to admin users.
            returned: on success
            type: string
            sample: RESTRICTED
        time_of_last_switchover:
            description:
                - The timestamp of the last switchover operation for the Autonomous Database.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        time_of_last_failover:
            description:
                - The timestamp of the last failover operation.
            returned: on success
            type: string
            sample: 2013-10-20T19:20:30+01:00
        is_data_guard_enabled:
            description:
                - Indicates whether the Autonomous Database has Data Guard enabled.
            returned: on success
            type: bool
            sample: true
        failed_data_recovery_in_seconds:
            description:
                - Indicates the number of seconds of data loss for a Data Guard failover.
            returned: on success
            type: int
            sample: 56
        standby_db:
            description:
                - ""
            returned: on success
            type: complex
            contains:
                lag_time_in_seconds:
                    description:
                        - The amount of time, in seconds, that the data of the standby database lags the data of the primary database. Can be used to determine
                          the potential data loss in the event of a failover.
                    returned: on success
                    type: int
                    sample: 56
                lifecycle_state:
                    description:
                        - The current state of the Autonomous Database.
                    returned: on success
                    type: string
                    sample: PROVISIONING
                lifecycle_details:
                    description:
                        - Additional information about the current lifecycle state.
                    returned: on success
                    type: string
                    sample: lifecycle_details_example
        available_upgrade_versions:
            description:
                - List of Oracle Database versions available for a database upgrade. If there are no version upgrades available, this list is empty.
            returned: on success
            type: list
            sample: []
        key_store_id:
            description:
                - The L(OCID,https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the key store.
            returned: on success
            type: string
            sample: ocid1.keystore.oc1..xxxxxxEXAMPLExxxxxx
        key_store_wallet_name:
            description:
                - The wallet name for Oracle Key Vault.
            returned: on success
            type: string
            sample: key_store_wallet_name_example
    sample: [{
        "id": "ocid1.resource.oc1..xxxxxxEXAMPLExxxxxx",
        "compartment_id": "ocid1.compartment.oc1..xxxxxxEXAMPLExxxxxx",
        "lifecycle_state": "PROVISIONING",
        "lifecycle_details": "lifecycle_details_example",
        "db_name": "db_name_example",
        "is_free_tier": true,
        "system_tags": {},
        "time_reclamation_of_free_autonomous_database": "2013-10-20T19:20:30+01:00",
        "time_deletion_of_free_autonomous_database": "2013-10-20T19:20:30+01:00",
        "backup_config": {
            "manual_backup_bucket_name": "manual_backup_bucket_name_example",
            "manual_backup_type": "NONE"
        },
        "cpu_core_count": 56,
        "data_storage_size_in_tbs": 56,
        "infrastructure_type": "CLOUD",
        "is_dedicated": true,
        "autonomous_container_database_id": "ocid1.autonomouscontainerdatabase.oc1..xxxxxxEXAMPLExxxxxx",
        "time_created": "2013-10-20T19:20:30+01:00",
        "display_name": "display_name_example",
        "service_console_url": "service_console_url_example",
        "connection_strings": {
            "high": "high_example",
            "medium": "medium_example",
            "low": "low_example",
            "dedicated": "dedicated_example",
            "all_connection_strings": {}
        },
        "connection_urls": {
            "sql_dev_web_url": "sql_dev_web_url_example",
            "apex_url": "apex_url_example",
            "machine_learning_user_management_url": "machine_learning_user_management_url_example"
        },
        "license_model": "LICENSE_INCLUDED",
        "used_data_storage_size_in_tbs": 56,
        "freeform_tags": {'Department': 'Finance'},
        "defined_tags": {'Operations': {'CostCenter': 'US'}},
        "subnet_id": "ocid1.subnet.oc1..xxxxxxEXAMPLExxxxxx",
        "nsg_ids": [],
        "private_endpoint": "private_endpoint_example",
        "private_endpoint_label": "private_endpoint_label_example",
        "private_endpoint_ip": "private_endpoint_ip_example",
        "db_version": "db_version_example",
        "is_preview": true,
        "db_workload": "OLTP",
        "is_access_control_enabled": true,
        "whitelisted_ips": [],
        "is_auto_scaling_enabled": true,
        "data_safe_status": "REGISTERING",
        "operations_insights_status": "ENABLING",
        "time_maintenance_begin": "2013-10-20T19:20:30+01:00",
        "time_maintenance_end": "2013-10-20T19:20:30+01:00",
        "is_refreshable_clone": true,
        "time_of_last_refresh": "2013-10-20T19:20:30+01:00",
        "time_of_last_refresh_point": "2013-10-20T19:20:30+01:00",
        "time_of_next_refresh": "2013-10-20T19:20:30+01:00",
        "open_mode": "READ_ONLY",
        "refreshable_status": "REFRESHING",
        "refreshable_mode": "AUTOMATIC",
        "source_id": "ocid1.source.oc1..xxxxxxEXAMPLExxxxxx",
        "permission_level": "RESTRICTED",
        "time_of_last_switchover": "2013-10-20T19:20:30+01:00",
        "time_of_last_failover": "2013-10-20T19:20:30+01:00",
        "is_data_guard_enabled": true,
        "failed_data_recovery_in_seconds": 56,
        "standby_db": {
            "lag_time_in_seconds": 56,
            "lifecycle_state": "PROVISIONING",
            "lifecycle_details": "lifecycle_details_example"
        },
        "available_upgrade_versions": [],
        "key_store_id": "ocid1.keystore.oc1..xxxxxxEXAMPLExxxxxx",
        "key_store_wallet_name": "key_store_wallet_name_example"
    }]
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.oracle.oci.plugins.module_utils import oci_common_utils
from ansible_collections.oracle.oci.plugins.module_utils.oci_resource_utils import (
    OCIResourceFactsHelperBase,
    get_custom_class,
)

try:
    from oci.database import DatabaseClient

    HAS_OCI_PY_SDK = True
except ImportError:
    HAS_OCI_PY_SDK = False

class AutonomousDatabaseFactsHelperGen(OCIResourceFactsHelperBase):
    """Supported operations: get, list"""

    def get_required_params_for_get(self):
        return [
            "autonomous_database_id",
        ]

    def get_required_params_for_list(self):
        return [
            "compartment_id",
        ]

    def get_resource(self):
        return oci_common_utils.call_with_backoff(
            self.client.get_autonomous_database,
            autonomous_database_id=self.module.params.get("autonomous_database_id"),
        )

    def list_resources(self):
        optional_list_method_params = [
            "autonomous_container_database_id",
            "sort_by",
            "sort_order",
            "infrastructure_type",
            "lifecycle_state",
            "db_workload",
            "db_version",
            "is_free_tier",
            "display_name",
            "is_refreshable_clone",
            "is_data_guard_enabled",
        ]
        optional_kwargs = dict(
            (param, self.module.params[param])
            for param in optional_list_method_params
            if self.module.params.get(param) is not None
        )
        return oci_common_utils.list_all_resources(
            self.client.list_autonomous_databases,
            compartment_id=self.module.params.get("compartment_id"),
            **optional_kwargs
        )

AutonomousDatabaseFactsHelperCustom = get_custom_class(
    "AutonomousDatabaseFactsHelperCustom"
)

class ResourceFactsHelper(
    AutonomousDatabaseFactsHelperCustom, AutonomousDatabaseFactsHelperGen
):
    pass

def main():
    module_args = oci_common_utils.get_common_arg_spec()
    module_args.update(
        dict(
            autonomous_database_id=dict(aliases=["id"], type="str"),
            compartment_id=dict(type="str"),
            autonomous_container_database_id=dict(type="str"),
            sort_by=dict(type="str", choices=["TIMECREATED", "DISPLAYNAME"]),
            sort_order=dict(type="str", choices=["ASC", "DESC"]),
            infrastructure_type=dict(
                type="str", choices=["CLOUD", "CLOUD_AT_CUSTOMER"]
            ),
            lifecycle_state=dict(
                type="str",
                choices=[
                    "PROVISIONING",
                    "AVAILABLE",
                    "STOPPING",
                    "STOPPED",
                    "STARTING",
                    "TERMINATING",
                    "TERMINATED",
                    "UNAVAILABLE",
                    "RESTORE_IN_PROGRESS",
                    "RESTORE_FAILED",
                    "BACKUP_IN_PROGRESS",
                    "SCALE_IN_PROGRESS",
                    "AVAILABLE_NEEDS_ATTENTION",
                    "UPDATING",
                    "MAINTENANCE_IN_PROGRESS",
                    "RESTARTING",
                    "RECREATING",
                    "ROLE_CHANGE_IN_PROGRESS",
                    "UPGRADING",
                ],
            ),
            db_workload=dict(type="str", choices=["OLTP", "DW", "AJD"]),
            db_version=dict(type="str"),
            is_free_tier=dict(type="bool"),
            display_name=dict(aliases=["name"], type="str"),
            is_refreshable_clone=dict(type="bool"),
            is_data_guard_enabled=dict(type="bool"),
        )
    )

    module = AnsibleModule(argument_spec=module_args)

    if not HAS_OCI_PY_SDK:
        module.fail_json(msg="oci python sdk required for this module.")

    resource_facts_helper = ResourceFactsHelper(
        module=module,
        resource_type="autonomous_database",
        service_client_class=DatabaseClient,
        namespace="database",
    )

    result = []

    if resource_facts_helper.is_get():
        result = [resource_facts_helper.get()]
    elif resource_facts_helper.is_list():
        result = resource_facts_helper.list()
    else:
        resource_facts_helper.fail()

    module.exit_json(autonomous_databases=result)

if __name__ == "__main__":
    main()