package ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.validators;

import ru.yandex.common.util.collections.Cf;
import ru.yandex.common.util.collections.Pair;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.data.ComplexMicrodata;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.data.Microdata;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.exceptions.IslandsValidationMicrodataValidatorException;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.exceptions.MicrodataValidatorException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import static ru.yandex.common.util.collections.CollectionFactory.pair;

/**
 * Created by IntelliJ IDEA.
 * User: rasifiel
 * Date: 11.06.14
 * Time: 0:14
 */
public class IslandsBatchMicrodataValidator implements BatchMicrodataValidator {

    private final static Pattern CURRENCY_REGEXP = Pattern.compile("^[0-9]+([,\\.][0-9]+)?$");
    private static final Set<String> CURRENCY_SET =
            Cf.set("AFN", "EUR", "ALL", "DZD", "USD", "EUR", "AOA", "XCD", "XCD", "ARS", "AMD", "AWG", "AUD", "EUR",
                    "AZN", "BSD", "BHD", "BDT", "BBD", "BYR", "EUR", "BZD", "XOF", "BMD", "BTN", "INR", "BOB", "BOV",
                    "USD", "BAM", "BWP", "NOK", "BRL", "USD", "BND", "BGN", "XOF", "BIF", "KHR", "XAF", "CAD", "CVE",
                    "KYD", "XAF", "XAF", "CLF", "CLP", "CNY", "AUD", "AUD", "COP", "COU", "KMF", "XAF", "CDF", "NZD",
                    "CRC", "XOF", "HRK", "CUC", "CUP", "ANG", "EUR", "CZK", "DKK", "DJF", "XCD", "DOP", "USD", "EGP",
                    "SVC", "USD", "XAF", "ERN", "EUR", "ETB", "EUR", "FKP", "DKK", "FJD", "EUR", "EUR", "EUR", "XPF",
                    "EUR", "XAF", "GMD", "GEL", "EUR", "GHS", "GIP", "EUR", "DKK", "XCD", "EUR", "USD", "GTQ", "GBP",
                    "GNF", "XOF", "GYD", "HTG", "USD", "AUD", "EUR", "HNL", "HKD", "HUF", "ISK", "INR", "IDR", "XDR",
                    "IRR", "IQD", "EUR", "GBP", "ILS", "EUR", "JMD", "JPY", "GBP", "JOD", "KZT", "KES", "AUD", "KPW",
                    "KRW", "KWD", "KGS", "LAK", "EUR", "LBP", "LSL", "ZAR", "LRD", "LYD", "CHF", "LTL", "EUR", "MOP",
                    "MKD", "MGA", "MWK", "MYR", "MVR", "XOF", "EUR", "USD", "EUR", "MRO", "MUR", "EUR", "XUA", "MXN",
                    "MXV", "USD", "MDL", "EUR", "MNT", "EUR", "XCD", "MAD", "MZN", "MMK", "NAD", "ZAR", "AUD", "NPR",
                    "EUR", "XPF", "NZD", "NIO", "XOF", "NGN", "NZD", "AUD", "USD", "NOK", "OMR", "PKR", "USD", "PAB",
                    "USD", "PGK", "PYG", "PEN", "PHP", "NZD", "PLN", "EUR", "USD", "QAR", "EUR", "RON", "RUB", "RWF",
                    "EUR", "SHP", "XCD", "XCD", "EUR", "EUR", "XCD", "WST", "EUR", "STD", "SAR", "XOF", "RSD", "SCR",
                    "SLL", "SGD", "ANG", "XSU", "EUR", "EUR", "SBD", "SOS", "ZAR", "SSP", "EUR", "LKR", "SDG", "SRD",
                    "NOK", "SZL", "SEK", "CHE", "CHF", "CHW", "SYP", "TWD", "TJS", "TZS", "THB", "USD", "XOF", "NZD",
                    "TOP", "TTD", "TND", "TRY", "TMT", "USD", "AUD", "UGX", "UAH", "AED", "GBP", "USD", "USN", "USD",
                    "UYI", "UYU", "UZS", "VUV", "VEF", "VND", "USD", "USD", "XPF", "MAD", "YER", "ZMW", "ZWL", "XBA",
                    "XBB", "XBC", "XBD", "XTS", "XXX", "XAU", "XPD", "XPT", "XAG");

    private static Pattern start_with_schema = Pattern.compile("http?://(www.)?schema.org.*");
    private static Pattern item_list_pattern = Pattern.compile("http?://(www.)?schema.org/ItemList");
    private static Pattern offer_pattern = Pattern.compile("http?://(www.)?schema.org/Offer");
    @Override
    public List<MicrodataValidatorException> validate(final List<Microdata> microdata) {
        List<Pair<String, ComplexMicrodata>> typeCardList = new ArrayList<Pair<String, ComplexMicrodata>>();
        for (final Microdata card : microdata) {
            if (card instanceof ComplexMicrodata) {
                String type = ((ComplexMicrodata) card).getType();
                if (type != null && start_with_schema.matcher(type).matches()) {
                    if (item_list_pattern.matcher(type).matches()) {
                        for (final Microdata c : ((ComplexMicrodata) card).getPropAsList("itemListElement")) {
                            if (c instanceof ComplexMicrodata) {
                                String ctype = ((ComplexMicrodata) c).getType();
                                if (ctype != null && start_with_schema.matcher(ctype).matches()) {
                                    if (!item_list_pattern.matcher(ctype).matches()) {
                                        typeCardList.add(pair(ctype, (ComplexMicrodata) c));
                                    }
                                }
                            }
                        }
                    } else {
                        typeCardList.add(pair(type, (ComplexMicrodata) card));
                    }
                }
            }
        }
        String currentType = null;
        List<ComplexMicrodata> cards = new ArrayList<ComplexMicrodata>();

        for (final Pair<String, ComplexMicrodata> p : typeCardList) {
            if (!p.first.equals(currentType)) {
                if (cards.size() >= 3) {
                    return validatePack(cards);
                }
                currentType = p.second.getType();
                cards.clear();
            }
            cards.add(p.second);
        }
        if (cards.size() >= 3) {
            return validatePack(cards);
        }
        for (final Pair<String, ComplexMicrodata> p : typeCardList) {
            List<ComplexMicrodata> offers = new ArrayList<ComplexMicrodata>();
            for (final String key : p.second.getPropList()) {
                for (final Microdata m : p.second.getPropAsList(key)) {
                    if (m instanceof ComplexMicrodata) {
                        if(null!=((ComplexMicrodata) m).getType()) {
                            if (offer_pattern.matcher(((ComplexMicrodata) m).getType()).matches()) {
                                offers.add((ComplexMicrodata) m);
                            }
                        }
                    }
                }
            }
            final List<MicrodataValidatorException> exceptionList = new ArrayList<MicrodataValidatorException>();
            final String name = p.second.getFirstAsText("name");
            final String description = p.second.getFirstAsText("description");
            if (offers.isEmpty() && p.second.getFirst("propertiesList") == null) {
                final String islandType = "island_article";
                if (name != null) {
                    if (name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
                        exceptionList.add(
                                (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                        "field_wrong", false, "name", p.second).setRecurrentMessage(islandType));
                    }
                }
                if (description == null) {
                    exceptionList.add(
                            (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException("no_field",
                                    false, "description", p.second).setRecurrentMessage(islandType));
                } else if (description != null && description.length() <= 250) {
                    exceptionList.add((MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                            "short_field", false, "description$$250", p.second).setRecurrentMessage(islandType));
                }
                if (name != null) {
                    if (name.equals(description)) {
                        exceptionList.add(
                                (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                        "name_description_equals", false, "", p.second).setRecurrentMessage(
                                        islandType));
                    }
                }
                return exceptionList;
            } else {
                final String islandType = "island_object";
                if (name == null) {
                    exceptionList.add(
                            (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException("no_field",
                                    false, "name", p.second).setRecurrentMessage(islandType));
                }
                if (description == null && p.second.getFirst("image") == null) {
                    exceptionList.add((MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                            "description_or_image", false, "", p.second).setRecurrentMessage(islandType));
                }
                if (description == null && p.second.getFirst("propertiesList") == null) {
                    exceptionList.add((MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                            "description_or_properties", false, "", p.second).setRecurrentMessage(islandType));
                }
                if (name != null) {
                    if (name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
                        exceptionList.add(
                                (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                        "field_wrong", false, "name", p.second).setRecurrentMessage(islandType));
                    }
                }
                if (description != null && description.length() <= 100) {
                    exceptionList.add((MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                            "short_field", false, "description$$100", p.second).setRecurrentMessage(islandType));
                }
                if (name != null) {
                    if ((name.equals(description)) && (p.second.getFirst("propertiesList") == null)) {
                        exceptionList.add(
                                (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                        "no_properties", false, "", p.second).setRecurrentMessage(islandType));
                    }
                }
                if (!offers.isEmpty()) {
                    ComplexMicrodata offer = offers.get(0);
                    String price = offer.getFirstAsText("price");
                    String priceCurrency = offer.getFirstAsText("priceCurrency");
                    if (price == null || !CURRENCY_REGEXP.matcher(price.trim()).matches()) {
                        exceptionList.add(
                                (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                        "bad_price", false, "price", offer).setRecurrentMessage(islandType));
                    }
                    if (priceCurrency == null || !CURRENCY_SET.contains(priceCurrency.trim())) {
                        exceptionList.add(
                                (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                        "bad_price_currency", false, "price", offer).setRecurrentMessage(islandType));
                    }
                }
                return exceptionList;
            }
        }
        return Collections.EMPTY_LIST;
    }

    private List<MicrodataValidatorException> validatePack(final List<ComplexMicrodata> cards) {
        boolean allHaveImages = true;
        for (final ComplexMicrodata card : cards) {
            if (card.getFirst("image") == null || card.getFirst("name") == null) {
                allHaveImages = false;
                break;
            }
        }
        String islandType;
        if (allHaveImages) {
            islandType = "island_catalog_images";
        } else {
            islandType = "island_catalog";
        }
        return checkCatalog(cards, islandType);
    }

    private List<MicrodataValidatorException> checkCatalog(final List<ComplexMicrodata> cards, final String islandType) {
        final List<MicrodataValidatorException> exceptionList = new ArrayList<MicrodataValidatorException>();
        for (final ComplexMicrodata microdata : cards) {
            final String name = microdata.getFirstAsText("name");
            if (name != null && name.length() <= 5) {
                exceptionList.add(
                        (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException("short_field",
                                false, "name$$5", microdata).setRecurrentMessage(islandType));
            }
            if (name != null) {
                if (name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
                    exceptionList.add((MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                            "field_wrong", false, "name", microdata).setRecurrentMessage(islandType));
                }
            }
            final String description = microdata.getFirstAsText("description");
            if (description != null && description.length() <= 20) {
                exceptionList.add(
                        (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException("short_field",
                                false, "description$$20", microdata).setRecurrentMessage(islandType));
            }
            if (name != null) {
                if ((name.equals(description)) && (microdata.getFirst("propertiesList") == null)) {
                    exceptionList.add((MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                            "no_properties", false, "", microdata).setRecurrentMessage(islandType));
                }
            }
            List<ComplexMicrodata> offers = new ArrayList<ComplexMicrodata>();
            for (final String key : microdata.getPropList()) {
                for (final Microdata m : microdata.getPropAsList(key)) {
                    if (m instanceof ComplexMicrodata) {
                        String type = ((ComplexMicrodata) m).getType();
                        if (type != null && Pattern.compile("http?://schema.org/Offer").matcher(type).matches()) {
                            offers.add((ComplexMicrodata) m);
                        }
                    }
                }
            }
            if (!offers.isEmpty()) {
                ComplexMicrodata offer = offers.get(0);
                String price = offer.getFirstAsText("price");
                String priceCurrency = offer.getFirstAsText("priceCurrency");
                if (price == null || !CURRENCY_REGEXP.matcher(price.trim()).matches()) {
                    exceptionList.add(
                            (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                    "bad_price", false, "price", offer).setRecurrentMessage(islandType));
                }
                if (priceCurrency == null || !CURRENCY_SET.contains(priceCurrency.trim())) {
                    exceptionList.add(
                            (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException(
                                    "bad_price_currency", false, "price", offer).setRecurrentMessage(islandType));
                }
            }
            if (islandType.equals("island_catalog")) {
                if (description == null) {
                    exceptionList.add(
                            (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException("no_field",
                                    false, "description", microdata).setRecurrentMessage(islandType));
                }
            } else {
                if (name == null) {
                    exceptionList.add(
                            (MicrodataValidatorException) new IslandsValidationMicrodataValidatorException("no_field",
                                    false, "name", microdata).setRecurrentMessage(islandType));
                }
            }
        }
        return exceptionList;
    }
}
