/*
 * RHQ Management Platform
 * Copyright (C) 2011 Red Hat, Inc.
 * All rights reserved.
 *
 * 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 version 2 of the License.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.enterprise.server.drift;

import static javax.ejb.TransactionAttributeType.NEVER;
import static org.rhq.core.domain.common.EntityContext.forResource;
import static org.rhq.core.domain.drift.DriftChangeSetCategory.COVERAGE;
import static org.rhq.core.domain.drift.DriftConfigurationDefinition.DriftHandlingMode.normal;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.criteria.DriftDefinitionCriteria;
import org.rhq.core.domain.criteria.DriftDefinitionTemplateCriteria;
import org.rhq.core.domain.drift.DriftDefinition;
import org.rhq.core.domain.drift.DriftDefinitionComparator;
import org.rhq.core.domain.drift.DriftDefinitionTemplate;
import org.rhq.core.domain.drift.DriftSnapshot;
import org.rhq.core.domain.drift.DriftSnapshotRequest;
import org.rhq.core.domain.drift.dto.DriftChangeSetDTO;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.PageList;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.authz.RequiredPermission;
import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal;
import org.rhq.enterprise.server.resource.ResourceTypeNotFoundException;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;

@Stateless
public class DriftTemplateManagerBean implements DriftTemplateManagerLocal, DriftTemplateManagerRemote {

    @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
    private EntityManager entityMgr;

    @EJB
    ResourceTypeManagerLocal resourceTypeMgr;

    @EJB
    DriftManagerLocal driftMgr;

    @EJB
    DriftTemplateManagerLocal templateMgr;

    @Override
    public PageList<DriftDefinitionTemplate> findTemplatesByCriteria(Subject subject,
        DriftDefinitionTemplateCriteria criteria) {
        CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
        CriteriaQueryRunner<DriftDefinitionTemplate> queryRunner = new CriteriaQueryRunner<DriftDefinitionTemplate>(
            criteria, generator, entityMgr);
        PageList<DriftDefinitionTemplate> result = queryRunner.execute();

        return result;
    }

    @RequiredPermission(Permission.MANAGE_SETTINGS)
    @Override
    public DriftDefinitionTemplate createTemplate(Subject subject, int resourceTypeId, boolean isUserDefined,
        DriftDefinition definition) {

        try {
            // before we do anything, validate certain field values to prevent downstream errors            
            if (isUserDefined) {
                DriftManagerBean.validateDriftDefinition(definition);
            }

            ResourceType resourceType = resourceTypeMgr.getResourceTypeById(subject, resourceTypeId);
            DriftDefinitionTemplate template = new DriftDefinitionTemplate();
            template.setName(definition.getName());
            template.setDescription(definition.getDescription());
            template.setUserDefined(isUserDefined);
            template.setTemplateDefinition(definition);

            if (isDuplicateName(resourceType, template)) {
                throw new IllegalArgumentException("Drift definition template name must be unique. A template named "
                    + "[" + template.getName() + "] already exists for " + resourceType);
            }

            resourceType.addDriftDefinitionTemplate(template);
            entityMgr.persist(template);

            return template;
        } catch (ResourceTypeNotFoundException e) {
            throw new RuntimeException("Failed to create template", e);
        }
    }

    private boolean isDuplicateName(ResourceType resourceType, DriftDefinitionTemplate newTemplate) {
        for (DriftDefinitionTemplate template : resourceType.getDriftDefinitionTemplates()) {
            if (template.getName().equals(newTemplate.getName())) {
                return true;
            }
        }
        return false;
    }

    @RequiredPermission(Permission.MANAGE_SETTINGS)
    @Override
    @TransactionAttribute(NEVER)
    public void pinTemplate(Subject subject, int templateId, int driftDefId, int snapshotVersion) {
        templateMgr.createTemplateChangeSet(subject, templateId, driftDefId, snapshotVersion);

        DriftDefinitionTemplateCriteria templateCriteria = new DriftDefinitionTemplateCriteria();
        templateCriteria.addFilterId(templateId);
        templateCriteria.fetchDriftDefinitions(true);

        PageList<DriftDefinitionTemplate> templates = templateMgr.findTemplatesByCriteria(subject, templateCriteria);
        DriftDefinitionTemplate template = templates.get(0);

        DriftDefinitionCriteria criteria = new DriftDefinitionCriteria();
        criteria.addFilterTemplateId(templateId);
        criteria.fetchConfiguration(true);
        criteria.fetchResource(true);

        PageList<DriftDefinition> definitions = driftMgr.findDriftDefinitionsByCriteria(subject, criteria);
        for (DriftDefinition def : definitions) {
            if (def.isAttached()) {
                int resourceId = def.getResource().getId();
                driftMgr.deleteDriftDefinition(subject, forResource(resourceId), def.getName());

                DriftDefinition newDef = new DriftDefinition(def.getConfiguration().deepCopyWithoutProxies());
                newDef.setTemplate(template);
                newDef.setPinned(true);

                driftMgr.updateDriftDefinition(subject, forResource(resourceId), newDef);
            }
        }
    }

    @Override
    @TransactionAttribute
    public void createTemplateChangeSet(Subject subject, int templateId, int driftDefId, int snapshotVersion) {
        DriftDefinitionTemplate template = entityMgr.find(DriftDefinitionTemplate.class, templateId);
        DriftSnapshot snapshot = driftMgr.getSnapshot(subject, new DriftSnapshotRequest(driftDefId, snapshotVersion));

        DriftChangeSetDTO changeSetDTO = new DriftChangeSetDTO();
        changeSetDTO.setCategory(COVERAGE);
        changeSetDTO.setDriftHandlingMode(normal);
        changeSetDTO.setVersion(0);

        String newChangeSetId = driftMgr.persistSnapshot(subject, snapshot, changeSetDTO);
        template.setChangeSetId(newChangeSetId);
    }

    @RequiredPermission(Permission.MANAGE_SETTINGS)
    @Override
    public void deleteTemplate(Subject subject, int templateId) {
        DriftDefinitionTemplate template = entityMgr.find(DriftDefinitionTemplate.class, templateId);
        for (DriftDefinition defintion : template.getDriftDefinitions()) {
            if (defintion.isAttached()) {
                driftMgr.deleteDriftDefinition(subject, forResource(defintion.getResource().getId()), defintion
                    .getName());
            } else {
                defintion.setTemplate(null);
            }
        }
        entityMgr.remove(template);
    }

    @RequiredPermission(Permission.MANAGE_SETTINGS)
    @Override
    public void updateTemplate(Subject subject, DriftDefinitionTemplate updatedTemplate) {

        DriftDefinitionTemplate template = entityMgr.find(DriftDefinitionTemplate.class, updatedTemplate.getId());

        if (null == template) {
            throw new IllegalArgumentException("Template with id [" + updatedTemplate.getId() + "] not found");
        }
        if (!template.isUserDefined()) {
            throw new IllegalArgumentException("Plugin-defined templates cannot be be modified");
        }
        if (!template.getName().equals(updatedTemplate.getName())) {
            throw new IllegalArgumentException("The template's name cannot be modified");
        }
        DriftDefinitionComparator comparator = new DriftDefinitionComparator(
            DriftDefinitionComparator.CompareMode.ONLY_DIRECTORY_SPECIFICATIONS);
        if (comparator.compare(template.getTemplateDefinition(), updatedTemplate.getTemplateDefinition()) != 0) {
            throw new IllegalArgumentException("The template's base directory and filters cannot be modified");
        }

        template.setTemplateDefinition(updatedTemplate.getTemplateDefinition());
        template = entityMgr.merge(template);
        DriftDefinition templateDef = template.getTemplateDefinition();

        for (DriftDefinition resourceDef : template.getDriftDefinitions()) {
            if (resourceDef.isAttached()) {
                resourceDef.setInterval(templateDef.getInterval());
                resourceDef.setDriftHandlingMode(templateDef.getDriftHandlingMode());
                resourceDef.setEnabled(templateDef.isEnabled());

                driftMgr.updateDriftDefinition(subject, forResource(resourceDef.getResource().getId()), resourceDef);
            }
        }
    }
}
