/*
 * Copyright (c) 2006-2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.trading;

import java.io.Serializable;
import java.util.Date;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

/**
 * {@link Position} を実装したデフォルトの実装クラスを提供します。
 * 
 * @author 杉澤 浩二
 */
//@Entity(name = "position")
//@Table(name = "position")
@XmlRootElement(name = "position")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(
	propOrder = {
		"id", "symbol", "type",
		"entryLabel", "entryDate", "entryPrice", "entryQuantity", "entryCommission", "entrySlippage",
		"exitLabel", "exitDate", "exitPrice", "exitQuantity", "exitCommission", "exitSlippage",
		"hold"
	}
)
public class DefaultPosition implements Position, Serializable, Comparable<DefaultPosition> {

	private static final long serialVersionUID = -5406781383133951498L;

	/**
	 * デフォルトコンストラクタです。
	 */
	protected DefaultPosition() {}

	/**
	 * 新規のポジションをエントリーしてこのクラスを構築するコンストラクタです。
	 * 
	 * @param id ID
	 * @param symbol シンボル
	 * @param entryLabel エントリーラベル
	 * @param type ポジションの種類
	 * @param entryDate エントリー日時
	 * @param entryPrice エントリー価格
	 * @param entryQuantity エントリー数量
	 * @param entryCommission エントリー手数料
	 * @param entrySlippage エントリースリッページ
	 * @throws NullPointerException シンボル、ポジションの種類、エントリー日時のいずれかまたはすべてに <code>null</code> が指定された場合
	 * @throws IllegalArgumentException エントリー価格、エントリー数量のいずれかまたはすべてに <code>0</code> 以下の値が指定された場合。または、エントリー手数料、エントリースリッページのいずれかまたはすべてに <code>0</code> 未満の値が指定された場合。
	 */
	protected DefaultPosition(
			final int id,
			final String symbol,
			final PositionType type,
			final String entryLabel,
			final Date entryDate,
			final double entryPrice,
			final int entryQuantity,
			final double entryCommission,
			final double entrySlippage)
	{
		if (symbol == null)
			throw new NullPointerException("Symbol is null.");
		if (type == null)
			throw new NullPointerException("PositionType is null.");
		if (entryDate == null)
			throw new NullPointerException("EntryDate is null.");
		if (entryPrice <= 0)
			throw new IllegalArgumentException();
		if (entryQuantity <= 0)
			throw new IllegalArgumentException();
		if (entryCommission < 0)
			throw new IllegalArgumentException();
		if (entrySlippage < 0)
			throw new IllegalArgumentException();

		this.id = id;
		this.symbol = symbol;
		this.entryLabel = entryLabel;
		this.type = type;
		this.entryDate = entryDate;
		this.entryPrice = entryPrice;
		this.entryQuantity = entryQuantity;
		this.entryCommission = entryCommission;
		this.entrySlippage = entrySlippage;
	}

	@Override
	public DefaultPosition close(
			final int id,
			final String exitLabel,
			final Date exitDate,
			final double exitPrice,
			final int exitQuantity,
			final double exitCommission,
			final double exitSlippage,
			final int hold)
	{
		if (isClosed())
			return null;

		if (exitDate == null)
			throw new NullPointerException("ExitDate is null.");
		if (exitPrice <= 0)
			throw new IllegalArgumentException();
		if (exitQuantity <= 0)
			throw new IllegalArgumentException();
		if (exitCommission < 0)
			throw new IllegalArgumentException();
		if (exitSlippage < 0)
			throw new IllegalArgumentException();
		if (hold < 0)
			throw new IllegalArgumentException();

		if (entryDate.compareTo(exitDate) > 0)
			throw new IllegalArgumentException();
		if (entryQuantity < exitQuantity)
			throw new IllegalArgumentException();

		this.exitLabel = exitLabel;
		this.exitDate = exitDate;
		this.exitPrice = exitPrice;
		this.exitQuantity = exitQuantity;
		this.exitCommission = exitCommission;
		this.exitSlippage = exitSlippage;
		this.hold = hold;

		// 分割決済の場合
		if (this.entryQuantity != this.exitQuantity) {
			try {
				// 分割ポジション情報を生成して返します。
				return new DefaultPosition(id, this.symbol, this.type, this.entryLabel, this.entryDate, this.entryPrice, this.entryQuantity - this.exitQuantity, 0, this.entrySlippage);
			} finally {
				// 元のポジションのエントリー数量をイグジット数量と同じにする
				this.entryQuantity = this.exitQuantity;
			}
		}
		return null;
	}

	/**
	 * IDを保持します。
	 */
	//@Id
	//@Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
	//@OrderBy
	@XmlElement(name = "id", nillable = false, required = true)
	protected int id;
	@Override public int getId() { return id; }

	/**
	 * シンボルを保持します。
	 */
	//@Column(name = "symbol", unique = false, nullable = false, insertable = true, updatable = false)
	@XmlElement(name = "symbol", nillable = false, required = true)
	protected String symbol;
	@Override public String getSymbol() { return symbol; }

	/**
	 * ポジションの種類を保持します。
	 */
	//@Column(name = "type", unique = false, nullable = false, insertable = true, updatable = false)
	//@Enumerated(EnumType.STRING)
	@XmlElement(name = "type", nillable = false, required = true)
	protected PositionType type;
	@Override public PositionType getType() { return type; }

	/**
	 * エントリーラベルを保持します。
	 */
	@XmlElement(name = "entry_label", nillable = true, required = true)
	protected String entryLabel;
	@Override public String getEntryLabel() { return entryLabel; }

	/**
	 * エントリー日時を保持します。
	 */
	//@Column(name = "entry_date", unique = false, nullable = false, insertable = true, updatable = false)
	//@Temporal(TemporalType.TIMESTAMP)
	@XmlElement(name = "entry_date", nillable = false, required = true)
	protected Date entryDate;
	@Override public Date getEntryDate() { return entryDate; }

	/**
	 * エントリー価格を保持します。
	 */
	@XmlElement(name = "entry_price", nillable = false, required = true)
	protected double entryPrice;
	@Override public double getEntryPrice() { return entryPrice; }

	/**
	 * エントリー数量を保持します。
	 */
	@XmlElement(name = "entry_quantity", nillable = false, required = true)
	protected int entryQuantity;
	@Override public int getEntryQuantity() { return entryQuantity; }

	@Override public double getEntryAmount() { return getEntryPrice() * getEntryQuantity(); }

	/**
	 * エントリー手数料を保持します。
	 */
	@XmlElement(name = "entry_commission", nillable = false, required = true)
	protected double entryCommission;
	@Override public double getEntryCommission() { return entryCommission; }

	/**
	 * エントリースリッページを保持します。
	 */
	@XmlElement(name = "entry_slippage", nillable = false, required = true)
	protected double entrySlippage;
	@Override public double getEntrySlippage() { return entrySlippage; }

	/**
	 * イグジットラベルを保持します。
	 */
	@XmlElement(name = "exit_label", nillable = true, required = false)
	protected String exitLabel;
	@Override public String getExitLabel() { return exitLabel; }

	/**
	 * イグジット日時を保持します。
	 */
	@XmlElement(name = "exit_date", nillable = true, required = false)
	protected Date exitDate;
	@Override public Date getExitDate() { return exitDate; }

	/**
	 * イグジット価格を保持します。
	 */
	@XmlElement(name = "exit_price", nillable = true, required = false)
	protected double exitPrice;
	@Override public double getExitPrice() { return exitPrice; }

	/**
	 * イグジット数量を保持します。
	 */
	@XmlElement(name = "exit_quantity", nillable = true, required = false)
	protected int exitQuantity;
	@Override public int getExitQuantity() { return exitQuantity; }

	@Override public double getExitAmount() { return getExitPrice() * getExitQuantity(); }

	/**
	 * イグジット手数料を保持します。
	 */
	@XmlElement(name = "exit_commission", nillable = true, required = false)
	protected double exitCommission;
	@Override public double getExitCommission() { return exitCommission; }

	/**
	 * イグジットスリッページを保持します。
	 */
	@XmlElement(name = "exit_slippage", nillable = true, required = false)
	protected double exitSlippage;
	@Override public double getExitSlippage() { return exitSlippage; }

	/**
	 * 損切りによってイグジットしたかどうかを保持します。
	 * 
	 * @deprecated 未使用
	 */
	@Deprecated
	//@Transient
	//@XmlTransient
	protected transient boolean stoped;

	/**
	 * 保有期間を保持します。
	 */
	@XmlElement(name = "hold", nillable = true, required = false)
	protected int hold;
	@Override public int getHold() { return hold; }

	// ----------------------------------------------------------------------

	@Override public boolean isClosed() { return exitDate != null; }

	@Override
	public int getDays() {
		if (isClosed())
			return getDays(exitDate);
		return 0;
	}

	// ----------------------------------------------------------------------

	@Override
	public double getGrossSales(final double amount) {
		switch (type) {
			case LONG:
				return amount - getEntryAmount();
			case SHORT:
				return getEntryAmount() - amount;
			default:
				return 0.0;
		}
	}

	@Override
	public double getNetProfit(final double amount, final double commission) {
		return getGrossSales(amount) - (getEntryCommission() + commission);
	}

	@Override
	public double getPercent(final double amount) {
		final double entryAmount = getEntryAmount();
		if (amount == 0.0 || entryAmount == 0.0)
			return 0.0;
		return amount / entryAmount;
	}

	@Override
	public int getDays(final Date date) {
		final long one_date_time = 1000 * 60 * 60 * 24;
		final long diffDays = (date.getTime() - entryDate.getTime()) / one_date_time;
		return (int) diffDays;
	}

	// ----------------------------------------------------------------------

	@Override
	public double getGrossSales() {
		if (isClosed())
			return getGrossSales(getExitAmount());
		return 0.0;
	}

	@Override public double getCommission() { return getEntryCommission() + getExitCommission(); }
	@Override public double getSlippage() { return getEntrySlippage() + getExitSlippage(); }

	@Override
	public double getNetProfit() {
		if (isClosed())
			return getGrossSales() - (getCommission() + getSlippage());
		return 0.0;
	}

	@Override public double getGrossProfit() { return Math.max(getNetProfit(), 0.0); }
	@Override public double getGrossLoss() { return Math.min(getNetProfit(), 0.0); }

	// ----------------------------------------------------------------------

	@Override public double getPercentCommission() { return getPercent(getCommission()); }
	@Override public double getPercentNetProfit() { return getPercent(getNetProfit()); }
	@Override public double getPercentGrossProfit() { return getPercent(getGrossProfit()); }
	@Override public double getPercentGrossLoss() { return getPercent(getGrossLoss()); }

	// ----------------------------------------------------------------------

	/**
	 * <p>
	 * このポジション情報と指定されたポジション情報の順序を比較します。
	 * このポジション情報が指定されたポジション情報より小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数を返します。
	 * </p>
	 * <p>注: このクラスは <code>equals</code> と一貫性のない自然順序付けを持ちます</p>
	 * 
	 * @param o 比較対照のポジション情報
	 * @return このポジション情報が指定されたポジション情報より小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数
	 */
	@Override
	public int compareTo(final DefaultPosition o) {
		final int c;
		if (exitDate == null && o.getExitDate() == null) {
			c = 0;
		} else if (exitDate != null && o.getExitDate() == null) {
			c = 1;
		} else if (exitDate == null && o.getExitDate() != null) {
			c = -1;
		} else {
			c = this.exitDate.compareTo(o.getExitDate());
		}
		if (c != 0)
			return c;
		return entryDate.compareTo(o.getEntryDate());
	}

}
