##############################################################################
#
# Copyright (c) 2011 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 slapos.recipe.librecipe import GenericSlapRecipe
import os
import json
import traceback

SECTION_BACKEND_PUBLISHER = """[publish-apache-backend-list]
recipe = slapos.cookbook:publish"""
ZOPE_PORT_BASE = 12000
ZEO_PORT_BASE = 15000
HAPROXY_PORT_BASE = 11000
APACHE_PORT_BASE = 10000

class Recipe(GenericSlapRecipe):
  def _options(self, options):
    self.dirname = os.path.join(self.buildout['buildout']['parts-directory'],
      self.name)
    options['output'] = os.path.join(self.dirname, self.name + '.cfg')

  def _generateRealTemplate(self):
    current_zeo_port = ZEO_PORT_BASE
    current_zope_port = ZOPE_PORT_BASE
    current_apache_port = APACHE_PORT_BASE
    current_haproxy_port = HAPROXY_PORT_BASE
    json_data = json.loads(self.parameter_dict['json'])
    site_id = str(json_data['site-id'])
    # prepare zeo
    output = ''
    part_list = []
    zope_dict = {}
    zope_connection_dict = {}
    known_tid_storage_identifier_dict = {}
    snippet_zeo = open(self.options['snippet-zeo']).read()
    for zeo_id, zeo_configuration_list in json_data['zeo'].iteritems():
      if not type(zeo_configuration_list) in (type([]), type(set()), type(())):
        raise ValueError('%s passed in json is not a list, json: %s.' % (
          zeo_configuration_list, json_data))
      storage_list = []
      a = storage_list.append
      for zeo_slave in zeo_configuration_list:
        zope_connection_dict[zeo_slave['storage-name']] = {
          'zope-cache-size': zeo_slave['zope-cache-size'],
          'zeo-cache-size': zeo_slave['zeo-cache-size'],
          'mount-point': zeo_slave['mount-point'] % {'site-id': site_id},
          'storage-name': zeo_slave['storage-name'],
          'server': '${zeo-instance-%(zeo-id)s:ip}:${zeo-instance-%(zeo-id)s:port}' % {'zeo-id': zeo_id}
        }
        zodb_path = os.path.join('${directory:zodb}', zeo_slave['storage-name'] + '.fs')
        a('  storage-name=%(storage-name)s zodb-path=%(zodb-path)s' % {'zodb-path': zodb_path, 'storage-name': zeo_slave['storage-name']})
        known_tid_storage_identifier_dict[
          "((('%(ip)s', %(port)s),), '%(storage_name)s')" % dict(
            ip='${zeo-instance-%s:ip}' % zeo_id,
            port='${zeo-instance-%s:port}' % zeo_id,
            storage_name=zeo_slave['storage-name'])] = (zodb_path, '${directory:zodb-backup}/%s/' % zeo_slave['storage-name'], zeo_slave['serialize-path'] % {'site-id': site_id})
      current_zeo_port += 1
      output += snippet_zeo % dict(
        zeo_id=zeo_id,
        zeo_port=current_zeo_port,
        storage_list='\n'.join(storage_list)
      )
      part_list.extend([
        "zeo-instance-%s" % zeo_id,
        "logrotate-entry-zeo-%s" % zeo_id
      ])

    zeo_connection_list = []
    a = zeo_connection_list.append
    for k, v in zope_connection_dict.iteritems():
      a('  zeo-cache-size=%(zeo-cache-size)s zope-cache-size=%(zope-cache-size)s server=%(server)s mount-point=%(mount-point)s storage-name=%(storage-name)s' % v)
    zeo_connection_string = '\n'.join(zeo_connection_list)
    zope_dict.update(
      timezone=json_data['timezone'],
      zeo_connection_string=zeo_connection_string,
      site_id=site_id,
    )
    # always one distribution node
    current_zope_port += 1
    snippet_zope = open(self.options['snippet-zope']).read()
    zope_id = 'zope-distribution'
    part_list.append(zope_id)
    part_list.append('logrotate-entry-%s' % zope_id)
    output += snippet_zope % dict(zope_thread_amount=1, zope_id=zope_id,
      zope_port=current_zope_port, zope_timeserver=True, 
      longrequest_logger_file='', longrequest_logger_timeout='', 
      longrequest_logger_interval='', **zope_dict)
    # always one admin node
    current_zope_port += 1
    zope_id = 'zope-admin'
    part_list.append(zope_id)
    part_list.append('logrotate-entry-%s' % zope_id)
    output += snippet_zope % dict(zope_thread_amount=1, zope_id=zope_id,
      zope_port=current_zope_port, zope_timeserver=False, 
      longrequest_logger_file='', longrequest_logger_timeout='', 
      longrequest_logger_interval='', **zope_dict)
    # handle activity key
    for q in range(1, json_data['activity']['zopecount'] + 1):
      current_zope_port += 1
      part_name = 'zope-activity-%s' % q
      part_list.append(part_name)
      part_list.append('logrotate-entry-%s' % part_name)
      output += snippet_zope % dict(zope_thread_amount=1, zope_id=part_name,
        zope_port=current_zope_port, zope_timeserver=True,
        longrequest_logger_file='', longrequest_logger_timeout='', 
        longrequest_logger_interval='', **zope_dict)
    # handle backend key
    snippet_backend = open(self.options['snippet-backend']).read()
    publish_url_list = []
    for backend_name, backend_configuration in json_data['backend'].iteritems():
      haproxy_backend_list = []
      for q in range(1, backend_configuration['zopecount'] + 1):
        current_zope_port += 1
        part_name = 'zope-%s-%s' % (backend_name, q)
        part_list.append(part_name)
        part_list.append('logrotate-entry-%s' % part_name)
        longrequest_logger = backend_configuration.get("longrequest-logger", None)
        if longrequest_logger is not None:
          longrequest_part_name = '%s-longrequest' %part_name 
          longrequest_logger_file = '${basedirectory:log}/%s.log' \
                                      %longrequest_part_name
          longrequest_logger_timeout = longrequest_logger.get('timeout', '4')
          longrequest_logger_interval = longrequest_logger.get('interval', '2')
        else:
          longrequest_logger_file = longrequest_logger_timeout = \
            longrequest_logger_interval = ''
        output += snippet_zope % dict(
          zope_thread_amount=backend_configuration['thread-amount'],
          zope_id=part_name, zope_port=current_zope_port, zope_timeserver=False,
          longrequest_logger_file=longrequest_logger_file, 
          longrequest_logger_timeout=longrequest_logger_timeout, 
          longrequest_logger_interval=longrequest_logger_interval,
          **zope_dict)
        haproxy_backend_list.append('${%(part_name)s:ip}:${%(part_name)s:port}' % dict(part_name=part_name))

      scheme = backend_configuration.get('scheme', ['https'])

      # now generate backend access
      current_apache_port += 1
      current_haproxy_port += 1
      backend_dict = dict(
        backend_name=backend_name,
        apache_port=current_apache_port,
        apache_public_port=current_apache_port+1,
        haproxy_port=current_haproxy_port,
        access_control_string=backend_configuration['access-control-string'],
        maxconn=backend_configuration['maxconn'],
        server_check_path='/%s/getId' % site_id,
        haproxy_backend_list=' '.join(haproxy_backend_list),
        ssl_authentication=backend_configuration.get('ssl-authentication',
          False),
        backend_path=backend_configuration.get('backend-path', '/') % {
            'site-id': site_id}

      )
      current_apache_port += 1
      output += snippet_backend % backend_dict

      if 'http' in scheme:
        part_list.append('apache-public-%(backend_name)s logrotate-entry-apache-public-%(backend_name)s' % dict(backend_name=backend_name))
        publish_url_list.append('url-public-%(backend_name)s = http://[${apache-public-%(backend_name)s:ip}]:${apache-public-%(backend_name)s:port}' % dict(
          backend_name=backend_name))
      if 'https' in scheme:
        part_list.append('apache-%(backend_name)s ca-apache-%(backend_name)s logrotate-entry-apache-%(backend_name)s haproxy-%(backend_name)s' % dict(backend_name=backend_name))
        publish_url_list.append('url-%(backend_name)s = https://[${apache-%(backend_name)s:ip}]:${apache-%(backend_name)s:port}' % dict(
          backend_name=backend_name))

    output += SECTION_BACKEND_PUBLISHER + '\n'
    output += '\n'.join(publish_url_list)
    part_list.append('publish-apache-backend-list')
    master_dict = self.parameter_dict.copy()
    if 'erp5-ca' in json_data:
      erp5_ca = json_data['erp5-ca']
      # Fetching exactly named parameters from json in order to raise proper
      # error if required
      master_dict.update(
        erp5_ca_country_code = erp5_ca['country-code'],
        erp5_ca_email = erp5_ca['email'],
        erp5_ca_state = erp5_ca['state'],
        erp5_ca_city = erp5_ca['city'],
        erp5_ca_company = erp5_ca['company']
      )
    else:
      master_dict.update(dict(
        erp5_ca_country_code = 'XX',
        erp5_ca_email = 'xx@example.com',
        # XXX-BBB: State by mistake has been configured as string "('State',)"
        #          string, so keep this for backward compatibility of existing
        #          automatically setup CAs
        erp5_ca_state = "('State',)",
        erp5_ca_city = 'City',
        erp5_ca_company = 'Company'
      ))
    prepend = open(self.options['snippet-master']).read() % dict(
        part_list='  \n'.join(['  '+q for q in part_list]),
        known_tid_storage_identifier_dict=known_tid_storage_identifier_dict,
        haproxy_section="haproxy-%s" % backend_name,
        zope_section=zope_id,
        site_id=site_id,
        **master_dict
        )
    output = prepend + output
    with open(self.options['output'], 'w') as f:
      f.write(output)

  def _install(self):
    if not os.path.exists(self.dirname):
      os.mkdir(self.dirname)
    if not "json" in self.parameter_dict:
      # no json transimtted, nothing to do
      with open(self.options['output'], 'w') as f:
        f.write("[buildout]\nparts =\n")
    else:
      try:
        self._generateRealTemplate()
      except Exception:
        print 'Ignored issue during template generation:\n%s' % \
          traceback.format_exc()
    return [self.dirname]