/*
 * Copyright (C) 2005-2006 Kouji Sugisawa. All rights reserved.
 */

package jp.sourceforge.livez.vodafone;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.math.NumberUtils;

import jp.sourceforge.livez.IllegalUserAgentException;
import jp.sourceforge.livez.UserAgentNotFoundException;

/**
 * VodafoneLiveUserAgent を実装したクラスです。
 * 
 * @author 杉澤 浩二
 */
final class VodafoneUserAgentImpl implements VodafoneUserAgent {

	static final String JPHONE_MSNAME = "J-[A-Z]+[0-9]+[A-Z]*";
	static final String VODAFONE_MSNAME = "V[0-9]{3}[A-Z]+[0-9a-z]*";

	private static final String MSNAME 		= "(" + JPHONE_MSNAME + "|" + VODAFONE_MSNAME + "|V-XX111){1}(_[a-z]+)?";
	private static final String SERIAL_NUMBER	= "SN[a-zA-Z0-9]{11,15}";
	private static final String MSVENDOR		= "[A-Z]+/.+";
	private static final String MIDP			= "Profile/MIDP-[0-9]\\.[0-9]";
	private static final String CLDC			= "Configuration/CLDC-[0-9]\\.[0-9]";
	private static final String JSCL			= "Ext-Profile/JSCL-[0-9]\\.[0-9]\\.[0-9]";

	private static final String USERAGENT_JPHONE =
		"^J-PHONE/[23456789]\\.[0-9]/" + MSNAME +
			"(/" + SERIAL_NUMBER + ")?" +
			"( " + MSVENDOR + ")?" +
			"( " + MIDP + ")?" +
			"( " + CLDC + ")?" +
			"( " + JSCL + ")?" +
		"$";

	private static final String UNTRUSTED		= "UNTRUSTED/1.0";
	private static final String USERAGENT_UNTRUSTED = "^" + UNTRUSTED + "$";

	private static final String APPLICATION	= "(Browser|Java|SVG|Flash)+/.+/.+";
	private static final String JSCL2			= "Ext-J-Profile/JSCL-[0-9]\\.[0-9]\\.[0-9]";
	private static final String VSCL			= "Ext-V-Profile/VSCL-[0-9]\\.[0-9]\\.[0-9]";

	private static final String USERAGENT_VODAFONE =
		"^Vodafone/1\\.[0-9]/" + MSNAME + "/.+(/" + SERIAL_NUMBER + ")?.*" +
			"( " + APPLICATION + ")?" +
			"( " + MIDP + ")?" +
			"( " + CLDC + ")?" +
			"( (" + JSCL + '|' + JSCL2 + "){1})?" +
			"( " + VSCL + ")?" +
			"(( |/){1}" + UNTRUSTED + ")?" +
		"$";

	private static final String MOTOROLA_PREFIX = "MOT-";

	private static final String USERAGENT_MOTOROLA =
		"^" + MOTOROLA_PREFIX + ".+/[a-zA-Z0-9]+\\.[a-zA-Z0-9]+\\.[a-zA-Z0-9]+" +
			"( MIB/[0-9]\\.[0-9]\\.[0-9])?" +
			"( " + MIDP + ")?" +
			"( " + CLDC + ")?" +
		"$";

	/**
	 * 指定されたHTTPリクエスト情報からクラスを構築します。
	 * 
	 * @param request HTTPリクエスト情報
	 * @throws UserAgentNotFoundException ユーザーエージェントが null の場合
	 * @throws IllegalUserAgentException ユーザエージェントの形式が不正な場合
	 */
	public VodafoneUserAgentImpl(final HttpServletRequest request) throws UserAgentNotFoundException, IllegalUserAgentException {
		this(request.getHeader(HTTP_HEADER_KEY));
	}

	/**
	 * 指定された文字列からクラスを構築します。
	 * 
	 * @param ua HTTP ユーザーエージェントを表す文字列
	 * @throws UserAgentNotFoundException ユーザーエージェントが null の場合
	 * @throws IllegalUserAgentException ユーザエージェントの形式が不正な場合
	 */
	public VodafoneUserAgentImpl(final String ua) throws UserAgentNotFoundException, IllegalUserAgentException {
		if (ua == null)
			throw new UserAgentNotFoundException();

		this.value = ua;

		// 前半部分を処理します
		String[] s;
		s = ua.split("/");

		// 端末IDや端末名を設定します
		if (value.matches(USERAGENT_JPHONE)) {
			this.id = s[2];
			this.generationVersion = NumberUtils.toFloat(s[1]);
		} else if (value.matches(USERAGENT_UNTRUSTED)) {
			this.generationVersion = NumberUtils.toFloat("1.0");
			this.application = new ApplicationImpl(ApplicationType.JAVA);
			this.vappli = new VappliImpl((float)2.0, (float)1.1);
			return;		// これ以上処理をせず抜けます
		} else if (value.matches(USERAGENT_VODAFONE)) {
			this.id = s[2];
			this.generationVersion = NumberUtils.toFloat(s[1]);
		} else if (value.matches(USERAGENT_MOTOROLA)) {
			this.id = s[0].substring(MOTOROLA_PREFIX.length());
			this.generationVersion = (float) 1.0;
		} else
			throw new IllegalUserAgentException(ua);

		// シリアル番号を設定します
		for (int i = 3; i < s.length; i++) {
			if (s[i].matches(SERIAL_NUMBER))
				this.serialNumber = s[i].substring(2);
		}

		// 拡張情報の解析
		// 後半部分を処理し、パケット対応機の拡張プロパティを設定します
		Float midp = null;
		Float cldc = null;
		String jscl = null;
		String vscl = null;
		boolean untrusted = false;

		s = ua.split(" ");
		for (int i = 1; i < s.length; i++) {
			// Vアプリ情報
			if (s[i].matches(MIDP)) {
				midp = NumberUtils.toFloat(s[i].substring(s[i].lastIndexOf('-') + 1));
			} else if (s[i].matches(CLDC)) {
				cldc = NumberUtils.toFloat(s[i].substring(s[i].lastIndexOf('-') + 1));
			} else if (s[i].matches(JSCL) || s[i].matches(JSCL2)) {
				jscl = s[i].substring(s[i].lastIndexOf('-') + 1);
			} else if (s[i].matches(VSCL)) {
				vscl = s[i].substring(s[i].lastIndexOf('-') + 1);

			// 3GC端末のアプリケーション情報
			} else if (s[i].startsWith("Browser/")) {
				final String[] strings = s[i].split("/");
				this.application = new ApplicationImpl(ApplicationType.BROWSER, strings[1], NumberUtils.toFloat(strings[2]));
			} else if (s[i].startsWith("Java/")) {
				final String[] strings = s[i].split("/");
				this.application = new ApplicationImpl(ApplicationType.JAVA, strings[1], NumberUtils.toFloat(strings[2]));
			} else if (s[i].startsWith("SVG/")) {
				final String[] strings = s[i].split("/");
				this.application = new ApplicationImpl(ApplicationType.SVG, strings[1], NumberUtils.toFloat(strings[2]));
			} else if (s[i].startsWith("Flash/")) {
				final String[] strings = s[i].split("/");
				this.application = new ApplicationImpl(ApplicationType.FLASH, strings[1], NumberUtils.toFloat(strings[2]));
			}

			// UNTRUSTED 判定
			if (s[i].indexOf(UNTRUSTED) != -1)
				untrusted = true;
		}

		// UNTRUSTED の場合は MIDP-2.0 の Java アプリでアクセスなので設定
		if (untrusted) {
			if (this.application == null)
				this.application = new ApplicationImpl(ApplicationType.JAVA);

			if (midp == null)
				midp = (float) 2.0;
			if (cldc == null)
				cldc = (float) 1.1;
		}

		if (midp != null || cldc != null || jscl != null || vscl != null)
			this.vappli = new VappliImpl(midp, cldc, jscl, vscl);
	}

	/**
	 * ユーザエージェントを保持します。
	 */
	private String value = null;

	/*
	 * @see jp.sourceforge.livez.UserAgent#getValue()
	 */
	public String getValue() {
		return value;
	}

	/**
	 * 端末IDを保持します。
	 */
	private String id = null;

	/*
	 * @see jp.sourceforge.livez.UserAgent#getId()
	 */
	public String getId() {
		return id;
	}

	/**
	 * 世代のバージョン情報を保持します。
	 */
	private float generationVersion;

	/*
	 * @see jp.sourceforge.livez.vodafone.VodafoneUserAgent#getGenerationVersion()
	 */
	public float getGenerationVersion() {
		return generationVersion;
	}

	/**
	 * シリアル番号を保持します。
	 */
	private String serialNumber = null;

	/*
	 * @see jp.sourceforge.livez.vodafone.VodafoneUserAgent#getSerialNumber()
	 */
	public String getSerialNumber() {
		return serialNumber;
	}

	/**
	 * 3GC端末でのアプリケーション情報を保持します。
	 */
	private ApplicationImpl application = null;

	/*
	 * @see jp.sourceforge.livez.vodafone.VodafoneUserAgent#getApplication()
	 */
	public Application getApplication() {
		return application;
	}

	/**
	 * Vアプリ実行環境情報を保持します。
	 */
	private VappliImpl vappli = null;

	/*
	 * @see jp.sourceforge.livez.vodafone.VodafoneUserAgent#getVappli()
	 */
	public Vappli getVappli() {
		return vappli;
	}

	@Override
	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(this);
	}

	@Override
	public boolean equals(final Object obj) {
		return EqualsBuilder.reflectionEquals(this, obj);
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
	}

}
