package dareka.processor.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;

import dareka.common.Logger;
import dareka.common.json.*;
import dareka.processor.HttpHeader;
import dareka.processor.HttpRequestHeader;
import dareka.processor.HttpResponseHeader;
import dareka.processor.Processor;
import dareka.processor.Resource;
import dareka.processor.StringResource;
import dareka.processor.URLResource;

public class DmcApiProcessor implements Processor {

    private final String[] SUPPORTED_METHODS = new String[]{"POST"};
    private final Pattern SUPPORTED_PATTERN
            = Pattern.compile("^http://api\\.dmc\\.nico(?::\\d+)?/api/sessions(/[^?]*)?"
                    + "\\?.*_format=([^&]+).*$");
    
    @Override
    public String[] getSupportedMethods() {
        return SUPPORTED_METHODS;
    }

    @Override
    public Pattern getSupportedURLAsPattern() {
        return SUPPORTED_PATTERN;
    }

    @Override
    public String getSupportedURLAsString() {
        return null;
    }

    @Override
    public Resource onRequest(HttpRequestHeader requestHeader, Socket browser) throws IOException {
        String uri = requestHeader.getURI();
        InputStream in = browser.getInputStream();
        requestHeader.removeHopByHopHeaders();

        // 解凍できないEncodingは削除
        String acceptEncoding = requestHeader.getMessageHeader(HttpHeader.ACCEPT_ENCODING);
        if (acceptEncoding != null) {
            acceptEncoding = acceptEncoding.toLowerCase().replaceAll("( *,)?(bzip2|sdch)", "");
            acceptEncoding = acceptEncoding.replaceFirst("^ *,", "");
            requestHeader.setMessageHeader(HttpHeader.ACCEPT_ENCODING, acceptEncoding);
        }

        // ヘッダを受信して、Bodyも受信するか判断
        URLResource r = new URLResource(uri);
        HttpResponseHeader responseHeader = r.getResponseHeader(in, requestHeader);
        if (responseHeader == null) {
            Logger.warning("failed to access to api: " + uri + " (no responseHeader)");
            return r;
        }

        // Bodyの取得
        byte[] bcontent = r.getResponseBody();
        if (bcontent == null) {
            Logger.warning("failed to rewrite: " + uri + " (no responseBody)");
            return r;
        }
        if (responseHeader.getMessageHeader(HttpHeader.CONTENT_ENCODING) != null) {
            Logger.warning("failed to rewrite: " + uri + " (cannot decode)");
            return r;
        }
        responseHeader.removeMessageHeader("Vary");
        responseHeader.removeMessageHeader("Accept-Ranges");
        responseHeader.removeHopByHopHeaders();

        // nlパラメータのコピー
        for (Map.Entry<String, String> e : requestHeader.getParameters().entrySet()) {
            responseHeader.setParameter(e.getKey(), e.getValue());
        }
        processResponse(uri, bcontent);
        return StringResource.getRawResource(responseHeader, bcontent);
    }

    private final Pattern METHOD_PATTERN
            = Pattern.compile("\\?.*_method=([^&]+)");

    private void processResponse(String uri, byte[] bcontent)
            throws IOException {
        Logger.debug("ApiProcessor: " + uri);
        Matcher m = SUPPORTED_PATTERN.matcher(uri);
        if (!m.matches()) {
            return;
        }
        String method = null;
        Matcher m2 = METHOD_PATTERN.matcher(uri);
        if (m2.find()) {
            method = m2.group(1);
        }
        if (method != null && method.equals("DELETE")) {
            return;
        }

        String format = m.group(2);
        if (format.equals("json")) {
            processJsonResponse(bcontent);
        } else if (format.equals("xml")) {
            processXmlResponse(bcontent);
        }
    }

    private void processJsonResponse(byte[] bcontent) throws IOException {
        String content = new String(bcontent, "UTF-8");

        JsonObject obj = Json.parseObject(content);
        if (obj == null) {
            Logger.debug("Broken JSON: " + content);
            return;
        }

        //Logger.debug(obj.toString());
        try {
            String ht2Key = obj.getString("data", "session", "content_auth", "content_auth_info", "value");
            String video_src_id = obj.getString("data", "session", "content_src_id_sets",
                    0, "content_src_ids", 0, "src_id_to_mux",
                    "video_src_ids", 0);
            String audio_src_id = obj.getString("data", "session", "content_src_id_sets",
                    0, "content_src_ids", 0, "src_id_to_mux",
                    "audio_src_ids", 0);
            String file_extension = obj.getString("data", "session", "protocol", "parameters", "http_parameters",
                    "parameters", "http_output_download_parameters", "file_extension");
            boolean isFlash = file_extension != null && file_extension.equals("flv");
            Logger.debug("ht2_nicovideo: " + ht2Key);
            Logger.debug("video_src_id: " + video_src_id);
            Logger.debug("audio_src_id: " + audio_src_id);
            Logger.debug("file_extension: " + file_extension);
            Logger.debug("isFlash: " + isFlash);
            updateHt2Info(ht2Key, video_src_id, audio_src_id, isFlash);
        } catch (Exception e) {
            Logger.warning("failed to extract information from API: " + e.getMessage());
        }
    }

    XPathExpression ht2key_xpath, video_src_id_xpath, audio_src_id_xpath, file_extension_xpath;

    private void processXmlResponse(byte[] bcontent) throws IOException {
        InputStream bais = new ByteArrayInputStream(bcontent);

        try {
            Document document = DocumentBuilderFactory.newInstance()
                    .newDocumentBuilder()
                    .parse(bais);

            XPath xpath = XPathFactory.newInstance().newXPath();

            if (ht2key_xpath == null) {
                ht2key_xpath = xpath.compile("/object/data/session/content_auth/content_auth_info/value/text()");
                video_src_id_xpath = xpath.compile("/object/data/session/content_src_id_sets/content_src_id_set[1]/content_src_ids/src_id_to_mux/video_src_ids/string[1]/text()");
                audio_src_id_xpath = xpath.compile("/object/data/session/content_src_id_sets/content_src_id_set[1]/content_src_ids/src_id_to_mux/audio_src_ids/string[1]/text()");
                file_extension_xpath = xpath.compile("/object/data/session/protocol/parameters/http_parameters/parameters/http_output_download_parameters/file_extension/text()");
            }

            String ht2Key = ht2key_xpath.evaluate(document);
            String video_src_id = video_src_id_xpath.evaluate(document);
            String audio_src_id = audio_src_id_xpath.evaluate(document);
            String file_extension = file_extension_xpath.evaluate(document);
            boolean isFlash = file_extension != null && file_extension.equals("flv");
            Logger.debug("ht2_nicovideo: " + ht2Key);
            Logger.debug("video_src_id: " + video_src_id);
            Logger.debug("audio_src_id: " + audio_src_id);
            Logger.debug("file_extension: " + file_extension);
            Logger.debug("isFlash: " + isFlash);
            if (ht2Key.equals("")) {
                Logger.warning("failed to extract information from API: empty key");
                return;
            }
            if (video_src_id.equals("")) {
                Logger.warning("failed to extract information from API: empty video_src_id");
                return;
            }
            if (audio_src_id.equals("")) {
                Logger.warning("failed to extract information from API: empty audio_src_id");
                return;
            }
            updateHt2Info(ht2Key, video_src_id, audio_src_id, isFlash);
        } catch (Exception e) {
            Logger.warning("failed to extract information from API: " + e.getMessage());
        }
    }

    private void updateHt2Info(String ht2Key, String videoType, String audioType, boolean isFlash) {
        Ht2Entry entry = new Ht2Entry(ht2Key, videoType, audioType, isFlash);
        NLShared.INSTANCE.getHt2Manager().update(entry);
    }
}
