package com.readrops.api.localfeed;

import android.accounts.NetworkErrorException;
import android.util.Log;

import com.readrops.api.localfeed.atom.ATOMFeed;
import com.readrops.api.localfeed.json.JSONFeed;
import com.readrops.api.localfeed.rss.RSSFeed;
import com.readrops.api.localfeed.rss.RSSLink;
import com.readrops.api.utils.HttpManager;
import com.readrops.api.utils.LibUtils;
import com.readrops.api.utils.UnknownFormatException;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;

import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class RSSQuery {

    private static final String TAG = RSSQuery.class.getSimpleName();

    private static final String RSS_CONTENT_TYPE_REGEX = "([^;]+)";

    private static final String RSS_2_REGEX = "rss.*version=\"2.0\"";

    private static final String ATOM_REGEX = "<feed.* xmlns=\"http://www.w3.org/2005/Atom\"";

    /**
     * Request the url given in parameter.
     * This method is synchronous, it <b>has</b> to be called from another thread than the main one.
     *
     * @param url url to request
     * @throws Exception
     */
    public RSSQueryResult queryUrl(String url, Map<String, String> headers) throws Exception {
        Response response = query(url, headers);

        if (response.isSuccessful()) {
            String header = response.header(LibUtils.CONTENT_TYPE_HEADER);
            RSSType type = getRSSType(header);

            if (type == null)
                return new RSSQueryResult(new UnknownFormatException("bad content type : " + header + "for " + url));

            return parseFeed(response.body().byteStream(), type, response);
        } else if (response.code() == 304)
            return null;
        else
            return new RSSQueryResult(new NetworkErrorException("Error " + response.code() + " when requesting url " + url));
    }

    public boolean isUrlFeedLink(String url) throws IOException {
        Response response = query(url, new HashMap<>());

        if (response.isSuccessful()) {
            String header = response.header(LibUtils.CONTENT_TYPE_HEADER);
            RSSType type = getRSSType(header);

            if (type == RSSType.RSS_UNKNOWN) {
                RSSType contentType = getContentRSSType(response.body().string());
                return contentType != RSSType.RSS_UNKNOWN;
            } else return true;
        } else
            return false;
    }

    private Response query(String url, Map<String, String> headers) throws IOException {
        OkHttpClient okHttpClient = HttpManager.getInstance().getOkHttpClient();
        HttpManager.getInstance().setCredentials(null);

        Request.Builder builder = new Request.Builder().url(url);
        for (String header : headers.keySet()) {
            String value = headers.get(header);
            builder.addHeader(header, value);
        }

        Request request = builder.build();
        return okHttpClient.newCall(request).execute();
    }

    private RSSType getRSSType(String contentType) {
        Pattern pattern = Pattern.compile(RSS_CONTENT_TYPE_REGEX);
        Matcher matcher = pattern.matcher(contentType);

        String header;
        if (matcher.find())
            header = matcher.group(0);
        else
            header = contentType;

        switch (header) {
            case LibUtils.RSS_DEFAULT_CONTENT_TYPE:
                return RSSType.RSS_2;
            case LibUtils.ATOM_CONTENT_TYPE:
                return RSSType.RSS_ATOM;
            case LibUtils.JSON_CONTENT_TYPE:
                return RSSType.RSS_JSON;
            case LibUtils.RSS_TEXT_CONTENT_TYPE:
            case LibUtils.HTML_CONTENT_TYPE:
            case LibUtils.RSS_APPLICATION_CONTENT_TYPE:
            default:
                Log.d(TAG, "bad content type : " + contentType);
                return RSSType.RSS_UNKNOWN;
        }
    }

    /**
     * Parse input feed
     *
     * @param stream   source to parse
     * @param type     rss type, important to know the feed format
     * @param response query response
     * @throws Exception
     */
    private RSSQueryResult parseFeed(InputStream stream, RSSType type, Response response) throws Exception {
        String xml = LibUtils.inputStreamToString(stream);
        Serializer serializer = new Persister();

        if (type == RSSType.RSS_UNKNOWN) {
            RSSType contentType = getContentRSSType(xml);
            if (contentType == RSSType.RSS_UNKNOWN) {
                return new RSSQueryResult(new UnknownFormatException("Unknown content format"));
            } else
                type = contentType;
        }

        String etag = response.header(LibUtils.ETAG_HEADER);
        String lastModified = response.header(LibUtils.LAST_MODIFIED_HEADER);
        AFeed feed = null;
        RSSQueryResult queryResult = new RSSQueryResult();

        switch (type) {
            case RSS_2:
                feed = serializer.read(RSSFeed.class, xml);

                // workaround if the channel does not have any atom:link tag
                if (((RSSFeed) feed).getChannel().getFeedUrl() == null) {
                    ((RSSFeed) feed).getChannel().getLinks().add(new RSSLink(null, response.request().url().toString()));
                }
                break;
            case RSS_ATOM:
                feed = serializer.read(ATOMFeed.class, xml);
                ((ATOMFeed) feed).setWebsiteUrl(response.request().url().scheme() + "://" + response.request().url().host());
                ((ATOMFeed) feed).setUrl(response.request().url().toString());
                break;
            case RSS_JSON:
                Moshi moshi = new Moshi.Builder()
                        .build();

                JsonAdapter<JSONFeed> jsonFeedAdapter = moshi.adapter(JSONFeed.class);
                feed = jsonFeedAdapter.fromJson(xml);
                break;
        }

        queryResult.setFeed(feed);
        queryResult.setRssType(type);

        feed.setEtag(etag);
        feed.setLastModified(lastModified);

        return queryResult;
    }

    private RSSType getContentRSSType(String content) {
        RSSType type;

        if (Pattern.compile(RSS_2_REGEX).matcher(content).find())
            type = RSSType.RSS_2;
        else if (Pattern.compile(ATOM_REGEX).matcher(content).find())
            type = RSSType.RSS_ATOM;
        else
            type = RSSType.RSS_UNKNOWN;

        return type;
    }

    public enum RSSType {
        RSS_2,
        RSS_ATOM,
        RSS_JSON,
        RSS_UNKNOWN
    }


}
