#  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.


Namespaces:
  =: io.murano.applications
  std: io.murano

--- # ------------------------------------------------------------------ # ---

Name: ReplicationGroup

Properties:
  provider:
    Contract: $.class(ReplicaProvider).notNull()

  minItems:
    Contract: $.int().notNull()
    Default: 0

  maxItems:
    Contract: $.int()
    Default: null

  numItems:
    Usage: InOut
    Contract: $.int().notNull()
    Default: 1

  items:
    Usage: Out
    Contract:
      - $.class(std:Object)

Methods:
  deploy:
    Body:
      # release excessive replicas
      - $oldItems: $this.items.take($this.numItems)
      - $itemsToRelease: $this.items.skip($this.numItems)
      - $this.provider.releaseReplicas($itemsToRelease)

      - $delta: $this.numItems - len($this.items)
      - $startIndex: len($this.items) + 1
      - $endIndex: $startIndex + $delta
      - $createdItems: range($startIndex, $endIndex).select(
          $this.provider.createReplica($, $this)).takeWhile($ != null)
      - $this.items: $oldItems + $createdItems

  scale:
    Arguments:
      - delta:
          Contract: $.int().notNull()
          Default: 1
    Body:
      - $this.numItems: max($this.numItems + $delta, $this.minItems)
      - If: $this.maxItems != null
        Then:
          - $this.numItems: min($this.numItems, $this.maxItems)
      - $this.deploy()

  .destroy:
    Body:
      - $this.numItems: 0
      - $this.deploy()

--- # ------------------------------------------------------------------ # ---

Name: ReplicaProvider

Methods:
  createReplica:
    Arguments:
      - index:
          Contract: $.int().notNull()
      - owner:
          Contract: $.class(std:Object)

  releaseReplicas:
    Arguments:
      replicas:
        Contract:
          - $.class(std:Object)
    Body:
      Return: $replicas


--- # ------------------------------------------------------------------ # ---


Name: CloneReplicaProvider
Extends: ReplicaProvider

Properties:
  template:
    Contract: $.template(std:Object).notNull()

Methods:
  createReplica:
    Arguments:
      - index:
          Contract: $.int().notNull()
      - owner:
          Contract: $.class(std:Object)

    Body:
      - Return: new($this.template, $owner)

--- # ------------------------------------------------------------------ # ---
# Replica provider that is a composition of other replica providers

Name: CompositeReplicaProvider
Extends: ReplicaProvider

Properties:
  providers:
    Contract:
      - $.class(ReplicaProvider).notNull()

Methods:
  createReplica:
    Arguments:
      - index:
          Contract: $.int().notNull()
      - owner:
          Contract: $.class(std:Object)
    Body:
      - Return: $this.providers.select($.createReplica($index, $owner)).where($ != null).first(null)

  releaseReplicas:
    Arguments:
      replicas:
        Contract:
          - $.class(std:Object)
    Body:
      - For: provider
        In: $this.providers
        Do:
          - $replicas: $provider.releaseReplicas($replicas)
          - If: $replicas = null or not $replicas.any()
            Then:
              Break:
      - Return: $replicas

--- # ------------------------------------------------------------------ # ---
# A replica provider from the prepopulated pool

Name: PoolReplicaProvider
Extends: ReplicaProvider

Properties:
  pool:
    Contract:
      - $.class(std:Object).notNull()

  consumedItems:
    Usage: Out
    Contract:
      - $.class(std:Object).notNull()

Methods:
  createReplica:
    Arguments:
      - index:
          Contract: $.int().notNull()
      - owner:
          Contract: $.class(std:Object)
    Body:
      - $item: $this.pool.where(not $this.consumedItems.contains($)).first(null)
      - If: $item != null
        Then:
          - $this.consumedItems: $this.consumedItems.append($item)
      - Return: $item

  releaseReplicas:
    Arguments:
      replicas:
        Contract:
          - $.class(std:Object)
    Body:
      - $poolReplicas: $replicas.where($this.consumedItems.contains($))
      - $this.consumedItems: $this.consumedItems.where(not $poolReplicas.contains($))
      - Return: $replicas.where(not $poolReplicas.contains($))

--- # ------------------------------------------------------------------ # ---
# Replica provider with a load balancing that returns instance from the
# prepopulated list. Once the provider runs out of free items it goes to
# beginning of the list and returns the same instances again.

Name: RoundrobinReplicaProvider
Extends: ReplicaProvider

Properties:
  items:
    Contract:
      - $.class(std:Object).notNull()
      - 1

Methods:
  createReplica:
    Arguments:
      - index:
          Contract: $.int().notNull()
      - owner:
          Contract: $.class(std:Object)
    Body:
      - $index: $.getAttr(lastIndex, 0)
      - $.setAttr(lastIndex, ($index + 1) mod len($this.items))
      - Return: $this.items[$index]
