/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.any23.extractor.html.microformats2;

import org.apache.any23.extractor.ExtractionException;
import org.apache.any23.extractor.ExtractionResult;
import org.apache.any23.extractor.ExtractorDescription;
import org.apache.any23.extractor.html.EntityBasedMicroformatExtractor;
import org.apache.any23.vocab.HEvent;
import org.apache.any23.vocab.VCard;
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.w3c.dom.Node;
import org.apache.any23.extractor.html.HTMLDocument;

import java.util.List;

import static org.apache.any23.extractor.html.HTMLDocument.TextField;

/**
 * Extractor for the <a href="http://microformats.org/wiki/h-event">h-event</a> microformat.
 *
 * @author Nisala Nirmana
 */
public class HEventExtractor extends EntityBasedMicroformatExtractor {

    private static final HEvent vEvent = HEvent.getInstance();
    private static final VCard vVCARD = VCard.getInstance();

    private String[] eventFields = { "name", "summary", "start", "end", "duration", "description", "url", "category",
            "location", "attendee" };

    private static final String[] geoFields = { "latitude", "longitude", "altitude" };

    @Override
    public ExtractorDescription getDescription() {
        return HEventExtractorFactory.getDescriptionInstance();
    }

    @Override
    protected String getBaseClassName() {
        return Microformats2Prefixes.CLASS_PREFIX + "event";
    }

    @Override
    protected void resetExtractor() {
        // Empty.
    }

    @Override
    protected boolean extractEntity(Node node, ExtractionResult out) throws ExtractionException {
        final BNode event = getBlankNodeFor(node);
        conditionallyAddResourceProperty(event, RDF.TYPE, vEvent.event);
        final HTMLDocument fragment = new HTMLDocument(node);
        addName(fragment, event);
        addSummary(fragment, event);
        addStart(fragment, event);
        addEnd(fragment, event);
        addDuration(fragment, event);
        addDescription(fragment, event);
        addURLs(fragment, event);
        addCategories(fragment, event);
        addLocations(fragment, event);
        addAttendees(fragment, event);
        return true;
    }

    public Resource extractEntityAsEmbeddedProperty(HTMLDocument fragment, BNode event, ExtractionResult out)
            throws ExtractionException {
        this.setCurrentExtractionResult(out);
        addName(fragment, event);
        addSummary(fragment, event);
        addStart(fragment, event);
        addEnd(fragment, event);
        addDuration(fragment, event);
        addDescription(fragment, event);
        addURLs(fragment, event);
        addCategories(fragment, event);
        addLocations(fragment, event);
        addAttendees(fragment, event);
        return event;
    }

    private void addAttendees(HTMLDocument doc, Resource entry) throws ExtractionException {
        List<Node> nodes = doc.findAllByClassName(Microformats2Prefixes.PROPERTY_PREFIX + eventFields[9]
                + Microformats2Prefixes.SPACE_SEPARATOR + Microformats2Prefixes.CLASS_PREFIX + "card");
        if (nodes.isEmpty())
            return;
        HCardExtractorFactory factory = new HCardExtractorFactory();
        HCardExtractor extractor = factory.createExtractor();
        for (Node node : nodes) {
            BNode attendee = valueFactory.createBNode();
            addIRIProperty(attendee, RDF.TYPE, vEvent.attendee);
            extractor.extractEntityAsEmbeddedProperty(new HTMLDocument(node), attendee, getCurrentExtractionResult());
        }
    }

    private void mapFieldWithProperty(HTMLDocument fragment, BNode recipe, String fieldClass, IRI property) {
        HTMLDocument.TextField title = fragment.getSingularTextField(fieldClass);
        conditionallyAddStringProperty(title.source(), recipe, property, title.value());
    }

    private void addName(HTMLDocument fragment, BNode event) {
        mapFieldWithProperty(fragment, event, Microformats2Prefixes.PROPERTY_PREFIX + eventFields[0], vEvent.name);
    }

    private void addSummary(HTMLDocument fragment, BNode event) {
        mapFieldWithProperty(fragment, event, Microformats2Prefixes.PROPERTY_PREFIX + eventFields[1], vEvent.summary);
    }

    private void addStart(HTMLDocument fragment, BNode event) {
        final TextField start = fragment
                .getSingularTextField(Microformats2Prefixes.TIME_PROPERTY_PREFIX + eventFields[2]);
        if (start.source() == null)
            return;
        Node attribute = start.source().getAttributes().getNamedItem("datetime");
        if (attribute == null) {
            conditionallyAddStringProperty(start.source(), event, vEvent.start, start.value());
        } else {
            conditionallyAddStringProperty(start.source(), event, vEvent.start, attribute.getNodeValue());
        }
    }

    private void addEnd(HTMLDocument fragment, BNode event) {
        final TextField end = fragment
                .getSingularTextField(Microformats2Prefixes.TIME_PROPERTY_PREFIX + eventFields[3]);
        if (end.source() == null)
            return;
        Node attribute = end.source().getAttributes().getNamedItem("datetime");
        if (attribute == null) {
            conditionallyAddStringProperty(end.source(), event, vEvent.end, end.value());
        } else {
            conditionallyAddStringProperty(end.source(), event, vEvent.end, attribute.getNodeValue());
        }
    }

    private void addDuration(HTMLDocument fragment, BNode event) {
        final TextField duration = fragment
                .getSingularTextField(Microformats2Prefixes.TIME_PROPERTY_PREFIX + eventFields[4]);
        if (duration.source() == null)
            return;
        Node attribute = duration.source().getAttributes().getNamedItem("datetime");
        if (attribute == null) {
            conditionallyAddStringProperty(duration.source(), event, vEvent.duration, duration.value());
        } else {
            conditionallyAddStringProperty(duration.source(), event, vEvent.duration, attribute.getNodeValue());
        }
    }

    private void addDescription(HTMLDocument fragment, BNode event) {
        mapFieldWithProperty(fragment, event, Microformats2Prefixes.PROPERTY_PREFIX + eventFields[5],
                vEvent.description);
    }

    private void addURLs(HTMLDocument fragment, BNode event) throws ExtractionException {
        final HTMLDocument.TextField[] urls = fragment
                .getPluralUrlField(Microformats2Prefixes.URL_PROPERTY_PREFIX + eventFields[6]);
        for (HTMLDocument.TextField url : urls) {
            addIRIProperty(event, vEvent.url, fragment.resolveIRI(url.value()));
        }
    }

    private void addCategories(HTMLDocument fragment, BNode event) {
        final HTMLDocument.TextField[] categories = fragment
                .getPluralTextField(Microformats2Prefixes.PROPERTY_PREFIX + eventFields[7]);
        for (HTMLDocument.TextField category : categories) {
            conditionallyAddStringProperty(category.source(), event, vEvent.category, category.value());
        }
    }

    private void addLocations(HTMLDocument doc, Resource entry) throws ExtractionException {
        List<Node> nodes = doc.findAllByClassName(Microformats2Prefixes.PROPERTY_PREFIX + eventFields[8]
                + Microformats2Prefixes.SPACE_SEPARATOR + Microformats2Prefixes.CLASS_PREFIX + "geo");
        if (nodes.isEmpty())
            return;
        for (Node node : nodes) {
            BNode location = valueFactory.createBNode();
            addIRIProperty(location, RDF.TYPE, vEvent.location);
            HTMLDocument fragment = new HTMLDocument(node);
            for (String field : geoFields) {
                HTMLDocument.TextField[] values = fragment
                        .getPluralTextField(Microformats2Prefixes.PROPERTY_PREFIX + field);
                for (HTMLDocument.TextField val : values) {
                    Node attribute = val.source().getAttributes().getNamedItem("title");
                    if (attribute == null) {
                        conditionallyAddStringProperty(val.source(), location, vVCARD.getProperty(field), val.value());
                    } else {
                        conditionallyAddStringProperty(val.source(), location, vVCARD.getProperty(field),
                                attribute.getNodeValue());
                    }
                }
            }
        }
    }

}
