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

import org.apache.commons.lang3.StringEscapeUtils;
import org.htmlcleaner.*;
import org.json.JSONException;
import org.json.JSONObject;
import ru.yandex.common.util.*;
import ru.yandex.common.util.collections.MultiMap;
import ru.yandex.common.util.functional.Function;
import ru.yandex.common.util.xml.Xmler;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.data.*;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.transformer.*;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.serialize.util.APIVersion;

import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static ru.yandex.common.util.StringUtils.isEmpty;
import static ru.yandex.common.util.xml.Xmler.Tag;
import static ru.yandex.common.util.xml.Xmler.tag;
import static ru.yandex.webmaster3.core.semantic.semantic_document_parser.microformats.FrontEnd.verifierNode;

/**
 * Created by IntelliJ IDEA.
 * User: rasifiel
 * Date: 8/8/11
 * Time: 12:31 PM
 * To change this template use File | Settings | File Templates.
 */
public class MicrodataUtils {

    public static Xmler.Tag toXml(final Microdata data, final String rootName, final boolean firstLevel) {
        if (data instanceof TextMicrodata) {
            return ((TextMicrodata) data).toValidatorXml(rootName);
        } else if (data instanceof ComplexMicrodata) {
            final ComplexMicrodata complexMicrodata = (ComplexMicrodata) data;
            final List<Tag> tags = new LinkedList<Xmler.Tag>();
            tags.add(verifierNode("itemType", null, false, tag("v", complexMicrodata.getType())));
            for (final String key : complexMicrodata.getPropList()) {
                for (final Microdata element : complexMicrodata.getPropAsList(key)) {
                    if (element instanceof ComplexMicrodata) {
                        tags.add(verifierNode(key.toLowerCase(), null, false, toXml(element, null, false)));
                    } else {
                        tags.add(toXml(element, key.toLowerCase(), false));
                    }
                }
            }
            if (rootName == null) {
                final String type = extractType(complexMicrodata.getType());
                if (type != null) {
                    return verifierNode(type.toLowerCase(), data.number, firstLevel, tags);
                } else {
                    return verifierNode("microdata", data.number, firstLevel, tags);
                }
            } else {
                return verifierNode(rootName, data.number, firstLevel, tags);
            }
        }
        throw new IllegalArgumentException("Unsupported microdata exception");
    }

    public static String highlightEnding(final String typeHref) {
        final int lastSlash = typeHref.lastIndexOf('/');
        if (lastSlash < 0) {
            return typeHref;
        } else {
            return typeHref.substring(0, lastSlash + 1) + "<b>" + typeHref.substring(lastSlash + 1) + "</b>";
        }
    }

    public static String extractType(final String typeHref) {
        if (typeHref == null) {
            return null;
        }
        if (isSchemaOrgTypeHref(typeHref)) {
            return extractSchemaType(typeHref);
        }
        if (yndx_pattern.matcher(typeHref).matches()) {
            return extractYandexMicrodataType(typeHref);
        }
        if (data_vacob_pattern.matcher(typeHref).matches()) {
            return extractSchemaType(typeHref);
        }
        return null;
    }


    public static boolean isSchemaOrgTypeHref(final String typeHref) {
        if (typeHref == null) {
            return false;
        }
        return schema_pattern.matcher(typeHref).matches();
    }

    private static String extractYandexMicrodataType(final String typeHref) {
        final int lastSlash = typeHref.lastIndexOf('/');
        final int xmlSuffixPos = typeHref.lastIndexOf(".xml");
        if (xmlSuffixPos == -1) {
            return null;
        }
        return typeHref.substring(lastSlash + 1, xmlSuffixPos);
    }

    private static String extractSchemaType(final String typeHref) {
        final int lastSlash = typeHref.lastIndexOf('/');

        return typeHref.substring(lastSlash + 1);
    }


    public static String extractBaseUrl(final String typeHref) {
        if(null == typeHref){
            return "";
        }

        final int lastSlash = typeHref.split(" ")[0].lastIndexOf('/');
        if (lastSlash < 0) {
            return typeHref;
        } else {
            return typeHref.substring(0, lastSlash);
        }
    }

    public static List<Microdata> extractMD(final String content, final String url, final boolean expandTree, final boolean isVerifier, final boolean isAPI) {
        return extractMD(content.getBytes(Charset.forName("UTF-8")), url, "UTF-8", expandTree, isVerifier, isAPI);
    }

    public static List<Microdata> extractMD(final String content, final String url, final boolean expandTree, final boolean isVerifier) {
        return extractMD(content.getBytes(Charset.forName("UTF-8")), url, "UTF-8", expandTree, isVerifier);
    }

    public static List<Microdata> extractMD(final byte[] content, final String url, final String proposedCharset, final boolean expandTree, final boolean isVerifier) {
        return extractMD(content, url, "UTF-8", expandTree, isVerifier,false);
    }

    public static List<Microdata> extractMD(final byte[] content, final String url, final String proposedCharset, final boolean expandTree, final boolean isVerifier,final boolean isAPI) {
        if (content == null || content.length == 0) {
            return Collections.emptyList();
        }
        final TransformStrategy strategy = new TransformStrategy();
        final ByteDocumentContext context = new ByteDocumentContext(content,
                new DocumentProperties(url, null, proposedCharset, expandTree, isVerifier, isAPI));
        final Context result = strategy.processTillDone(context);
        if (result instanceof FinalContext) {
            return ((FinalContext) result).getInfo();
        } else {
            return Collections.emptyList();
        }
    }

    public static List<String> extractStringList(final ComplexMicrodata microdata, final String key) {
        final List<String> result = new LinkedList<String>();
        for (final Microdata data : microdata.getPropAsList(key)) {
            if (data instanceof TextMicrodata) {
                result.add(data.toString());
            }
        }
        return result;
    }

    public static List<String> extractNameList(final ComplexMicrodata microdata, final String key) {
        final List<String> result = new LinkedList<String>();
        for (final Microdata data : microdata.getPropAsList(key)) {
            final String name = getTextOrName(data);
            if (name != null) {
                result.add(name);
            }
        }
        return result;
    }

    public static String getTextOrName(final Microdata microdata) {
        if (microdata instanceof TextMicrodata) {
            return ((TextMicrodata) microdata).toDataString();
        } else {
            return ((ComplexMicrodata) microdata).getFirstAsText("name");
        }
    }

    public static String getTextOrName(final ComplexMicrodata microdata, final String field) {
        Microdata value = microdata.getFirst(field);
        if (value != null) {
            return getTextOrName(value);
        } else {
            return null;
        }
    }

    public static TextMicrodata getNameHrefPair(final Microdata microdata) {
        if (microdata instanceof TextMicrodata) {
            return (TextMicrodata) microdata;
        } else if (microdata instanceof ComplexMicrodata) {
            return new TextMicrodata(((ComplexMicrodata) microdata).forceFirstAsText("name"),
                    ((ComplexMicrodata) microdata).getFirstAsText("url"),
                    ((ComplexMicrodata) microdata).forceFirstAsText("name"));
        } else {
            throw new IllegalArgumentException("Unknown microdata type");
        }
    }

    public static TextMicrodata getNameHrefPair(final ComplexMicrodata microdata, final String field) {
        Microdata value = microdata.getFirst(field);
        if (value != null) {
            return getNameHrefPair(value);
        } else {
            return null;
        }
    }

    public static List<TextMicrodata> extractNameHrefList(final ComplexMicrodata microdata, final String key) {
        final List<TextMicrodata> result = new LinkedList<TextMicrodata>();
        for (final Microdata data : microdata.getPropAsList(key)) {
            final TextMicrodata name = getNameHrefPair(data);
            if (name != null) {
                result.add(name);
            }
        }
        return result;
    }

    public static List<String> extractHtmlList(final ComplexMicrodata microdata, final String key) {
        final List<String> result = new LinkedList<String>();
        for (final Microdata value : microdata.getPropAsList(key)) {
            if (value instanceof TextMicrodata) {
                result.add(((TextMicrodata) value).html);
            }
        }
        return result;
    }

    private final static int MAX_DEPTH = 100;

    public static String _stringifyNode(final HtmlNode root, final int depth) {
        if (depth > MAX_DEPTH) {
            return "";
        }
        if (root instanceof ContentNode) {
            return ((ContentNode) root).getContent().toString();
        }
        if (root instanceof TagNode) {
            final TagNode rootAsElement = (TagNode) root;
            if ("script".equalsIgnoreCase(rootAsElement.getName())) {
                return "";
            }
            if ("input".equalsIgnoreCase(rootAsElement.getName())) {
                final String text = rootAsElement.getAttributeByName("value");
                return text == null ? "" : text;
            }
            if ("br".equalsIgnoreCase(rootAsElement.getName())) {
                return "\n";
            }
            if ("del".equalsIgnoreCase(rootAsElement.getName())) {
                return "";
            }
            final StringBuilder sb = new StringBuilder();
            for (final Object child : rootAsElement.getAllChildren()) {
                if (child instanceof HtmlNode) {
                    sb.append(_stringifyNode((HtmlNode) child, depth + 1));
                }
            }
            if ("p".equalsIgnoreCase(rootAsElement.getName())) {
                sb.append('\n');
            }
            return sb.toString();
        } else {
            return "";
        }
    }

    public static String getVocabHost(final String url) {
        if (!isEmpty(url)) {
            return URLUtils.stripPort(URLUtils.stripPath(URLUtils.stripQuery(URLUtils.stripProtocol(url))));
        } else {
            return url;
        }
    }

    public static String stringifyNode(final HtmlNode node) {
        return StringEscapeUtils.unescapeHtml4(_stringifyNode(node, 0));
    }

    private static JSONObject toJson(final ComplexMicrodata md, final MultiMap<Integer, JSONObject> exceptionMultiMap, final boolean only_errors, final APIVersion version) {
        JSONObject result = new JSONObject();
        try {
            if(!only_errors) {
                if (null == md.getType()) {
                    result.put("@type", "");
                } else {
                    result.put("@type", md.getType().split(" "));
                    switch (version) {
                        case VERSION_1_1:
                            result.put("#location", md.location);
                            break;
                    }
                }
            }
            if (!(exceptionMultiMap == null) && exceptionMultiMap.containsKey(md.number)) {
                for (JSONObject e : exceptionMultiMap.get(md.number)) {
                    result.append(APIVersion.getErrorKey(version), APIVersion.serializeToArrayForOldVersion(version,e));
                }
            }
            for (final String key : md.getPropList()) {
                for (final Microdata data : md.getPropAsList(key)) {

                    if(data instanceof ComplexMicrodata) {
                        result.append(extractBaseUrl(((ComplexMicrodata) data).getType()) + "/" + key, md2Json(data, exceptionMultiMap, only_errors, version));
                    }
                    else if ((data instanceof TextMicrodata) && !only_errors){
                        JSONObject resData;
                        switch (version){
                            case VERSION_1_1:
                                resData = new JSONObject().put("@value", data)
                                    .put("#location",data.location);
                                result.append(extractBaseUrl(md.getType()) + "/" + key, resData);
                                break;
                            default:
                                result.append(extractBaseUrl(md.getType()) + "/" + key, data);
                        }
                    }
                }
            }
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        return result;
    }



    public static final Function<Microdata, JSONObject> MD2JSON(APIVersion version) {
        return new Function<Microdata, JSONObject>() {
            @Override
            public JSONObject apply(final Microdata microdata) {

                if (microdata instanceof ComplexMicrodata) {
                    return toJson((ComplexMicrodata) microdata, null, false, version);
                } else if (microdata instanceof TextMicrodata) {
                    try {
                        return toJson((TextMicrodata) microdata, version);
                    } catch (JSONException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    throw new IllegalArgumentException("Unknown microdata type");
                }
            }
        };
    }

    public static JSONObject md2Json(final Microdata microdata, final MultiMap<Integer, JSONObject> exceptionMultiMap, APIVersion version) {
        if (microdata instanceof ComplexMicrodata) {
            return toJson((ComplexMicrodata) microdata, exceptionMultiMap, false, version);
        } else if (microdata instanceof TextMicrodata) {
            try {
                return toJson((TextMicrodata) microdata, version);
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        } else {
            throw new IllegalArgumentException("Unknown microdata type");
        }
    }

    public static JSONObject md2Json(final Microdata microdata, final MultiMap<Integer, JSONObject> exceptionMultiMap, final boolean only_errors, final APIVersion version) {
        if (microdata instanceof ComplexMicrodata) {
            return toJson((ComplexMicrodata) microdata, exceptionMultiMap, only_errors, version);
        } else if (microdata instanceof TextMicrodata) {
            try {
                return toJson((TextMicrodata) microdata, version);
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        } else {
            throw new IllegalArgumentException("Unknown microdata type");
        }
    }


    private static JSONObject toJson(final TextMicrodata md, final APIVersion version) throws JSONException {
        JSONObject result = new JSONObject();
        if (!isEmpty(md.data)) {
            result.put("text", md.data);
        }
        if (!isEmpty(md.href)) {
            result.put("href", md.href);
        }
        return result;
    }

    public static String addPrefixIfEmpty(String url){
        if(!(url.startsWith("http://") || url.startsWith("https://"))){
            return "http://" + url;
        }
        return url;
    }
    public static List<String> protocolAliases(String about) {
//        System.out.println(about);
        ArrayList<String> variants  = new ArrayList<String>();
        variants.add(about);
        if(null!=about) {
            variants.add(StringUtils.replaceFirst(about, "http", "https"));
            if(about.contains("schema.org")){
                variants.add(StringUtils.replaceFirst(about, "http://", "http://www."));
                variants.add(StringUtils.replaceFirst(about, "http://", "https://www."));
            }
        }
        return variants;
    }

    public static List<String> protocolAliases(Iterable<String> abouts) {
//        System.out.println(about);
        ArrayList<String> variants  = new ArrayList<String>();
        for(String about: abouts) {
            variants.add(about);
            if (null != about) {
                variants.add(StringUtils.replaceFirst(about, "http", "https"));
                if (about.contains("schema.org")) {
                    variants.add(StringUtils.replaceFirst(about, "http://", "http://www."));
                    variants.add(StringUtils.replaceFirst(about, "http://", "https://www."));
                }
            }
        }
        return variants;
    }

    public static Set<String> httpPrefixOnly(List<String> validTypes) {
        Set<String> res = new HashSet<String>();
        for(String validType: validTypes){
            res.add(replacePrefix(validType));
        }
        return res;
    }

    public static String replacePrefix(String validType) {
        return Su.replace(validType, Pattern.compile("https?://(www.)*"), new Function<String, String>() {
            @Override
            public String apply(String s) {
                return "http://";
            }
        });
    }

    public static String cutPrefix(String validType) {
        return Su.replace(validType, Pattern.compile("https?://(www.)*"), new Function<String, String>() {
            @Override
            public String apply(String s) {
                return "";
            }
        });
    }
    public static String onlyProtocol(String keyAlias) {
        try {
            Matcher matcher = Pattern.compile("(https?://(www.)?).*").matcher(keyAlias);

            if(matcher.matches()) {
                return matcher.group(1);
            }

            return "";
        } catch (IllegalStateException e){
            return "";
        }
    }

    private static Pattern data_vacob_pattern = Pattern.compile("https?://data-vocabulary/.*");
    private static Pattern yndx_pattern = Pattern.compile("https?://webmaster.yandex.ru/.*");
    private static Pattern schema_pattern = Pattern.compile("(https?://)?(www.)?schema.org.*");


}
