package com.nifty.cloud.ncs.service.parser;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import com.nifty.cloud.ncs.NiftyCloudClientException;
import com.nifty.cloud.ncs.NiftyCloudResponse;
import com.nifty.cloud.ncs.http.HttpClient;
import com.nifty.cloud.ncs.http.HttpResponse;
import com.nifty.cloud.ncs.http.HttpResponseParser;
import com.nifty.cloud.ncs.service.NcsObject;
import com.nifty.cloud.ncs.service.StorageHeaders;
import com.nifty.cloud.ncs.service.StorageHeadersImpl;
import com.nifty.cloud.ncs.service.StorageResponse;
import com.nifty.cloud.ncs.util.DateUtils;

/**
 * ニフクラストレージ応答解析抽象クラスです。
 *
 * @param <T>
 */
public abstract class AbstractStorageResponseParser<T> implements HttpResponseParser<NiftyCloudResponse<T>>
{
	protected static final Log log = LogFactory.getLog(StorageResponseParser.class);
	protected XMLReader xmlReader = null;
	protected boolean sanitizing = true;

	private static final Set<String> ignoredHeaders;

	static {
		ignoredHeaders = new HashSet<String>();
		ignoredHeaders.add(StorageHeadersImpl.DATE);
		ignoredHeaders.add(StorageHeadersImpl.SERVER);
		ignoredHeaders.add(StorageHeadersImpl.REQUEST_ID);
		ignoredHeaders.add(StorageHeadersImpl.EXTENDED_REQUEST_ID);
	}

	/**
	 *
	 *
	 * @throws NiftyCloudClientException
	 * 			XMLリーダの初期化に失敗したことを表します。
	 */
	public AbstractStorageResponseParser() throws NiftyCloudClientException
	{
		try {
			xmlReader = XMLReaderFactory.createXMLReader();
		} catch (SAXException e) {
			throw (new NiftyCloudClientException("XMLReader initialization failed."));
		}
	}

	/**
	 * ニフクラストレージの応答メタデータを解析します。
	 *
	 * @param response
	 *  ニフクラストレージからのHTTP応答
	 * @return
	 *  A new NiftyCloudResponse<T>.
	 *  新しいNiftyCloudResponse<T>オブジェクト
	 */
	protected NiftyCloudResponse<T> parseMetaData(HttpResponse response)
	{
		NiftyCloudResponse<T> ncsResponse = new NiftyCloudResponse<T>();
		String requestId = response.getHeader(StorageHeadersImpl.REQUEST_ID);
		String hostId = response.getHeader(StorageHeadersImpl.EXTENDED_REQUEST_ID);

		ncsResponse.addMetadata(NiftyCloudResponse.NIFTY_REQUEST_ID, requestId);
		ncsResponse.addMetadata(StorageResponse.HOST_ID, hostId);

		return (ncsResponse);
	}

	/**
	 * オブジェクトのメタデータを設定します。
	 *
	 * @param response
	 * 			ニフクラストレージからのHTTP応答
	 * @param object
	 * 			メタデータ設定対象のオブジェクト
	 */
	protected void setObjectMetadata(HttpResponse response, NcsObject object)
	{
		for (Entry<String, String> header : response.getHeaders().entrySet()) {
			String key = header.getKey();
			if (key.startsWith(StorageHeadersImpl.NIFTY_USER_METADATA_PREFIX)) {
				key = key.substring(StorageHeadersImpl.NIFTY_USER_METADATA_PREFIX.length());
				object.addUserMetadata(key, header.getValue());
			} else if (ignoredHeaders.contains(key)) {
				// ignore
			} else if (key.equals(StorageHeaders.LAST_MODIFIED)) {
				try {
					object.addHeader(key, DateUtils.parseRfc822Date(header.getValue()));
				} catch (ParseException e) {
					log.warn("Can't parse last modified date: " + header.getValue(), e);
				}
			} else if (key.equals(StorageHeadersImpl.CONTENT_LENGTH)) {
				try {
					object.addHeader(key, Long.parseLong(header.getValue()));
				} catch (NumberFormatException e) {
					log.warn("Can't parse content length: " + header.getValue(), e);
				}
			} else if (key.equals(StorageHeadersImpl.ETAG)) {
				object.addHeader(key, removeQuotes(header.getValue()));
			} else {
				object.addHeader(key, header.getValue());
			}
		}
	}

	/**
	 * 応答ボディを解析し、応答オブジェクトのインスタンスを返却します。
	 *
	 * @param response
	 *  HTTP応答
	 * @return
	 *  ニフクラストレージの応答オブジェクト
	 */
	public NiftyCloudResponse<T> parse(HttpResponse response) throws Exception
	{
		NiftyCloudResponse<T> ncsResponse = parseMetaData(response);

		try {
			if (response.getContent() != null) {
				response.getContent().close();
			}
		} catch (IOException e) {
			log.error("Can't cleanly close input stream: " + e.getMessage(), e);
		}

		return (ncsResponse);
	}

	/**
	 * 指定したハンドラを使用してXML解析を行います。
	 *
	 * @param handler
	 *  XML解析ハンドラ
	 * @param is
	 *  応答ボディの入力ストリーム
	 * @throws NiftyCloudClientException
	 */
	protected void parseXml(DefaultHandler handler, InputStream is) throws NiftyCloudClientException
	{
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(is, HttpClient.DEFAULT_ENCODING));
			xmlReader.setContentHandler(handler);
			xmlReader.setErrorHandler(handler);
			xmlReader.parse(new InputSource(br));
		} catch (Exception e) {
			try {
				is.close();
			} catch (IOException e1) {
				// NOP
			}
			throw (new NiftyCloudClientException("XML parse failed with handler " + handler.getClass(), e));
		}
	}

	/**
	 * 応答ボディのサニタイズを行います。
	 *
	 * @param is
	 * 			応答ボディの入力ストリーム
	 *
	 * @return サニタイズ後の応答ボディ
	 *
	 * @throws NiftyCloudClientException
	 */
	protected InputStream sanitize(InputStream is) throws NiftyCloudClientException
	{
		if (sanitizing == false) {
			// Not performed.
			return (is);
		}

		InputStream ss = null;
		StringBuffer sb = new StringBuffer(10240);
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(is, HttpClient.DEFAULT_ENCODING));
			char[] c = new char[10240];
			int len;
			while ((len = br.read(c)) != -1) {
				sb.append(c, 0, len);
			}
			br.close();

			String s = sb.toString().replaceAll("\r", "&#013;");
			ss = new ByteArrayInputStream(s.getBytes(HttpClient.DEFAULT_ENCODING));
		} catch (Exception e) {
			try {
				is.close();
			} catch (IOException ex) {
				// NOP
			}
			throw (new NiftyCloudClientException("MLX sanitizing failed.", e));
		}

		return (ss);
	}

	/**
	 * 指定した文字列が<code>null</code>かどうかの判定を行います。
	 *
	 * @param str
	 * 			判定対象文字列
	 *
	 * @return 判定対象文字列が<code>null</code>または空文字の場合は<code>null</code>、
	 * 			そうでなければそのままの文字列
	 */
	protected String checkForEmptyString(String str)
	{
		if (str == null) {
			return (null);
		}
		if (str.length() == 0) {
			return (null);
		}

		return (str);
	}

	/**
	 * 文字列をint型に変換します。
	 *
	 * @param str
	 * 			変換対象文字列
	 *
	 * @return 変換に失敗した場合は-1、そうでなければ変換後の値
	 */
	protected int parseInt(String str)
	{
		try {
			return (Integer.parseInt(str));
		} catch (NumberFormatException e) {
			log.error("Parse integer failed '" + str + "'", e);
		}

		return (-1);
	}

	/**
	 * 文字列をlong型に変換します。
	 *
	 * @param str
	 * 			変換対象文字列
	 *
	 * @return 変換に失敗した場合は-1、そうでなければ変換後の値
	 */
	protected long parseLong(String str)
	{
		try {
			return (Long.parseLong(str));
		} catch (NumberFormatException e) {
			log.error("Parse long failed '" + str + "'", e);
		}

		return (-1);
	}

	/**
	 * 指定した文字列からダブルクォーテーションを除去します。
	 *
	 * @param str
	 * 			削除対象文字列
	 *
	 * @return 削除対象文字列が<code>null</code>の場合は<code>null</code>、
	 * 			そうでなければダブルクォーテーションを除去した文字列
	 */
	protected String removeQuotes(String str)
	{
		if (str == null) {
			return (null);
		}

		str = str.trim();

		if (str.startsWith("\"")) {
			str = str.substring(1);
		}
		if (str.endsWith("\"")) {
			str = str.substring(0, str.length() - 1);
		}

		return (str);
	}

}
