module Puppet::Parser::Functions
  newfunction(
      :pacemaker_cluster_nodes,
      type: :rvalue,
      arity: -2,
      doc: <<-eof
Convert different forms of node list to the required form.

Input data can be:
* String 'n1 n2a,n2b n3'
* Array ['n1', ['n2a','n2b'], 'n3']
* Hash { 'name' => 'n1', 'ring0' => '192.168.0.1' }

Hash can have optional keys: name, id, ip, votes,

Node id will be autogenerated unless id key is provided.

Internal nodes structure example:
  ```
  [
    {
      name: 'node1',
      id: '1',
      votes: '2',
      ring0: '192.168.0.1',
      ring1: '172.16.0.1',
    },
    {
      name: 'node2',
      id: '2',
      votes: '1',
      ring0: '192.168.0.2',
      ring1: '172.16.0.2',
    },
    {
      name: 'node3',
      id: '3',
      votes: '1',
      ring0: '192.168.0.3',
      ring1: '172.16.0.3',
    }
  ]
  # All fields except at least one ring address are optional
  ```

If neither 'ring0' nor 'ring1' are found fields 'ip' and 'name' will be used.

Output forms:
* hash - output the hash of the node ids and their data (used for corosync config template)
* list - output the space ad comma separated nodes and interfaces (used for "pcs cluster setup")
* array - output the plain array of nodes (used for pacemaker_auth or "pcs cluster auth")
      eof
  ) do |args|
    nodes = args[0]
    form = args[1] || 'hash'
    form = form.to_s.downcase
    fail 'Nodes are not provided!' unless nodes
    forms = %w(list array hash)
    fail "Unknown form: '#{form}'" unless forms.include? form

    array_formatter = lambda do |structure|
      list = []
      structure.each do |node|
        next unless node.is_a? Hash
        list << node['ring0'] if node['ring0']
        list << node['ring1'] if node['ring1']
      end
      list.flatten.compact.uniq
    end

    list_formatter = lambda do |structure|
      list = []
      structure.each do |node|
        node_rings = []
        node_rings[0] = node['ring0'] if node['ring0']
        node_rings[1] = node['ring1'] if node['ring1']
        list << node_rings.join(',')
      end
      list.join ' '
    end

    hash_formatter = lambda do |structure|
      hash = {}
      structure.each do |node|
        id = node['id']
        next unless id
        hash[id] = node
      end
      hash
    end

    node_split_to_rings = lambda do |node|
      node = node.to_s.chomp.strip
      rings = node.split ','
      node_hash = {}
      node_hash['ring0'] = rings[0].to_s if rings[0] and rings[0] != ''
      node_hash['ring1'] = rings[1].to_s if rings[1] and rings[1] != ''
      node_hash
    end

    node_hash_process = lambda do |node|
      ring0 = node['ring0']
      ring1 = node['ring1']
      ring0 = node['ip'] if node['ip'] and not ring0
      ring0 = node['name'] if node['name'] and not ring0
      node_hash = {}
      node_hash['ring0'] = ring0.to_s if ring0
      node_hash['ring1'] = ring1.to_s if ring1
      node_hash['name'] = node['name'].to_s if node['name']
      node_hash['id'] = node['id'].to_s if node['id']
      node_hash['vote'] = node['vote'].to_s if node['vote']
      node_hash
    end

    string_parser = lambda do |string|
      string = string.to_s.chomp.strip
      node_list = []
      string.split.each do |node|
        node_hash = node_split_to_rings.call node
        next unless node_hash['ring0'] or node_hash['ring1']
        node_list << node_hash
      end
      node_list
    end

    array_parser = lambda do |array|
      array = [array] unless array.is_a? Array
      node_list = []
      array.each do |node|
        if node.is_a? Array
          node_hash = {}
          node_hash['ring0'] = node[0].to_s if node[0]
          node_hash['ring1'] = node[1].to_s if node[1]
        elsif node.is_a? Hash
          node_hash = node_hash_process.call node
        else
          node_hash = node_split_to_rings.call node.to_s
        end
        next unless node_hash['ring0'] or node_hash['ring1']
        node_list << node_hash
      end
      node_list
    end

    hash_parser = lambda do |hash|
      fail "Data is not a hash: #{hash.inspect}" unless hash.is_a? Hash
      node_list = []
      hash.each do |node_name, node|
        node = node.dup
        node['name'] = node_name if node_name and not node['name']
        node_hash = node_hash_process.call node
        next unless node_hash['ring0'] or node_hash['ring1']
        node_list << node_hash
      end
      node_list
    end

    set_node_ids = lambda do |structure|
      next_id = 1
      structure.each do |node|
        unless node['id']
          node['id'] = next_id.to_s
          next_id += 1
        end
      end
    end

    structure = []

    if nodes.is_a? String
      structure = string_parser.call nodes
    elsif nodes.is_a? Array
      structure = array_parser.call nodes
    elsif nodes.is_a? Hash
      structure = hash_parser.call nodes
    else
      fail "Got unsupported nodes input data: #{nodes.inspect}"
    end

    set_node_ids.call structure

    if form == 'hash'
      hash_formatter.call structure
    elsif form == 'list'
      list_formatter.call structure
    elsif form == 'array'
      array_formatter.call structure
    end

  end
end
