package au.gov.ga.geodesy.domain.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.commons.collections.ComparatorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import au.gov.ga.geodesy.domain.model.Clock;
import au.gov.ga.geodesy.domain.model.Equipment;
import au.gov.ga.geodesy.domain.model.EquipmentInUse;
import au.gov.ga.geodesy.domain.model.EquipmentRepository;
import au.gov.ga.geodesy.domain.model.Event;
import au.gov.ga.geodesy.domain.model.EventPublisher;
import au.gov.ga.geodesy.domain.model.EventRepository;
import au.gov.ga.geodesy.domain.model.EventSubscriber;
import au.gov.ga.geodesy.domain.model.GnssAntenna;
import au.gov.ga.geodesy.domain.model.GnssCorsSite;
import au.gov.ga.geodesy.domain.model.GnssCorsSiteRepository;
import au.gov.ga.geodesy.domain.model.GnssReceiver;
import au.gov.ga.geodesy.domain.model.Node;
import au.gov.ga.geodesy.domain.model.NodeRepository;
import au.gov.ga.geodesy.domain.model.Setup;
import au.gov.ga.geodesy.domain.model.SetupRepository;
import au.gov.ga.geodesy.domain.model.SiteUpdated;
import au.gov.ga.geodesy.igssitelog.domain.model.EffectiveDates;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

@Component
@Transactional("geodesyTransactionManager")
public class NodeService implements EventSubscriber<SiteUpdated> {

    private static final Logger log = LoggerFactory.getLogger(NodeService.class);

    private final List<Class<? extends Equipment>> significantEquipment =
        new ArrayList<>(Arrays.asList(
            GnssReceiver.class,
            GnssAntenna.class,
            Clock.class));

    @Autowired
    private EventPublisher eventPublisher;

    @Autowired
    private EventRepository events;

    @Autowired
    private GnssCorsSiteRepository gnssCorsSites;

    @Autowired
    private NodeRepository nodes;

    @Autowired
    private EquipmentRepository equipment;

    @Autowired
    private SetupRepository setups;

    @PostConstruct
    private void subcribe() {
        eventPublisher.subscribe(this);
    }

    public boolean canHandle(Event e) {
        return e != null && (e instanceof SiteUpdated);
    }

    public void handle(SiteUpdated siteUpdated) {
        log.info("Notified of " + siteUpdated);

        String fourCharId = siteUpdated.getFourCharacterId();
        GnssCorsSite site = gnssCorsSites.findByFourCharacterId(fourCharId);
        EffectiveDates nodePeriod = null;

        for (final Setup setup : setups.findBySiteId(site.getId())) {
            if (nodePeriod != null) {
                Date nodeEffectiveTo = nodePeriod.getTo();
                if (nodeEffectiveTo == null) {
                    break;
                }
                if (nodeEffectiveTo.compareTo(setup.getEffectivePeriod().getFrom()) == 1) {
                    continue;
                }
            }
            if (isCompleteCorsSetup(setup)) {
                List<EffectiveDates> dates = new ArrayList<>();
                for (Class<? extends Equipment> equipmentType : significantEquipment) {
                    EquipmentInUse inUse = getOneEquipmentInUse(setup, equipmentType);
                    if (inUse != null) {
                        dates.add(inUse.getPeriod());
                    }
                }
                nodePeriod = lcd(dates);
                nodes.save(new Node(site.getId(), nodePeriod, setup.getId()));
            }
        }
        eventPublisher.handled(siteUpdated);
        log.info("Saving nodes: " + fourCharId);
    }

    private Boolean isCompleteCorsSetup(Setup setup) {
        return getOneEquipmentInUse(setup, GnssReceiver.class) != null
            && getOneEquipmentInUse(setup, GnssAntenna.class) != null;
    }

    @SuppressWarnings("unchecked")
    private EffectiveDates lcd(List<EffectiveDates> es) {
        return new EffectiveDates(
            Collections.max(Lists.transform(es, new Function<EffectiveDates, Date>() {
                public Date apply(EffectiveDates dates) {
                    return dates == null ? null : dates.getFrom();
                }
            }), ComparatorUtils.nullLowComparator(ComparatorUtils.NATURAL_COMPARATOR))
            ,
            Collections.min(Lists.transform(es, new Function<EffectiveDates, Date>() {
                public Date apply(EffectiveDates dates) {
                    return dates == null ? null : dates.getTo();
                }
            }), ComparatorUtils.nullHighComparator(ComparatorUtils.NATURAL_COMPARATOR)));
    }

    private <T extends Equipment> EquipmentInUse getOneEquipmentInUse(Setup setup, Class<T> equipmentClass) {
        Collection<EquipmentInUse> inUse = getEquipmentInUse(setup, equipmentClass);
        if (inUse.isEmpty()) {
            return null;
        } else if (inUse.size() == 1) {
            return inUse.iterator().next();
        } else {
            System.out.println("Multiple " + equipmentClass.getSimpleName() + " in use!");
            return inUse.iterator().next();
        }
    }

    private <T extends Equipment> Collection<EquipmentInUse> getEquipmentInUse(Setup setup, final Class<T> equipmentClass) {
        return Collections2.filter(setup.getEquipmentInUse(), new Predicate<EquipmentInUse>() {
            public boolean apply(EquipmentInUse equipmentInUse) {
                Equipment e = equipment.findOne(equipmentInUse.getEquipmentId());
                return equipmentClass.isAssignableFrom(e.getClass());
            }
        });
    }
}


@@ -3999,24 +3999,134 @@
 up setup) %7B%0A
+        return hasReceiver(setup) && hasAntenna(setup);%0A    %7D%0A%0A    private Boolean hasReceiver(Setup setup) %7B%0A
         retu
@@ -4187,23 +4187,77 @@
 null
+;
 %0A    
+%7D%0A%0A
     
-    &&
+private Boolean hasAntenna(Setup setup) %7B%0A        return
  get

Tidy up NodeService.isCompleteCorssetup