/*
 * 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.TagSoupExtractionResult;
import org.apache.any23.extractor.html.HTMLDocument;
import org.apache.any23.vocab.HCard;
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.EntityBasedMicroformatExtractor;
import org.apache.any23.extractor.html.DomUtils;

import java.util.List;


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

    private static final HCard vCARD = HCard.getInstance();

    private static final String[] cardFields = {
            "name",
            "honorific-prefix",
            "given-name",
            "additional-name",
            "family-name",
            "sort-string",
            "honorific-suffix",
            "nickname",
            "email",
            "logo",
            "photo",
            "url",
            "uid",
            "category",
            "tel",
            "note",
            "bday",
            "key",
            "org",
            "job-title",
            "role",
            "impp",
            "sex",
            "gender-identity",
            "anniversary",
            "adr",
            "geo"
    };

    private static final String[] addressFields = {
            "street-address",
            "extended-address",
            "locality",
            "region",
            "postal-code",
            "country-name",
            "geo"
    };

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



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

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

    @Override
    protected void resetExtractor() {
        //empty
    }

    @Override
    protected boolean extractEntity(Node node, ExtractionResult out) throws ExtractionException {
        final BNode card = getBlankNodeFor(node);
        conditionallyAddResourceProperty(card, RDF.TYPE, vCARD.Card);
        final HTMLDocument fragment = new HTMLDocument(node);
        addName(fragment, card);
        addHonorificPrefix(fragment, card);
        addGivenName(fragment, card);
        addAdditionalName(fragment, card);
        addFamilyName(fragment, card);
        addSortString(fragment, card);
        addHonorificSuffix(fragment, card);
        addNickname(fragment, card);
        addEmails(fragment, card);
        addLogo(fragment, card);
        addPhoto(fragment, card);
        addURLs(fragment, card);
        addUID(fragment, card);
        addCategories(fragment, card);
        addTelephones(fragment, card);
        addNotes(fragment, card);
        addBday(fragment, card);
        addKey(fragment, card);
        addOrg(fragment, card);
        addJobTitle(fragment, card);
        addRole(fragment, card);
        addImpp(fragment, card);
        addSex(fragment, card);
        addGenderIdentity(fragment, card);
        addAnniversary(fragment, card);
        addGeo(fragment, card);
        addAdr(fragment, card);
        final TagSoupExtractionResult tser = (TagSoupExtractionResult) out;
        tser.addResourceRoot( DomUtils.getXPathListForNode(node), card, this.getClass() );
        return true;
    }

    public Resource extractEntityAsEmbeddedProperty(HTMLDocument fragment, BNode card,
                                                    ExtractionResult out)
            throws ExtractionException {
        this.setCurrentExtractionResult(out);
        addName(fragment, card);
        addHonorificPrefix(fragment, card);
        addGivenName(fragment, card);
        addAdditionalName(fragment, card);
        addFamilyName(fragment, card);
        addSortString(fragment, card);
        addHonorificSuffix(fragment, card);
        addNickname(fragment, card);
        addEmails(fragment, card);
        addLogo(fragment, card);
        addPhoto(fragment, card);
        addURLs(fragment, card);
        addUID(fragment, card);
        addCategories(fragment, card);
        addTelephones(fragment, card);
        addNotes(fragment, card);
        addBday(fragment, card);
        addKey(fragment, card);
        addOrg(fragment, card);
        addJobTitle(fragment, card);
        addRole(fragment, card);
        addImpp(fragment, card);
        addSex(fragment, card);
        addGenderIdentity(fragment, card);
        addAnniversary(fragment, card);
        addGeo(fragment, card);
        addAdr(fragment, card);
        return card;
    }



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

    private void addName(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[0], vCARD.name);
    }

    private void addHonorificPrefix(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[1], vCARD.honorific_prefix);
    }

    private void addGivenName(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[2], vCARD.given_name);
    }

    private void addAdditionalName(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[3], vCARD.additional_name);
    }

    private void addFamilyName(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[4], vCARD.family_name);
    }

    private void addSortString(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[5], vCARD.sort_string);
    }

    private void addHonorificSuffix(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[6], vCARD.honorific_suffix);
    }

    private void addNickname(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[7], vCARD.nickname);
    }

    private void addEmails(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField[] emails = fragment.getPluralUrlField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[8]);
        for(HTMLDocument.TextField email : emails) {
            addIRIProperty(card, vCARD.email, fragment.resolveIRI(email.value()));

        }
    }

    private void addLogo(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField logo = fragment.getSingularUrlField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[9]);
        if(logo.source()==null)
            return;
        addIRIProperty(card, vCARD.logo, fragment.resolveIRI(logo.value()));
    }

    private void addPhoto(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField photo = fragment.getSingularUrlField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[10]);
        if(photo.source()==null)
            return;
        addIRIProperty(card, vCARD.photo, fragment.resolveIRI(photo.value()));
    }

    private void addURLs(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField[] urls = fragment.getPluralUrlField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[11]);
        for(HTMLDocument.TextField url : urls) {
            addIRIProperty(card, vCARD.url, fragment.resolveIRI(url.value()));

        }
    }

    private void addUID(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField uid = fragment.getSingularUrlField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[12]);
        if(uid.source()==null)
            return;
        addIRIProperty(card, vCARD.uid, fragment.resolveIRI(uid.value()));
    }


    private void addCategories(HTMLDocument fragment, BNode entry) {
        final HTMLDocument.TextField[] categories = fragment.getPluralTextField
                (Microformats2Prefixes.PROPERTY_PREFIX + cardFields[13]);
        for (HTMLDocument.TextField category : categories) {
            conditionallyAddStringProperty(
                    category.source(), entry, vCARD.category, category.value()
            );
        }
    }

    private void addTelephones(HTMLDocument fragment, BNode card) {
        final HTMLDocument.TextField[] telephones = fragment.getPluralTextField
                (Microformats2Prefixes.PROPERTY_PREFIX + cardFields[14]);
        for (HTMLDocument.TextField tel : telephones) {
            Node attribute=tel.source().getAttributes().getNamedItem("value");
            if (attribute==null){
                conditionallyAddStringProperty(
                        tel.source(), card, vCARD.tel, tel.value()
                );
            }else{
                conditionallyAddStringProperty(
                        tel.source(), card, vCARD.tel, attribute.getNodeValue()
                );
            }
        }
    }

    private void addNotes(HTMLDocument fragment, BNode entry) {
        final HTMLDocument.TextField[] categories = fragment.getPluralTextField
                (Microformats2Prefixes.PROPERTY_PREFIX + cardFields[15]);
        for (HTMLDocument.TextField category : categories) {
            conditionallyAddStringProperty(
                    category.source(), entry, vCARD.note, category.value()
            );
        }
    }

    private void addBday(HTMLDocument fragment, BNode card) {
        final HTMLDocument.TextField bday = fragment.getSingularTextField(
                Microformats2Prefixes.TIME_PROPERTY_PREFIX + cardFields[16]);
        if (bday.source() == null)
            return;

        Node attribute = bday.source().getAttributes().getNamedItem("datetime");
        if (attribute == null) {
            conditionallyAddStringProperty(
                    bday.source(),
                    card, vCARD.bday, bday.value()
            );
        } else {
            conditionallyAddStringProperty(
                    bday.source(),
                    card, vCARD.bday, attribute.getNodeValue()
            );

        }
    }

    private void addKey(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField uid = fragment.getSingularTextField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[17]);
        if(uid.source()==null)
            return;
        addIRIProperty(card, vCARD.key, fragment.resolveIRI(uid.value()));
    }

    private void addOrg(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[18], vCARD.org);
    }

    private void addJobTitle(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[19], vCARD.job_title);
    }

    private void addRole(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[20], vCARD.role);
    }

    private void addImpp(HTMLDocument fragment, BNode card) throws ExtractionException {
        final HTMLDocument.TextField impp = fragment.getSingularTextField
                (Microformats2Prefixes.URL_PROPERTY_PREFIX + cardFields[21]);
        if(impp.source()==null)
            return;
        addIRIProperty(card, vCARD.impp, fragment.resolveIRI(impp.value()));
    }

    private void addSex(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[22], vCARD.sex);
    }

    private void addGenderIdentity(HTMLDocument fragment, BNode card) {
        mapFieldWithProperty(fragment, card, Microformats2Prefixes.PROPERTY_PREFIX +
                cardFields[23], vCARD.gender_identity);
    }


    private void addAnniversary(HTMLDocument fragment, BNode card) {
        final HTMLDocument.TextField anniversary = fragment.getSingularTextField(
                Microformats2Prefixes.TIME_PROPERTY_PREFIX + cardFields[24]);
        if (anniversary.source() == null)
            return;

        Node attribute = anniversary.source().getAttributes().getNamedItem("datetime");
        if (attribute == null) {
            conditionallyAddStringProperty(
                    anniversary.source(),
                    card, vCARD.bday, anniversary.value()
            );
        } else {
            conditionallyAddStringProperty(
                    anniversary.source(),
                    card, vCARD.bday, attribute.getNodeValue()
            );

        }
    }

    private void addAdr(HTMLDocument doc, Resource card) throws ExtractionException {
        List<Node> nodes = doc.findAllByClassName(Microformats2Prefixes.PROPERTY_PREFIX + cardFields[25] +
                Microformats2Prefixes.SPACE_SEPARATOR + Microformats2Prefixes.CLASS_PREFIX + cardFields[25]);
        if (nodes.isEmpty())
            return;
        for (Node node : nodes) {
            BNode location = valueFactory.createBNode();
            addIRIProperty(location, RDF.TYPE, vCARD.Address);
            HTMLDocument fragment = new HTMLDocument(node);
            for (String field : addressFields) {
                HTMLDocument.TextField[] values = fragment.getPluralTextField(Microformats2Prefixes.PROPERTY_PREFIX+field);
                for (HTMLDocument.TextField val : values) {
                    if(!field.equals("geo")) {
                        conditionallyAddStringProperty(
                                val.source(),
                                location, vCARD.getProperty(field), val.value()
                        );
                    }else {
                        addGeo(new HTMLDocument(node),card);
                    }
                }
            }
        }
    }

    private void addGeo(HTMLDocument doc, Resource card) throws ExtractionException {
        List<Node> nodes = doc.findAllByClassName(Microformats2Prefixes.PROPERTY_PREFIX + cardFields[26] +
                Microformats2Prefixes.SPACE_SEPARATOR + Microformats2Prefixes.CLASS_PREFIX + cardFields[26]);
        if (nodes.isEmpty())
            return;
        for (Node node : nodes) {
            BNode location = valueFactory.createBNode();
            addIRIProperty(location, RDF.TYPE, vCARD.Geo);
            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, vCARD.getProperty(field), val.value()
                        );
                    }else{
                        conditionallyAddStringProperty(
                                val.source(),
                                location, vCARD.getProperty(field), attribute.getNodeValue()
                        );
                    }
                }
            }
        }
    }

}
