/******************************************************************************
 * Copyright (C) 2010-2016 CERN. All rights not expressly granted are reserved.
 * 
 * This file is part of the CERN Control and Monitoring Platform 'C2MON'.
 * C2MON is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the license.
 * 
 * C2MON 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 Lesser General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with C2MON. If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/
package cern.c2mon.server.configuration.handler.transacted;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import cern.c2mon.server.cache.AliveTimerCache;
import cern.c2mon.server.cache.CommFaultTagCache;
import cern.c2mon.server.cache.ControlTagCache;
import cern.c2mon.server.cache.ControlTagFacade;
import cern.c2mon.server.cache.ProcessCache;
import cern.c2mon.server.cache.ProcessXMLProvider;
import cern.c2mon.server.cache.SubEquipmentCache;
import cern.c2mon.server.cache.SubEquipmentFacade;
import cern.c2mon.server.cache.loading.SubEquipmentDAO;
import cern.c2mon.server.common.control.ControlTag;
import cern.c2mon.server.common.control.ControlTagCacheObject;
import cern.c2mon.server.common.subequipment.SubEquipment;
import cern.c2mon.server.common.subequipment.SubEquipmentCacheObject;
import cern.c2mon.server.configuration.handler.ControlTagConfigHandler;
import cern.c2mon.server.configuration.impl.ProcessChange;
import cern.c2mon.shared.client.configuration.ConfigConstants.Action;
import cern.c2mon.shared.client.configuration.ConfigConstants.Entity;
import cern.c2mon.shared.client.configuration.ConfigurationElement;
import cern.c2mon.shared.client.configuration.ConfigurationElementReport;
import cern.c2mon.shared.common.ConfigurationException;
import cern.c2mon.shared.daq.config.DataTagAdd;
import cern.c2mon.shared.daq.config.IChange;
import cern.c2mon.shared.daq.config.SubEquipmentUnitAdd;
import cern.c2mon.shared.daq.config.SubEquipmentUnitRemove;

/**
 * See interface docs.
 *
 * @author Mark Brightwell
 *
 */
@Service
public class SubEquipmentConfigTransactedImpl extends AbstractEquipmentConfigTransacted<SubEquipment> implements SubEquipmentConfigTransacted {

  /**
   * Class logger.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(SubEquipmentConfigTransactedImpl.class);

  /**
   * Facade.
   */
  private final SubEquipmentFacade subEquipmentFacade;

  /**
   * DAO.
   */
  private final SubEquipmentDAO subEquipmentDAO;

  private final ControlTagCache controlCache;
  
  private final ControlTagFacade controlTagFacade;

  private final ProcessXMLProvider processXMLProvider;

  /**
   * Autowired constructor.
   */
  @Autowired
  public SubEquipmentConfigTransactedImpl(ControlTagConfigHandler controlTagConfigHandler,
                                          SubEquipmentFacade subEquipmentFacade,
                                          SubEquipmentCache subEquipmentCache,
                                          SubEquipmentDAO subEquipmentDAO,
                                          AliveTimerCache aliveTimerCache,
                                          CommFaultTagCache commFaultTagCache,
                                          ProcessCache processCache,
                                          ProcessXMLProvider processXMLProvider,
                                          ControlTagCache controlCache, 
                                          ControlTagFacade controlTagFacade) {
    super(controlTagConfigHandler, subEquipmentFacade, subEquipmentCache, subEquipmentDAO, aliveTimerCache, commFaultTagCache);
    this.subEquipmentFacade = subEquipmentFacade;
    this.subEquipmentDAO = subEquipmentDAO;
    this.processXMLProvider = processXMLProvider;
    this.controlCache = controlCache;
    this.controlTagFacade = controlTagFacade;
  }

  /**
   * Creates the SubEquipment cache object and puts it into the cache and DB.
   * The alive and commfault tag caches are updated also. The Equipment cache
   * object is updated to include the new SubEquipment.
   *
   * @param element details of configuration
   * @throws IllegalAccessException should not be thrown here (in common
   *           interface for Tags)
   */
  @Override
  @Transactional(value = "cacheTransactionManager")
  public List<ProcessChange> doCreateSubEquipment(final ConfigurationElement element) throws IllegalAccessException {
    SubEquipment subEquipment = super.createAbstractEquipment(element);
    subEquipmentFacade.addSubEquipmentToEquipment(subEquipment.getId(), subEquipment.getParentId());
    SubEquipmentUnitAdd subEquipmentUnitAdd = new SubEquipmentUnitAdd(element.getSequenceId(), subEquipment.getId(), subEquipment.getParentId(),
    processXMLProvider.getSubEquipmentConfigXML((SubEquipmentCacheObject) subEquipment));
    
    List<ProcessChange> changes = new ArrayList<ProcessChange>();
    changes.add(new ProcessChange(subEquipmentFacade.getProcessIdForAbstractEquipment(subEquipment.getId()), subEquipmentUnitAdd));
    changes.addAll(updateControlTagInformation(element, subEquipment));
    
    return changes;
  }

  @Override
  @Transactional(value = "cacheTransactionManager", propagation = Propagation.REQUIRES_NEW)
  public List<ProcessChange> doUpdateAbstractEquipment(final SubEquipment subEquipment, Properties properties) throws IllegalAccessException {
    return super.updateAbstractEquipment(subEquipment, properties);
  }

  @Override
  @Transactional(value = "cacheTransactionManager", propagation = Propagation.REQUIRES_NEW)
  public List<ProcessChange> doRemoveSubEquipment(final SubEquipment subEquipment, final ConfigurationElementReport subEquipmentReport) {
    List<ProcessChange> processChanges = new ArrayList<ProcessChange>();

    try {
      subEquipmentDAO.deleteItem(subEquipment.getId());
      Long processId = this.subEquipmentFacade.getProcessIdForAbstractEquipment(subEquipment.getId());

      SubEquipmentUnitRemove subEquipmentUnitRemove = new SubEquipmentUnitRemove(0L, subEquipment.getId(), subEquipment.getParentId());
      processChanges.add(new ProcessChange(processId, subEquipmentUnitRemove));

    } catch (RuntimeException e) {
      subEquipmentReport.setFailure("Rolling back removal of sub-equipment " + subEquipment.getId());
      throw new UnexpectedRollbackException("Exception caught while removing Sub-equipment from DB: rolling back", e);
    }

    return processChanges;
  }
  
  
  /**
   * Ensures that the Alive-, Status- and CommFault Tags have appropriately the sub-equipment id set.
   * @param subEquipment The sub-equipment to which the control tags are assigned
   */
  private List<ProcessChange> updateControlTagInformation(final ConfigurationElement element, final SubEquipment subEquipment) {
      
      List<ProcessChange> changes = new ArrayList<ProcessChange>(3);
      final Long processId = subEquipmentFacade.getProcessIdForAbstractEquipment(subEquipment.getId());
      
      ControlTag aliveTagCopy = controlCache.getCopy(subEquipment.getAliveTagId());
      if (aliveTagCopy != null) {
        setSubEquipmentId((ControlTagCacheObject) aliveTagCopy, subEquipment.getId(), processId);
        
        if (aliveTagCopy.getAddress() != null) {
          // Inform Process about newly added alive tag
          IChange toAdd = new DataTagAdd(element.getSequenceId(), subEquipment.getId(), controlTagFacade.generateSourceDataTag(aliveTagCopy));
          ConfigurationElementReport report = new ConfigurationElementReport(Action.CREATE, Entity.CONTROLTAG, aliveTagCopy.getId());
          ProcessChange change = new ProcessChange(processId, toAdd);
          change.setNestedSubReport(report);
          changes.add(change);
        }
        else {
          throw new ConfigurationException(ConfigurationException.INVALID_PARAMETER_VALUE, 
              String.format("Alive tag #%d (%s) for sub-equipment #%d (%s) must by definition have a hardware address defined.", aliveTagCopy.getId(), aliveTagCopy.getName(), subEquipment.getId(), subEquipment.getName()));
        }
      
      } else {
        throw new ConfigurationException(ConfigurationException.INVALID_PARAMETER_VALUE, 
            String.format("No Alive tag (%s) found for sub-equipment #%d (%s).", subEquipment.getAliveTagId(), subEquipment.getId(), subEquipment.getName()));
      }
      
      
      ControlTag commFaultTagCopy = controlCache.getCopy(subEquipment.getCommFaultTagId());
      if (commFaultTagCopy != null) {
        setSubEquipmentId((ControlTagCacheObject) commFaultTagCopy, subEquipment.getId(), processId);
      } else {
        throw new ConfigurationException(ConfigurationException.INVALID_PARAMETER_VALUE, 
            String.format("No CommFault tag (%s) found for sub-equipment #%d (%s).", subEquipment.getCommFaultTagId(), subEquipment.getId(), subEquipment.getName()));
      }
      
      
      ControlTag statusTagCopy = controlCache.getCopy(subEquipment.getStateTagId());
      if (statusTagCopy != null) {
        setSubEquipmentId((ControlTagCacheObject) statusTagCopy, subEquipment.getId(), processId);
      } else {
        throw new ConfigurationException(ConfigurationException.INVALID_PARAMETER_VALUE, 
            String.format("No Status tag (%s) found for sub-equipment #%d (%s).", subEquipment.getStateTagId(), subEquipment.getId(), subEquipment.getName()));
      }
      
      return changes;
  }
  
  private void setSubEquipmentId(ControlTagCacheObject copy, Long subEquipmentId, Long processId) {
    String logMsg = String.format("Adding sub-equipment id #%s to control tag #%s", subEquipmentId, copy.getId()); 
    LOGGER.trace(logMsg);
    copy.setSubEquipmentId(subEquipmentId);
    copy.setProcessId(processId);
    controlCache.putQuiet(copy);
  }
  

}