##############################################################################
#
# Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from collections import OrderedDict
from .librecipe import unwrap, wrap, GenericSlapRecipe
import six
from zc.buildout import UserError

def volatileOptions(options, volatile):
  def copy():
    copy = options_copy()
    for key in volatile:
      copy.pop(key, None)
    return copy
  options_copy = options.copy
  options.copy = copy

class Recipe(GenericSlapRecipe):
  """
  Early initialization of published parameters.

  The '-init' option defines parameters that should be published before
  requesting any partitions, and how they are initialized.

  Example:

    [publish-early]
    recipe = slapos.cookbook:publish-early
    -init =
      foo gen-foo:x
      bar gen-bar:y
    bar = z

    [gen-foo]
    ...

    [publish]
    recipe = slapos.cookbook:publish.serialised
    -extends = publish-early
    ...

  Just before the recipe of [gen-foo] is instantiated, 'x' is overridden with
  the published value 'foo' if it exists. If its __init__ modifies 'x', the new
  value is published. To prevent [gen-foo] from being accessed too early, 'x'
  is then removed and the value can only be accessed with ${publish-early:foo}.
  Init sections are processed in the order of first appearance in the '-init'
  section, so that a init section can access a value that is generated by a
  previous one (above, [gen-bar] can access ${publish-early:foo}).

  Generated values don't end up in the buildout installed file, which is good
  if they're secret. Note however that buildout won't detect if values change
  and it may only call update().

  ${publish-early:bar} is forced to 'z' (${gen-bar:y} ignored):
  a line like 'bar = z' is usually rendered conditionally with Jinja2.
  """
  def __init__(self, buildout, name, options):
    GenericSlapRecipe.__init__(self, buildout, name, options)
    init = OrderedDict()
    for line in options['-init'].splitlines():
      if line:
        k, v = line.split()
        if k not in options:
          section, v = v.split(':')
          try:
            init[section][k] = v
          except KeyError:
            init[section] = {k: v}
    if init:
      self.slap.initializeConnection(self.server_url, self.key_file,
        self.cert_file)
      computer_partition = self.slap.registerComputerPartition(
        self.computer_id, self.computer_partition_id)
      published_dict = unwrap(computer_partition.getConnectionParameterDict())

      Options = buildout.Options
      if 'Options' in buildout.__dict__:
        def revertOptions():
          buildout.Options = Options
      else:
        def revertOptions():
          try:
            del buildout.Options
          except AttributeError:
            pass
      def newOptions(buildout, section, data):
        assert section == init_section, (section, init_section)
        revertOptions()
        self = buildout.Options(buildout, section, data)
        self.update(override)
        return self

      publish = False
      publish_dict = {}
      try:
        for init_section, init in six.iteritems(init):
          override = {}
          for k, v in six.iteritems(init):
            try:
              override[v] = published_dict[k]
            except KeyError:
              pass
          buildout.Options = newOptions
          init_section = buildout[init_section]
          if buildout.Options is not Options:
            raise UserError("%s section was already initialized" % init_section)
          new = {}
          for k, v in six.iteritems(init):
            try:
              options[k] = publish_dict[k] = new[v] = init_section.pop(v)
            except KeyError:
              pass
          if new != override:
            publish = True
      finally:
        revertOptions()

      if publish:
        computer_partition.setConnectionDict(wrap(publish_dict))

      publish = [k for k in options
        if k != 'recipe' and not k.startswith('-')]
      publish += publish_dict
      publish_dict['-publish'] = ' '.join(publish)
      volatileOptions(options, list(publish_dict))

  install = update = lambda self: None