package de.lcs.wep.service.update;

import de.lcs.wep.calendar.Event;
import de.lcs.wep.calendar.Resource;
import de.lcs.wep.rest.response.LabelInfo;
import de.lcs.wep.service.WePService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Purpose of this class is to only update WePs when necessary. This means not every time calendar data is received
 * a WeP is updated. Instead the calendar data is compared to previously received calendar data. Only when the content
 * of this data has changed the related WePs are updated.
 * <p/>
 * Relation:<br>
 * A calendar resource's summary/name field is handled as tag. Each WeP is associated with a list of tags, but only
 * it's first one is used in this scenario.<br/><br/>
 * <i>Resource <--- 1..1 ---> Tag <--- 1..n --> WeP</i><br>
 * <p/>
 *
 * @author sgreve on 31.03.2015.
 */
public class UpdateManager {

    private static Logger logger = LogManager.getLogger(UpdateManager.class);

    /**
     * tag => calendarName
     */
    private Map<String, Resource> tagToCalendar;
    /**
     * tag => list of LabelInfos
     */
    private Map<String, TagUpdater> tagToUpdater;

    public UpdateManager() {
        this.tagToCalendar = new HashMap<String, Resource>();
        this.tagToUpdater = new HashMap<String, TagUpdater>();
    }

    public void update(Set<Resource> calendars, List<LabelInfo> labelInfos, WePService service) {

        // create tags from calendar names and associate them with the specific calendar
        logger.debug("generating tags");
        extractTags(calendars);
        extractTags(labelInfos);
        update(service);
    }

    private void update(WePService service) {
        for (String tag : tagToUpdater.keySet()) {
            Resource calendar = tagToCalendar.get(tag);
            if (calendar == null) {
                logger.warn("Skipping update of tag '" + tag + "' as there is no calendar with matching name available");
            } else {
                TagUpdater tagUpdater = tagToUpdater.get(tag);
                List<Event> events = calendar.getEvents();
                try {
                    String roomName = calendar.getName();
                    tagUpdater.update(roomName, events, service);
                } catch (Exception e) {
                    logger.error("Error in update of tag '" + tag + "': " + e);
                }
            }
        }
    }


    /**
     * Extracts the tag-name for each calendar and puts it into the tagToCalendar-Mapping.
     *
     * @param calendars
     */
    private void extractTags(Set<Resource> calendars) {
        // create the association from tag name to calendar
        for (Resource cal : calendars) {
            try {
                String tag = calendarToTag(cal);
                logger.debug("Extracted tag '" + tag + "' from calendar: " + cal);

                if (tag != null) {
                    if (!tagToCalendar.containsKey(tag)) {
                        tagToCalendar.put(tag, cal);
                    }
                }
            } catch (Exception ex) {
                logger.warn("Couldn't update tag name: " + ex.getMessage());
            }
        }
    }

    /**
     * Transforms a calendar's name into a valid tag. This means everything is turned into upper case letter and spaces
     * are replaced by underscore characters.
     *
     * @param calendar
     * @return the calendar name represented as tag
     */
    private String calendarToTag(Resource calendar) {
        String tag = null;

        if (calendar.getName() == null || calendar.getName().isEmpty()) {
            logger.warn("Calendar name is not set for calendar with id: " + calendar.getID() + " - skipping calendar");
        } else {
            tag = calendar.getName().replace(' ', '_').toUpperCase();
        }

        return tag;
    }

    /**
     * Associates a tag with a list of Labels. <i>Attention: only the first tag per Label is used</i>
     *
     * @param labelInfos
     */
    private void extractTags(List<LabelInfo> labelInfos) {
        for (LabelInfo li : labelInfos) {
            // only handle WeP when it has a tag
            if (li.getTags() != null && li.getTags().size() > 0) {
                // only use the first tag
                String tag = li.getTags().get(0);
                // retrieve or create the TagUpdater
                TagUpdater tagUpdater = getTagUpdater(tag);
                // remove the label if tag has changed
                if (!tagUpdater.getTag().equals(tag)) {
                    logger.debug("Removing label '"+li+"' from tagUpdater for tag: " + tagUpdater.getTag());
                    removeLabel(li);
                }
                // add the label to the correct tagUpdater
                tagUpdater.addLabel(li);
                tagToUpdater.put(tag, tagUpdater);
            }
        }
    }

    /**
     * gets the existing {@link TagUpdater} for the passed tag or returns a newly created instance
     *
     * @param tag
     * @return
     */
    private TagUpdater getTagUpdater(String tag) {
        TagUpdater result;
        boolean exists = tagToUpdater.containsKey(tag);
        logger.debug("does a tagUpdater for tag '" + tag + "' exist: " + exists);

        if (exists) {
            result = tagToUpdater.get(tag);
        } else {
            result = new TagUpdater(tag);
        }
        return result;
    }

    /**
     * Removes the passed label from all existing {@link TagUpdater}s
     *
     * @param li
     */
    private void removeLabel(LabelInfo li) {
        for (String tag : tagToUpdater.keySet()) {
            TagUpdater tup = tagToUpdater.get(tag);
            tup.removeLabel(li);
        }
    }


}
