#  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
  res: io.murano.resources
  std: io.murano

--- # ------------------------------------------------------------------ # ---
# A group of Servers

Name: ServerGroup
Methods:
  getServers:

  getKey:
    Body:
      - Return: $this.getServers().select(id($)).orderBy($)


  deployServers:
    Usage: Static
    Arguments:
      - serverGroup:
          Contract: $.class(ServerGroup)
      - servers:
          Contract:
            - $.class(res:Instance).notNull()
    Body:
      - $environment: $serverGroup.find(std:Environment)
      - $servers.select($this._deployServer($, $environment, $serverGroup))
      - $servers.select($.endDeploy())

  releaseServers:
    Usage: Static
    Arguments:
      - servers:
          Contract:
            - $.class(res:Instance).notNull()
    Body:
      - $servers.select($.beginReleaseResources())
      - $servers.select($.endReleaseResources())

  _deployServer:
    Usage: Static
    Arguments:
      - server:
          Contract: $.class(res:Instance).notNull()
      - environment:
          Contract: $.class(std:Environment)
      - serverGroup:
          Contract: $.class(ServerGroup)
    Body:
      - If: $environment and not $server.openstackId
        Then:
          - $environment.reporter.report($serverGroup, 'Provisioning VM for ' + (name($server) or $server.name))
      - $server.beginDeploy()

--- # ------------------------------------------------------------------ # ---
# A group of prepopulated servers

Name: ServerList
Extends: ServerGroup

Properties:
  servers:
    Contract:
      - $.class(res:Instance).notNull()

Methods:
  deploy:
    Body:
      - $this.deployServers($this, $this.servers)

  .destroy:
    Body:
      - $this.releaseServers($this.servers)

  getServers:
    Body:
      Return: $.servers

--- # ------------------------------------------------------------------ # ---
# Degenrate case of a server group which consists of a single server

Name: SingleServerGroup
Extends: ServerGroup

Properties:
  server:
    Contract: $.class(res:Instance).notNull()

Methods:
  setServer:
    Arguments:
      - server:
          Contract: $.class(res:Instance).notNull()
    Body:
      - $this.items: $server

  deploy:
    Body:
      - $this.deployServers($this, [$this.server])

  .destroy:
    Body:
      - $this.releaseServers([$this.server])

  getServers:
    Body:
      Return: [$.server]

--- # ------------------------------------------------------------------ # ---
# A replication group aggregating Servers
# Adds a logic to concurrently provision and unprovision servers


Name: ServerReplicationGroup
Extends:
  - ReplicationGroup
  - ServerGroup

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

  items:
    Usage: Out
    Contract:
      - $.class(res:Instance)

Methods:
  .init:
    Body:
      - $this._env: $.find(std:Environment).require()

  deploy:
    Body:
      - $delta: $this.numItems - len($this.items)
      - If: abs($delta) > 1
        Then:
          - $verb: switch($delta > 0 => Creating, $delta < 0 => Removing)
          - $target: switch($delta > 0 => for, $delta < 0 => from)
          - If: name($this)
            Then:
              - $target: format(' {0} {1}', $target, name($this))
            Else:
              - $target: ''
          - $this._env.reporter.report($this, format('{0} {1} servers{2}',
              $verb, abs($delta), $target))
      - cast($this, ReplicationGroup).deploy()
      - $this.deployServers($this, $this.items)

  getServers:
    Body:
      Return: $.items

--- # ------------------------------------------------------------------ # ---
# A server group that composed of other server groups

Name: CompositeServerGroup
Extends: ServerGroup

Properties:
  serverGroups:
    Contract:
      - $.class(ServerGroup).notNull()

Methods:
  deploy:
    Body:
      - $this.serverGroups.pselect($.deploy())

  getServers:
    Body:
      Return: $this.serverGroups.selectMany($.getServers())

--- # ------------------------------------------------------------------ # ---
# A replication provider acting as a default factory class for Servers

Name: TemplateServerProvider
Extends: ReplicaProvider

Properties:
  template:
    Contract: $.template(res:Instance, excludeProperties => [name]).notNull()

  serverNamePattern:
    Contract: $.string().notNull()

  allocated:
    Usage: Out
    Contract: $.int().notNull()
    Default: 0

  capacity:
    Contract: $.int()

Methods:
  createReplica:
    Arguments:
      - index:
          Contract: $.int().notNull()
      - owner:
          Contract: $.class(std:Object)
    Body:
      - If: $this.capacity = null or $this.allocated < $this.capacity
        Then:
        - $template: $this.template
        - $template.name: $this.serverNamePattern.format($index)
        - $ownerGroup: $this.find(ServerGroup)
        - If: $ownerGroup and name($ownerGroup)
          Then:
          - $groupName: format(' ({0})', name($ownerGroup))
          Else:
          - $groupName: ''
        - $template['?'].name: format('Server {0}{1}', $index, $groupName)
        - $this.allocated: $this.allocated + 1
        - Return: new($template, $owner)
        Else:
        - Return: null

  releaseReplicas:
    Arguments:
      replicas:
        Contract:
          - $.class(res:Instance)
    Body:
      - $replicas.select($.beginReleaseResources())
      - $replicas.select($.endReleaseResources())
      - $this.allocated: max(0, $this.allocated - len($replicas))
      - Return: []


