# frozen_string_literal: true

# Copyright 2018 Google LLC
#
# 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
#
#     https://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.


require "google/cloud/bigtable/instance/job"
require "google/cloud/bigtable/instance/list"
require "google/cloud/bigtable/instance/cluster_map"
require "google/cloud/bigtable/app_profile"
require "google/cloud/bigtable/policy"
require "google/cloud/bigtable/routing_policy"

module Google
  module Cloud
    module Bigtable
      ##
      # # Instance
      #
      # Represents a Bigtable instance. Instances are dedicated Bigtable
      # storage resources that contain Bigtable tables.
      #
      # See {Google::Cloud::Bigtable::Project#instances},
      # {Google::Cloud::Bigtable::Project#instance}, and
      # {Google::Cloud::Bigtable::Project#create_instance}.
      #
      # @example
      #   require "google/cloud/bigtable"
      #
      #   bigtable = Google::Cloud::Bigtable.new
      #
      #   job = bigtable.create_instance(
      #     "my-instance",
      #     display_name: "Instance for user data",
      #     type: :DEVELOPMENT,
      #     labels: { "env" => "dev" }
      #   ) do |clusters|
      #     clusters.add "test-cluster", "us-east1-b" # nodes not allowed
      #   end
      #
      #   job.done? #=> false
      #
      #   # To block until the operation completes.
      #   job.wait_until_done!
      #   job.done? #=> true
      #
      #   if job.error?
      #     status = job.error
      #   else
      #     instance = job.instance
      #   end
      #
      class Instance
        # @private
        # The gRPC Service object.
        attr_accessor :service

        # @private
        #
        # Creates a new Instance instance.
        def initialize grpc, service
          @grpc = grpc
          @service = service
        end

        ##
        # The unique identifier for the project to which the instance belongs.
        #
        # @return [String]
        #
        def project_id
          @grpc.name.split("/")[1]
        end

        ##
        # The unique identifier for the instance.
        #
        # @return [String]
        #
        def instance_id
          @grpc.name.split("/")[3]
        end

        ##
        # The descriptive name for the instance as it appears in UIs. Must be
        # unique per project and between 4 and 30 characters long.
        #
        # @return [String]
        #
        def display_name
          @grpc.display_name
        end

        ##
        # Updates the descriptive name for the instance as it appears in UIs.
        # Can be changed at any time, but should be kept globally unique
        # to avoid confusion.
        #
        # @param value [String] The descriptive name for the instance.
        #
        def display_name= value
          @grpc.display_name = value
        end

        ##
        # The full path for the instance resource. Values are of the form
        # `projects/<project_id>/instances/<instance_id>`.
        #
        # @return [String]
        #
        def path
          @grpc.name
        end

        ##
        # The current instance state. Possible values are `:CREATING`,
        # `:READY`, `:STATE_NOT_KNOWN`.
        #
        # @return [Symbol]
        #
        def state
          @grpc.state
        end

        ##
        # The instance has been successfully created and can serve requests
        # to its tables.
        #
        # @return [Boolean]
        #
        def ready?
          state == :READY
        end

        ##
        # The instance is currently being created and may be destroyed if the
        # creation process encounters an error.
        #
        # @return [Boolean]
        #
        def creating?
          state == :CREATING
        end

        ##
        # Instance type. Possible values include `:DEVELOPMENT` and `:PRODUCTION`.
        #
        # @return [Symbol]
        #
        def type
          @grpc.type
        end

        ##
        # The instance is meant for development and testing purposes only; it has
        # no performance or uptime guarantees and is not covered by SLA.
        # After a development instance is created, it can be upgraded by
        # updating the instance to type `:PRODUCTION`. An instance created
        # as a production instance cannot be changed to a development instance.
        # When creating a development instance, `nodes` on the cluster must
        # not be set. (See {#create_cluster}.)
        #
        # @return [Boolean]
        #
        def development?
          type == :DEVELOPMENT
        end

        ##
        # An instance meant for production use. Requires that `nodes` must be set
        # on the cluster. (See {#create_cluster}.)
        #
        # @return [Boolean]
        #
        def production?
          type == :PRODUCTION
        end

        ##
        # Sets the instance type.
        #
        # Valid values are `:DEVELOPMENT` and `:PRODUCTION`.
        # After a development instance is created, it can be upgraded
        # by updating the instance to type `:PRODUCTION`.
        # An instance created as a production instance cannot be changed to a
        # development instance.
        #
        # @param instance_type [Symbol]
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   instance.development? # true
        #   instance.type = :PRODUCTION
        #   instance.development? # false
        #   instance.production? # true
        #
        def type= instance_type
          @grpc.type = instance_type
        end

        ##
        # Gets the Cloud Labels for the instance.
        #
        # Cloud Labels are a flexible and lightweight mechanism for organizing
        # cloud resources into groups that reflect a customer's organizational
        # needs and deployment strategies. Cloud Labels can be used to filter
        # collections of resources, to control how resource
        # metrics are aggregated, and as arguments to policy
        # management rules (e.g., route, firewall, load balancing, etc.).
        #
        # * Label keys must be between 1 and 63 characters long and must conform
        #   to the following regular expression: `[a-z]([-a-z0-9]*[a-z0-9])?`.
        # * Label values must be between 0 and 63 characters long and must
        #   conform to the regular expression `([a-z]([-a-z0-9]*[a-z0-9])?)?`.
        # * No more than 64 labels can be associated with a given resource.
        #
        # @return [Hash{String=>String}] The label keys and values in a hash.
        #
        def labels
          @grpc.labels
        end

        ##
        # Sets the Cloud Labels for the instance.
        #
        # @param labels [Hash{String=>String}] The Cloud Labels.
        #
        def labels= labels
          labels ||= {}
          @grpc.labels = Google::Protobuf::Map.new(
            :string, :string,
            labels.to_h { |k, v| [String(k), String(v)] }
          )
        end

        ##
        # Updates the instance.
        #
        # Updatable attributes are:
        #   * `display_name` - The descriptive name for the instance.
        #   * `type` -  `:DEVELOPMENT` type instance can be upgraded to `:PRODUCTION` instance.
        #     An instance created as a production instance cannot be changed to a development instance.
        #   * `labels` - Cloud Labels are a flexible and lightweight mechanism for organizing cloud resources.
        #
        # @return [Google::Cloud::Bigtable::Instance::Job]
        #   The job representing the long-running, asynchronous processing of
        #   an instance update operation.
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #   instance.display_name = "My app dev instance"  # Set display name
        #   instance.labels = { env: "dev", data: "users" }
        #   job = instance.save
        #
        #   job.done? #=> false
        #
        #   # Reload job until completion.
        #   job.wait_until_done!
        #   job.done? #=> true
        #
        #   if job.error?
        #     puts job.error
        #   else
        #     instance = job.instance
        #     puts instance.name
        #     puts instance.labels
        #   end
        #
        def save
          ensure_service!
          update_mask = Google::Protobuf::FieldMask.new paths: ["labels", "display_name", "type"]
          grpc = service.partial_update_instance @grpc, update_mask
          Instance::Job.from_grpc grpc, service
        end
        alias update save

        ##
        # Reloads instance data.
        #
        # @return [Google::Cloud::Bigtable::Instance]
        #
        def reload!
          @grpc = service.get_instance instance_id
          self
        end

        ##
        # Permanently deletes the instance from the project.
        #
        # @return [Boolean] Returns `true` if the instance was deleted.
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #   instance.delete
        #
        def delete
          ensure_service!
          service.delete_instance instance_id
          true
        end

        ##
        # Lists the clusters in the instance.
        #
        # See {Google::Cloud::Bigtable::Cluster#delete} and
        # {Google::Cloud::Bigtable::Cluster#save}.
        #
        # @param token [String] The `token` value returned by the last call to
        #   `clusters`; indicates that this is a continuation of a call
        #   and that the system should return the next page of data.
        # @return [Array<Google::Cloud::Bigtable::Cluster>]
        #   See({Google::Cloud::Bigtable::Cluster::List})
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   instance.clusters.all do |cluster|
        #     puts cluster.cluster_id
        #   end
        #
        def clusters token: nil
          ensure_service!
          grpc = service.list_clusters instance_id, token: token
          Cluster::List.from_grpc grpc, service, instance_id: instance_id
        end

        ##
        # Gets a cluster in the instance.
        #
        # See {Google::Cloud::Bigtable::Cluster#delete} and
        # {Google::Cloud::Bigtable::Cluster#save}.
        #
        # @param cluster_id [String] The unique ID of the requested cluster.
        # @return [Google::Cloud::Bigtable::Cluster, nil]
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   cluster = instance.cluster "my-cluster"
        #   puts cluster.cluster_id
        #
        def cluster cluster_id
          ensure_service!
          grpc = service.get_cluster instance_id, cluster_id
          Cluster.from_grpc grpc, service
        rescue Google::Cloud::NotFoundError
          nil
        end

        ##
        # Creates a cluster in the instance.
        #
        # @param cluster_id [String]
        #   The ID to be used when referring to the new cluster within its instance.
        # @param location [String]
        #   The location where this cluster's nodes and storage reside. For best
        #   performance, clients should be located as close as possible to this
        #   cluster. Example: "us-east-1b"
        # @param nodes [Integer] The number of nodes allocated to this cluster.
        #   More nodes enable higher throughput and more consistent performance.
        # @param storage_type [Symbol] Storage type.
        #   The type of storage used by this cluster to serve its
        #   parent instance's tables.
        #   Valid types are:
        #     * `:SSD` - Flash (SSD) storage.
        #     * `:HDD` - Magnetic drive (HDD).
        # @return [Google::Cloud::Bigtable::Cluster::Job]
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #   job = instance.create_cluster(
        #     "my-new-cluster",
        #     "us-east-1b",
        #     nodes: 3,
        #     storage_type: :SSD
        #   )
        #
        #   job.done? #=> false
        #
        #   # To block until the operation completes.
        #   job.wait_until_done!
        #   job.done? #=> true
        #
        #   if job.error?
        #     status = job.error
        #   else
        #     cluster = job.cluster
        #   end
        #
        def create_cluster cluster_id, location, nodes: nil, storage_type: nil
          ensure_service!
          attrs = {
            serve_nodes:          nodes,
            default_storage_type: storage_type,
            location:             location
          }.compact

          cluster = Google::Cloud::Bigtable::Admin::V2::Cluster.new attrs
          grpc = service.create_cluster instance_id, cluster_id, cluster
          Cluster::Job.from_grpc grpc, service
        end

        ##
        # Lists all tables in the instance.
        #
        #  See {Google::Cloud::Bigtable::Table#delete} and
        #  {Google::Cloud::Bigtable::Table#save}.
        #
        # @return [Array<Google::Cloud::Bigtable::Table>]
        #   (See {Google::Cloud::Bigtable::Table::List})
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   # Default name-only view
        #   instance.tables.all do |table|
        #     puts table.name
        #   end
        #
        def tables
          ensure_service!
          grpc = service.list_tables instance_id
          Table::List.from_grpc grpc, service
        end

        ##
        # Gets metadata information of a table in the instance.
        #
        # @param view [Symbol]
        #   The view to be applied to the returned tables' fields.
        #   Defaults to `SCHEMA_VIEW` if unspecified.
        #   Valid view types are.
        #   * `:NAME_ONLY` - Only populates `name`
        #   * `:SCHEMA_VIEW` - Only populates `name` and fields related to the table's schema
        #   * `:REPLICATION_VIEW` - Only populates `name` and fields related to the table's replication state.
        #   * `:FULL` - Populates all fields
        # @param perform_lookup [Boolean] Creates table object without verifying
        #   that the table resource exists.
        #   Calls made on this object will raise errors if the table
        #   does not exist. Default value is `false`. Optional.
        #   Helps to reduce admin API calls.
        # @param app_profile_id [String] The unique identifier for the app profile. Optional.
        #   Used only in data operations.
        #   This value specifies routing for replication. If not specified, the
        #   "default" application profile will be used.
        # @return [Google::Cloud::Bigtable::Table]
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   table = instance.table "my-table", perform_lookup: true
        #   puts table.name
        #   puts table.column_families
        #
        #   # Name-only view
        #   table = instance.table "my-table", view: :NAME_ONLY, perform_lookup: true
        #   puts table.name
        #
        # @example  Mutate rows.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   table = bigtable.table "my-instance", "my-table"
        #
        #   entry = table.new_mutation_entry "user-1"
        #   entry.set_cell(
        #     "cf1",
        #     "field1",
        #     "XYZ",
        #     timestamp: (Time.now.to_f * 1_000_000).round(-3) # microseconds
        #   ).delete_cells "cf2", "field02"
        #
        #   table.mutate_row entry
        #
        def table table_id, view: nil, perform_lookup: nil, app_profile_id: nil
          ensure_service!

          view ||= :SCHEMA_VIEW
          if perform_lookup
            grpc = service.get_table instance_id, table_id, view: view
            Table.from_grpc grpc, service, view: view, app_profile_id: app_profile_id
          else
            Table.from_path service.table_path(instance_id, table_id), service, app_profile_id: app_profile_id
          end
        rescue Google::Cloud::NotFoundError
          nil
        end

        ##
        # Creates a new table in the instance.
        #
        # The table can be created with a full set of initial column families,
        # specified in the request.
        #
        # @param name [String]
        #   The name by which the new table should be referred to within the parent
        #   instance.
        # @param column_families [Google::Cloud::Bigtable::ColumnFamilyMap]
        #   An object containing the column families for the table, mapped by
        #   column family name.
        # @param granularity [Symbol]
        #   The granularity at which timestamps are stored in this table.
        #   Timestamps not matching the granularity will be rejected.
        #   Valid value is `:MILLIS`.
        #   If unspecified, the value will be set to `:MILLIS`.
        # @param initial_splits [Array<String>]
        #   The optional list of row keys that will be used to initially split the
        #   table into several tablets (tablets are similar to HBase regions).
        #   Given two split keys, `s1` and `s2`, three tablets will be created,
        #   spanning the key ranges: `[, s1), [s1, s2), [s2, )`.
        #
        #   Example:
        #
        #   * Row keys := `["a", "apple", "custom", "customer_1", "customer_2", "other", "zz"]`
        #   * initial_split_keys := `["apple", "customer_1", "customer_2", "other"]`
        #   * Key assignment:
        #     * Tablet 1 : `[, apple)                => {"a"}`
        #     * Tablet 2 : `[apple, customer_1)      => {"apple", "custom"}`
        #     * Tablet 3 : `[customer_1, customer_2) => {"customer_1"}`
        #     * Tablet 4 : `[customer_2, other)      => {"customer_2"}`
        #     * Tablet 5 : `[other, )                => {"other", "zz"}`
        #   A hash in the form of `Google::Cloud::Bigtable::Admin::V2::CreateTableRequest::Split`
        #   can also be provided.
        # @yield [column_families] A block for adding column families.
        # @yieldparam [Google::Cloud::Bigtable::ColumnFamilyMap] column_families
        #   A mutable object containing the column families for the table,
        #   mapped by column family name.
        #
        # @return [Google::Cloud::Bigtable::Table]
        #
        # @example Create a table without column families.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   table = instance.create_table "my-table"
        #   puts table.name
        #
        # @example Create a table with initial splits and column families.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   initial_splits = ["user-00001", "user-100000", "others"]
        #   table = instance.create_table "my-table", initial_splits: initial_splits do |cfm|
        #     cfm.add "cf1", gc_rule: Google::Cloud::Bigtable::GcRule.max_versions(5)
        #     cfm.add "cf2", gc_rule: Google::Cloud::Bigtable::GcRule.max_age(600)
        #
        #     gc_rule = Google::Cloud::Bigtable::GcRule.union(
        #       Google::Cloud::Bigtable::GcRule.max_age(1800),
        #       Google::Cloud::Bigtable::GcRule.max_versions(3)
        #     )
        #     cfm.add "cf3", gc_rule: gc_rule
        #   end
        #
        #   puts table
        #
        def create_table name, column_families: nil, granularity: nil, initial_splits: nil, &block
          ensure_service!
          Table.create(
            service,
            instance_id,
            name,
            column_families: column_families,
            granularity:     granularity,
            initial_splits:  initial_splits,
            &block
          )
        end

        ##
        # Creates an app profile for the instance with a routing policy.
        # Only one routing policy can applied to the app profile. The policy can be
        # multi-cluster routing or single cluster routing.
        #
        # @param name [String] Unique ID of the app profile.
        # @param routing_policy [Google::Cloud::Bigtable::RoutingPolicy]
        #   The routing policy for all read/write requests that use this app
        #   profile. A value must be explicitly set.
        #
        #   Routing Policies:
        #   * {Google::Cloud::Bigtable::MultiClusterRoutingUseAny} - Read/write
        #     requests may be routed to any cluster in the instance and will
        #     fail over to another cluster in the event of transient errors or
        #     delays. Choosing this option sacrifices read-your-writes
        #     consistency to improve availability.
        #   * {Google::Cloud::Bigtable::SingleClusterRouting} - Unconditionally
        #     routes all read/write requests to a specific cluster. This option
        #     preserves read-your-writes consistency but does not improve
        #     availability. Value contains `cluster_id` and optional field
        #     `allow_transactional_writes`.
        # @param description [String] Description of the use case for this app profile.
        # @param etag [String]
        #   Strongly validated etag for optimistic concurrency control. Preserve the
        #   value returned from `GetAppProfile` when calling `UpdateAppProfile` to
        #   fail the request if there has been a modification in the meantime. The
        #   `update_mask` of the request need not include `etag` for this protection
        #   to apply.
        #   See [Wikipedia](https://en.wikipedia.org/wiki/HTTP_ETag) and
        #   [RFC 7232](https://tools.ietf.org/html/rfc7232#section-2.3) for more details.
        # @param ignore_warnings [Boolean]
        #   If true, ignore safety checks when creating the app profile.
        #   Default value is `false`.
        # @return [Google::Cloud::Bigtable::AppProfile]
        #
        # @example Create an app profile with a single cluster routing policy.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   routing_policy = Google::Cloud::Bigtable::AppProfile.single_cluster_routing(
        #     "my-cluster",
        #     allow_transactional_writes: true
        #   )
        #
        #   app_profile = instance.create_app_profile(
        #     "my-app-profile",
        #     routing_policy,
        #     description: "App profile for user data instance"
        #   )
        #   puts app_profile.name
        #
        # @example Create an app profile with multi-cluster routing policy.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   routing_policy = Google::Cloud::Bigtable::AppProfile.multi_cluster_routing
        #
        #   app_profile = instance.create_app_profile(
        #     "my-app-profile",
        #     routing_policy,
        #     description: "App profile for user data instance"
        #   )
        #   puts app_profile.name
        #
        # @example Create app profile and ignore warnings.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   routing_policy = Google::Cloud::Bigtable::AppProfile.multi_cluster_routing
        #
        #   app_profile = instance.create_app_profile(
        #     "my-app-profile",
        #     routing_policy,
        #     description: "App profile for user data instance",
        #     ignore_warnings: true
        #   )
        #   puts app_profile.name
        #
        def create_app_profile name, routing_policy, description: nil, etag: nil, ignore_warnings: false
          ensure_service!
          routing_policy_grpc = routing_policy.to_grpc
          if routing_policy_grpc.is_a? Google::Cloud::Bigtable::Admin::V2::AppProfile::MultiClusterRoutingUseAny
            multi_cluster_routing = routing_policy_grpc
          else
            single_cluster_routing = routing_policy_grpc
          end

          app_profile_attrs = {
            multi_cluster_routing_use_any: multi_cluster_routing,
            single_cluster_routing:        single_cluster_routing,
            description:                   description,
            etag:                          etag
          }.compact

          grpc = service.create_app_profile(
            instance_id,
            name,
            Google::Cloud::Bigtable::Admin::V2::AppProfile.new(app_profile_attrs),
            ignore_warnings: ignore_warnings
          )
          AppProfile.from_grpc grpc, service
        end

        ##
        # Gets an app profile in the instance.
        #
        # See {Google::Cloud::Bigtable::AppProfile#delete} and
        # {Google::Cloud::Bigtable::AppProfile#save}.
        #
        # @param app_profile_id [String] The unique name of the requested app profile.
        # @return [Google::Cloud::Bigtable::AppProfile, nil]
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   app_profile = instance.app_profile "my-app-profile"
        #
        #   if app_profile
        #     puts app_profile.name
        #   end
        #
        def app_profile app_profile_id
          ensure_service!
          grpc = service.get_app_profile instance_id, app_profile_id
          AppProfile.from_grpc grpc, service
        rescue Google::Cloud::NotFoundError
          nil
        end

        ##
        # Lists all app profiles in the instance.
        #
        # See {Google::Cloud::Bigtable::AppProfile#delete} and
        # {Google::Cloud::Bigtable::AppProfile#save}.
        #
        # @return [Array<Google::Cloud::Bigtable::AppProfile>]
        #   (See {Google::Cloud::Bigtable::AppProfile::List})
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   instance.app_profiles.all do |app_profile|
        #     puts app_profile.name
        #   end
        #
        def app_profiles
          ensure_service!
          grpc = service.list_app_profiles instance_id
          AppProfile::List.from_grpc grpc, service
        end

        ##
        # Gets the [Cloud IAM](https://cloud.google.com/iam/) access control
        # policy for the instance.
        #
        # @see https://cloud.google.com/bigtable/docs/access-control
        #
        # @yield [policy] A block for updating the policy. The latest policy
        #   will be read from the Bigtable service and passed to the block. After
        #   the block completes, the modified policy will be written to the
        #   service.
        # @yieldparam [Policy] policy the current Cloud IAM Policy for this
        #   instance.
        #
        # @return [Policy] The current Cloud IAM Policy for the instance.
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #   policy = instance.policy
        #
        # @example Update the policy by passing a block.
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #   instance = bigtable.instance "my-instance"
        #
        #   instance.policy do |p|
        #     p.add "roles/owner", "user:owner@example.com"
        #   end # 2 API calls
        #
        def policy
          ensure_service!
          grpc = service.get_instance_policy instance_id
          policy = Policy.from_grpc grpc
          return policy unless block_given?
          yield policy
          update_policy policy
        end

        ##
        # Updates the [Cloud IAM](https://cloud.google.com/iam/) access control
        # policy for the instance. The policy should be read from {#policy}.
        # See {Google::Cloud::Bigtable::Policy} for an explanation of the policy
        # `etag` property and how to modify policies.
        #
        # You can also update the policy by passing a block to {#policy}, which
        # will call this method internally after the block completes.
        #
        # @param new_policy [Policy] a new or modified Cloud IAM Policy for this
        #   instance
        #
        # @return [Policy] The policy returned by the API update operation.
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   policy = instance.policy
        #   policy.add "roles/owner", "user:owner@example.com"
        #   updated_policy = instance.update_policy policy
        #
        #   puts updated_policy.roles
        #
        def update_policy new_policy
          ensure_service!
          grpc = service.set_instance_policy instance_id, new_policy.to_grpc
          Policy.from_grpc grpc
        end
        alias policy= update_policy

        ##
        # Tests the specified permissions against the [Cloud
        # IAM](https://cloud.google.com/iam/) access control policy.
        #
        # @see https://cloud.google.com/iam/docs/managing-policies Managing Policies
        # @see https://cloud.google.com/bigtable/docs/access-control Access Control
        #
        # @param permissions [String, Array<String>] permissions The set of permissions to
        #   check access for. Permissions with wildcards (such as `*` or
        #   `bigtable.*`) are not allowed.
        #   See [Access Control](https://cloud.google.com/bigtable/docs/access-control).
        #
        #   Some of the permissions that can be checked on a instance are:
        #   * bigtable.instances.create
        #   * bigtable.instances.list
        #   * bigtable.instances.get
        #   * bigtable.tables.create
        #   * bigtable.tables.delete
        #   * bigtable.tables.get
        #   * bigtable.tables.list
        #
        # @return [Array<String>] The permissions that are configured for the policy.
        #
        # @example
        #   require "google/cloud/bigtable"
        #
        #   bigtable = Google::Cloud::Bigtable.new
        #
        #   instance = bigtable.instance "my-instance"
        #
        #   permissions = instance.test_iam_permissions(
        #     "bigtable.instances.get",
        #     "bigtable.instances.update"
        #   )
        #   permissions.include? "bigtable.instances.get" #=> true
        #   permissions.include? "bigtable.instances.update" #=> false
        #
        def test_iam_permissions *permissions
          ensure_service!
          grpc = service.test_instance_permissions instance_id, permissions.flatten
          grpc.permissions.to_a
        end

        # @private
        #
        # Creates a new Instance instance from a
        # Google::Cloud::Bigtable::Admin::V2::Instance.
        #
        # @param grpc [Google::Cloud::Bigtable::Admin::V2::Instance]
        # @param service [Google::Cloud::Bigtable::Service]
        # @return [Google::Cloud::Bigtable::Instance]
        #
        def self.from_grpc grpc, service
          new grpc, service
        end

        protected

        # @private
        #
        # Raise an error unless an active connection to the service is
        # available.
        #
        def ensure_service!
          raise "Must have active connection to service" unless service
        end
      end
    end
  end
end
