# Authors:
#   Rob Crittenden <rcritten@redhat.com>
#
# Copyright (C) 2011  Red Hat
# see file 'COPYING' for use and warranty information
#
# 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, see <http://www.gnu.org/licenses/>.

from ipaserver.install.plugins import FIRST, LAST
from ipaserver.install.plugins.baseupdate import PreUpdate, PostUpdate
from ipalib import api, errors
from ipapython import ipautil
from ipapython.dn import DN, EditableDN

def entry_to_update(entry):
    """
    Convert an entry into a name/value pair list that looks like an update.

    An entry is a dict.

    An update is a list of name/value pairs.
    """
    update = []
    for attr in entry.keys():
        if isinstance(entry[attr], list):
            for i in xrange(len(entry[attr])):
                update.append('%s:%s' % (str(attr), str(entry[attr][i])))
        else:
            update.append('%s:%s' % (str(attr), str(entry[attr])))

    return update

class GenerateUpdateMixin(object):
    def generate_update(self, deletes=False):
        """
        We need to separate the deletes that need to happen from the
        new entries that need to be added.
        """
        ldap = self.obj.backend

        suffix = ipautil.realm_to_suffix(api.env.realm)
        searchfilter = '(objectclass=*)'
        definitions_managed_entries = []

        old_template_container = DN(('cn', 'etc'), suffix)
        new_template_container = DN(('cn', 'Templates'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix)

        old_definition_container = DN(('cn', 'managed entries'), ('cn', 'plugins'), ('cn', 'config'), suffix)
        new_definition_container = DN(('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix)

        definitions_dn = DN(('cn', 'Definitions'))
        update_list = []
        restart = False

        # If the old entries don't exist the server has already been updated.
        try:
            definitions_managed_entries, truncated = ldap.find_entries(
                searchfilter, ['*'], old_definition_container,
                ldap.SCOPE_ONELEVEL)
        except errors.NotFound, e:
            return (False, update_list)

        for entry in definitions_managed_entries:
            assert isinstance(entry.dn, DN)
            if deletes:
                old_dn = entry['managedtemplate'][0]
                assert isinstance(old_dn, DN)
                try:
                    entry = ldap.get_entry(old_dn, ['*'])
                except errors.NotFound, e:
                    pass
                else:
                    # Compute the new dn by replacing the old container with the new container
                    new_dn = EditableDN(entry.dn)
                    if new_dn.replace(old_template_container, new_template_container) != 1:
                        self.error("unable to replace '%s' with '%s' in '%s'",
                                   old_template_container, new_template_container, entry.dn)
                        continue

                    new_dn = DN(new_dn)

                    # The old attributes become defaults for the new entry
                    new_update = {'dn': new_dn,
                                  'default': entry_to_update(entry)}

                    # Delete the old entry
                    old_update = {'dn': entry.dn, 'deleteentry': None}

                    # Add the delete and replacement updates to the list of all updates
                    update_list.append({entry.dn: old_update, new_dn: new_update})

            else:
                # Update the template dn by replacing the old containter with the new container
                old_dn = entry['managedtemplate'][0]
                new_dn = EditableDN(old_dn)
                if new_dn.replace(old_template_container, new_template_container) != 1:
                    self.error("unable to replace '%s' with '%s' in '%s'",
                               old_template_container, new_template_container, old_dn)
                    continue
                new_dn = DN(new_dn)
                entry['managedtemplate'] = new_dn

                # Edit the dn, then convert it back to an immutable DN
                old_dn = entry.dn
                new_dn = EditableDN(old_dn)
                if new_dn.replace(old_definition_container, new_definition_container) != 1:
                    self.error("unable to replace '%s' with '%s' in '%s'",
                               old_definition_container, new_definition_container, old_dn)
                    continue
                new_dn = DN(new_dn)

                # The old attributes become defaults for the new entry
                new_update = {'dn': new_dn,
                              'default': entry_to_update(entry)}

                # Add the replacement update to the collection of all updates
                update_list.append({new_dn: new_update})

        if len(update_list) > 0:
            restart = True
            update_list.sort(reverse=True)

        return (restart, update_list)

class update_managed_post_first(PreUpdate, GenerateUpdateMixin):
    """
    Update managed entries
    """
    order=FIRST

    def execute(self, **options):
        # Never need to restart with the pre-update changes
        (ignore, update_list) = self.generate_update(False)

        return (False, True, update_list)

api.register(update_managed_post_first)

class update_managed_post(PostUpdate, GenerateUpdateMixin):
    """
    Update managed entries
    """
    order=LAST

    def execute(self, **options):
        (restart, update_list) = self.generate_update(True)

        return (restart, True, update_list)

api.register(update_managed_post)
