/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.trader.domain;

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;
import javax.persistence.Version;

/**
 * An abstract root for domain objects in OpenTrader designates a {@link Stock financial instrument}
 * that can be traded. An abstract state of a tradable entity is immutable by the application. 
 * 
 * @author Pinaki Poddar
 *
 */
@SuppressWarnings("serial")
@MappedSuperclass
public abstract class Tradable implements Serializable {
    /**
     * Primary identity of a traded entity.
     * Its value is generated by the persistence provider.
     * The application must not set or change this value.
     * Hence no setter method is provided. 
     */
    @Id
    @GeneratedValue
    private Long id;
    
    /**
     * The price at which the underlying instrument be traded.
     * Must always be positive.
     */
    @Column(precision=10,scale=2)
    private double price;
    
    
    /**
     * The volume or discreet number of underlying instrument be traded.
     * Must always be positive.
     */
    private int volume;

    /**
     * The underlying instrument.
     * Must never be null.
     */
    @ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH,CascadeType.REFRESH},optional=false)
    private Stock stock;
    
    @OneToOne(cascade={CascadeType.MERGE,CascadeType.DETACH, CascadeType.REFRESH}, optional=true, 
    		fetch=FetchType.LAZY) 
    private Trade trade; 
    
   
    /**
     * A version identifier.
     * Important (and almost mandatory) for any persistent entity to be part of
     * simultaneous transaction. The persistence provider uses/updates the
     * version to detect concurrent modification of an entity in simultaneous
     * transaction.
     */
    @Version
    private int version;
    
    /**
     * A no-arg constructor to comply to both GWT compiler and OpenJPA 
     * bytecode enhancer.
     */
    protected Tradable() {
        
    }
    
    /**
     * Real constructor to be used by the concrete derivations.
     * 
     * @param stock the underlying instrument. Must not be null.
     * @param price the price. Must be positive.
     * @param volume the volume. Must be positive.
     */
    protected Tradable(Stock stock, double price, int volume) {
        if (stock == null)
            throw new IllegalArgumentException("Can not create Tradable with null stock");
        if (price <= 0.0)
            throw new IllegalArgumentException("Can not create Tradable with non-positive price " + price);
        if (volume <= 0)
            throw new IllegalArgumentException("Can not create Tradable with non-positive volume " + volume);
        
        this.stock  = stock;
        this.price  = price;
        this.volume = volume;
    }
    
    /**
     * Gets the identifier of this entity.
     * 
     * @return identifier generated by the persistence provider.
     */
    public long getId() {
        return id;
    }
    
    /**
     * Gets the underlying instrument.
     * 
     * @return the underlying instrument. Never null.
     */
    public Stock getStock() {
        return stock;
    }

    /**
     * Gets the price at which the underlying instrument be traded.
     * 
     * @return the price of the underlying instrument for trading. Always greater than zero.
     */
    public double getPrice() {
        return price;
    }

    /**
     * Gets the volume of the underlying instrument be traded.
     * 
     * @return the volume of the underlying instrument for trading. Always greater than zero.
     */
    public int getVolume() {
        return volume;
    }
    
    public abstract double getGain();
    
    /**
     * Affirms if this offer has expired.
     */
    public boolean isTraded() {
        return trade != null;
    }
    
    public void updateStock(Stock updated) {
        if (this.stock.equals(updated)) {
            this.stock = updated;
        } else {
            throw new IllegalArgumentException(this + " can not change Stock from " + this.stock 
                    + " to " + updated);
        }
    }
    
    public void setTrade(Trade t) {
        if (trade != null)
            throw new IllegalStateException(this + " has already been traded");
        this.trade = t;
    }
    
    public Trade getTrade() {
        return trade;
    }

    /**
     * The version of the entity. Updated by the persistence provider.
     * 
     * @return the current version of this entity. 
     */
    public int getVersion() {
        return version;
    }
    
    /**
     * It is important for persistence entity to overwrite the equals()
     * method, preferably based only on its primary key attribute(s). 
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }
    /**
     * It is important for persistence entity to overwrite the hashCode()
     * method, preferably based only on its primary key attribute(s). 
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Tradable other = (Tradable) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
    
    public String toString() {
    	String typeName = getClass().getName();
    	return typeName.substring(typeName.lastIndexOf('.')+1) + "-" + id;
    }
}
