/**
 *  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.integration.daytrader;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;

import org.apache.openjpa.lib.log.Log;

// import org.apache.geronimo.samples.daytrader.beans.AccountDataBean;
// import org.apache.geronimo.samples.daytrader.beans.AccountProfileDataBean;
// import org.apache.geronimo.samples.daytrader.beans.HoldingDataBean;
// import org.apache.geronimo.samples.daytrader.beans.OrderDataBean;
// import org.apache.geronimo.samples.daytrader.beans.QuoteDataBean;
// import org.apache.geronimo.samples.daytrader.core.FinancialUtils;
// import org.apache.geronimo.samples.daytrader.core.MarketSummaryDataBean;
// import org.apache.geronimo.samples.daytrader.core.RunStatsDataBean;
// import org.apache.geronimo.samples.daytrader.core.TradeDBServices;
// import org.apache.geronimo.samples.daytrader.core.TradeServices;
// import org.apache.geronimo.samples.daytrader.util.Log;
// import org.apache.geronimo.samples.daytrader.util.TradeConfig;

/**
 * TradeJPADirect uses JPA to implement the business methods of the Trade online
 * broker application. These business methods represent the features and
 * operations that can be performed by customers of the brokerage such as login,
 * logout, get a stock quote, buy or sell a stock, etc. and are specified in the
 * {@link org.apache.geronimo.samples.daytrader.TradeServices} interface
 *
 * Note: In order for this class to be thread-safe, a new TradeJPA must be
 * created for each call to a method from the TradeInterface interface.
 * Otherwise, pooled connections may not be released.
 *
 * @see org.apache.geronimo.samples.daytrader.TradeServices
 *
 */

// public class TradeJPADirect implements TradeServices, TradeDBServices {
public class TradeJPADirect {

    @PersistenceUnit(unitName="daytrader")
    private static EntityManagerFactory emf;

    private static BigDecimal ZERO = new BigDecimal(0.0);

    private static boolean initialized = false;

    protected static Log log = null;
    boolean _poolEm = true;

    EntityManager _em;

    public EntityManager getEm(EntityManagerFactory emf) {
        if (_poolEm) {
            if (_em == null)
                _em = emf.createEntityManager();
            return _em;
        }
        return emf.createEntityManager();
    }

    public void putEm(EntityManager em){
        if(_poolEm)
            em.clear();
        else{
            if(em.isOpen())
                em.close();
        }
    }


    // constructor for OpenJPA junit tests
    public TradeJPADirect(Log log, EntityManagerFactory emf, boolean poolEm) {
        TradeJPADirect.log = log;
        TradeJPADirect.emf = emf;
        _poolEm = poolEm;
        if (!initialized)
            init();
    }

    public static synchronized void init() {
        if (initialized)
            return;

        TradeConfig.setPublishQuotePriceChange(false);
        initialized = true;
    }

    public static void destroy() {
        try {
            if (!initialized)
                return;
            log.trace("TradeJPADirect:destroy");
        }
        catch (Exception e) {
            log.error("TradeJPADirect:destroy", e);
        }

    }

    public MarketSummaryDataBean getMarketSummary() {
        MarketSummaryDataBean marketSummaryData;

        /*
         * Creating entiManager
         */
        EntityManager entityManager = getEm(emf);

        try {
            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:getMarketSummary -- getting market summary");

            // Find Trade Stock Index Quotes (Top 100 quotes)
            // ordered by their change in value
            Collection<QuoteDataBean> quotes;

            Query query = entityManager.createNamedQuery("quoteejb.quotesByChange");
            quotes = query.getResultList();

            QuoteDataBean[] quoteArray = (QuoteDataBean[]) quotes.toArray(new QuoteDataBean[quotes.size()]);
            ArrayList<QuoteDataBean> topGainers = new ArrayList<>(
                                                                              5);
            ArrayList<QuoteDataBean> topLosers = new ArrayList<>(5);
            // BigDecimal TSIA = FinancialUtils.ZERO;
            BigDecimal TSIA = (new BigDecimal(0.00)).setScale(2);
            // BigDecimal openTSIA = FinancialUtils.ZERO;
            BigDecimal openTSIA = (new BigDecimal(0.00)).setScale(2);
            double totalVolume = 0.0;

            if (quoteArray.length > 5) {
                for (int i = 0; i < 5; i++)
                    topGainers.add(quoteArray[i]);
                for (int i = quoteArray.length - 1; i >= quoteArray.length - 5; i--)
                    topLosers.add(quoteArray[i]);

                for (QuoteDataBean quote : quoteArray) {
                    BigDecimal price = quote.getPrice();
                    BigDecimal open = quote.getOpen();
                    double volume = quote.getVolume();
                    TSIA = TSIA.add(price);
                    openTSIA = openTSIA.add(open);
                    totalVolume += volume;
                }
                TSIA = TSIA.divide(new BigDecimal(quoteArray.length),
                    // FinancialUtils.ROUND);
                    BigDecimal.ROUND_HALF_UP);
                openTSIA = openTSIA.divide(new BigDecimal(quoteArray.length),
                    // FinancialUtils.ROUND);
                    BigDecimal.ROUND_HALF_UP);
            }

            marketSummaryData = new MarketSummaryDataBean(TSIA, openTSIA,
                                                          totalVolume, topGainers, topLosers);
        }
        catch (Exception e) {
            log.error("TradeJPADirect:getMarketSummary", e);
            throw new RuntimeException("TradeJPADirect:getMarketSummary -- error ", e);
        } finally {
            putEm(entityManager);
        }

        return marketSummaryData;
    }

    public OrderDataBean buy(String userID, String symbol, double quantity, int orderProcessingMode) {
        OrderDataBean order = null;
        BigDecimal total;
        /*
         * creating entitymanager
         */
        EntityManager entityManager = getEm(emf);

        try {
            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:buy - userID=" + userID + " symbol=" + symbol + " quantity=" + quantity);

            entityManager.getTransaction().begin();

            AccountProfileDataBean profile = entityManager.find(AccountProfileDataBean.class, userID);
            AccountDataBean account = profile.getAccount();
            QuoteDataBean quote = entityManager.find(QuoteDataBean.class, symbol);
            HoldingDataBean holding = null; // The holding will be created by this buy order
            order = createOrder(account, quote, holding, "buy", quantity, entityManager);
            // order = createOrder(account, quote, holding, "buy", quantity);
            // UPDATE - account should be credited during completeOrder
            BigDecimal price = quote.getPrice();
            BigDecimal orderFee = order.getOrderFee();
            BigDecimal balance = account.getBalance();
            total = (new BigDecimal(quantity).multiply(price)).add(orderFee);
            account.setBalance(balance.subtract(total));

            // commit the transaction before calling completeOrder
            entityManager.getTransaction().commit();

            if (orderProcessingMode == TradeConfig.SYNCH)
                completeOrder(order.getOrderID(), false);
            else if (orderProcessingMode == TradeConfig.ASYNCH_2PHASE)
                queueOrder(order.getOrderID(), true);
        } catch (Exception e) {
            log.error("TradeJPADirect:buy(" + userID + "," + symbol + "," + quantity + ") --> failed", e);
            /* On exception - cancel the order */
            // TODO figure out how to do this with JPA
            if (order != null)
                order.cancel();
            if (entityManager.getTransaction().isActive())
                entityManager.getTransaction().rollback();
            // throw new EJBException(e);
            throw new RuntimeException(e);
        } finally {
            if (entityManager != null) {
                putEm(entityManager);
                entityManager = null;
            }
        }
        // after the purchase or sell of a stock, update the stocks volume and
        // price
        updateQuotePriceVolume(symbol, TradeConfig.getRandomPriceChangeFactor(), quantity);
        return order;
    }

    public OrderDataBean sell(String userID, Integer holdingID,
                              int orderProcessingMode) {
        EntityManager entityManager = getEm(emf);

        OrderDataBean order = null;
        BigDecimal total;
        try {
            entityManager.getTransaction().begin();
            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:sell - userID=" + userID + " holding=" + holdingID);

            AccountProfileDataBean profile = entityManager.find(AccountProfileDataBean.class, userID);

            AccountDataBean account = profile.getAccount();
            HoldingDataBean holding = entityManager.find(HoldingDataBean.class, holdingID);

            if (holding == null) {
                log.error("TradeJPADirect:sell User " + userID
                          + " attempted to sell holding " + holdingID
                          + " which has already been sold");

                OrderDataBean orderData = new OrderDataBean();
                orderData.setOrderStatus("cancelled");

                entityManager.persist(orderData);
                entityManager.getTransaction().commit();
                return orderData;
            }

            QuoteDataBean quote = holding.getQuote();
            double quantity = holding.getQuantity();

            order = createOrder(account, quote, holding, "sell", quantity,
                                entityManager);
            // UPDATE the holding purchase data to signify this holding is
            // "inflight" to be sold
            // -- could add a new holdingStatus attribute to holdingEJB
            holding.setPurchaseDate(new java.sql.Timestamp(0));

            // UPDATE - account should be credited during completeOrder
            BigDecimal price = quote.getPrice();
            BigDecimal orderFee = order.getOrderFee();
            BigDecimal balance = account.getBalance();
            total = (new BigDecimal(quantity).multiply(price)).subtract(orderFee);

            account.setBalance(balance.add(total));

            // commit the transaction before calling completeOrder
            entityManager.getTransaction().commit();

            if (orderProcessingMode == TradeConfig.SYNCH)
                completeOrder(order.getOrderID(), false);
            else if (orderProcessingMode == TradeConfig.ASYNCH_2PHASE)
                queueOrder(order.getOrderID(), true);

        }
        catch (Exception e) {
            log.error("TradeJPADirect:sell(" + userID + "," + holdingID + ") --> failed", e);
            // TODO figure out JPA cancel
            if (order != null)
                order.cancel();
            entityManager.getTransaction().rollback();
            throw new RuntimeException("TradeJPADirect:sell(" + userID + "," + holdingID + ")", e);
        } finally {
            if (entityManager != null) {
                putEm(entityManager);
            }
        }

        if (!(order.getOrderStatus().equalsIgnoreCase("cancelled")))
            //after the purchase or sell of a stock, update the stocks volume and price
            updateQuotePriceVolume(order.getSymbol(), TradeConfig.getRandomPriceChangeFactor(), order.getQuantity());

        return order;
    }

    public void queueOrder(Integer orderID, boolean twoPhase) {
        log.error("TradeJPADirect:queueOrder() not implemented for this runtime mode");
        throw new UnsupportedOperationException("TradeJPADirect:queueOrder() not implemented for this runtime mode");
    }

    public OrderDataBean completeOrder(Integer orderID, boolean twoPhase)
    throws Exception {
        EntityManager entityManager = getEm(emf);
        OrderDataBean order = null;

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:completeOrder - orderId=" + orderID + " twoPhase=" + twoPhase);

        try {
            order = entityManager.find(OrderDataBean.class, orderID);
            order.getQuote();

            if (order == null) {
                log.error("TradeJPADirect:completeOrder -- Unable to find orderID=" + orderID);
                return null;
            }

            if (order.isCompleted()) {
                throw new RuntimeException("Error: attempt to complete Order that is already completed\n" + order);
            }

            AccountDataBean account = order.getAccount();
            QuoteDataBean quote = order.getQuote();
            HoldingDataBean holding = order.getHolding();
            BigDecimal price = order.getPrice();
            double quantity = order.getQuantity();

            String userID = account.getProfile().getUserID();

            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:completeOrder--> Completing Order "
                          + order.getOrderID() + "\n\t Order info: " + order
                          + "\n\t Account info: " + account + "\n\t Quote info: "
                          + quote + "\n\t Holding info: " + holding);

            HoldingDataBean newHolding = null;
            if (order.isBuy()) {
                /*
                 * Complete a Buy operation - create a new Holding for the Account -
                 * deduct the Order cost from the Account balance
                 */
                newHolding = createHolding(account, quote, quantity, price, entityManager);
            }

            entityManager.getTransaction().begin();

            if (newHolding != null) {
                order.setHolding(newHolding);
            }

            if (order.isSell()) {
                /*
                 * Complete a Sell operation - remove the Holding from the Account -
                 * deposit the Order proceeds to the Account balance
                 */
                if (holding == null) {
                    log.error("TradeJPADirect:completeOrder -- Unable to sell order " + order.getOrderID()
                            + ", holding already sold (null)");
                    order.cancel();
                    entityManager.getTransaction().commit();
                    return order;
                }
                else {
                    entityManager.remove(holding);
                    order.setHolding(null);
                }
            }

            order.setOrderStatus("closed");
            order.setCompletionDate(new java.sql.Timestamp(System.currentTimeMillis()));
            entityManager.persist(order);
            entityManager.getTransaction().commit();

            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:completeOrder--> Completed Order "
                          + order.getOrderID() + "\n\t Order info: " + order
                          + "\n\t Account info: " + account + "\n\t Quote info: "
                          + quote + "\n\t Holding info: " + newHolding);

        } catch (Exception e) {
            log.error("TradeJPADirect:completeOrder() failed for orderID=" + orderID, e);
            if (entityManager.getTransaction().isActive())
                entityManager.getTransaction().rollback();
        } finally {
            if (entityManager != null) {
                putEm(entityManager);
            }
        }

        return order;
    }

    public void cancelOrder(Integer orderID, boolean twoPhase) {
        EntityManager entityManager = getEm(emf);

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:cancelOrder - orderId=" + orderID + " twoPhase=" + twoPhase);

        OrderDataBean order = entityManager.find(OrderDataBean.class, orderID);
        /*
         * managed transaction
         */
        try {
            entityManager.getTransaction().begin();
            order.cancel();
            entityManager.getTransaction().commit();
        }
        catch (Exception e) {
            entityManager.getTransaction().rollback();
        } finally {
            putEm(entityManager);
        }
    }

    public void orderCompleted(String userID, Integer orderID) {
        if (log.isTraceEnabled())
            log.trace("TradeAction:orderCompleted - userID=" + userID + " orderID=" + orderID);
    }

    public Collection<OrderDataBean> getOrders(String userID) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getOrders - userID=" + userID);
        EntityManager entityManager = getEm(emf);
        AccountProfileDataBean profile = entityManager.find(
                                                           AccountProfileDataBean.class, userID);
        AccountDataBean account = profile.getAccount();
        putEm(entityManager);
        return account.getOrders();
    }

    public Collection<OrderDataBean> getClosedOrders(String userID) {

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getClosedOrders - userID=" + userID);
        EntityManager entityManager = getEm(emf);

        try {

            // Get the primary keys for all the closed Orders for this
            // account.
            /*
             * managed transaction
             */
            entityManager.getTransaction().begin();
            Query query = entityManager
                          .createNamedQuery("orderejb.closedOrders");
            query.setParameter("userID", userID);

            entityManager.getTransaction().commit();
            Collection results = query.getResultList();
            Iterator itr = results.iterator();
            // entityManager.joinTransaction();
            // Spin through the orders to populate the lazy quote fields
            while (itr.hasNext()) {
                OrderDataBean thisOrder = (OrderDataBean) itr.next();
                thisOrder.getQuote();
            }

            if (TradeConfig.jpaLayer == TradeConfig.OPENJPA) {
                Query updateStatus = entityManager
                                     .createNamedQuery("orderejb.completeClosedOrders");
                /*
                 * managed transaction
                 */
                try {
                    entityManager.getTransaction().begin();
                    updateStatus.setParameter("userID", userID);

                    updateStatus.executeUpdate();
                    entityManager.getTransaction().commit();
                }
                catch (Exception e) {
                    entityManager.getTransaction().rollback();
                    putEm(entityManager);
                }
            }
            else if (TradeConfig.jpaLayer == TradeConfig.HIBERNATE) {
                /*
                 * Add logic to do update orders operation, because JBoss5'
                 * Hibernate 3.3.1GA DB2Dialect and MySQL5Dialect do not work
                 * with annotated query "orderejb.completeClosedOrders" defined
                 * in OrderDatabean
                 */
                Query findaccountid = entityManager.createNativeQuery(
                                            "select "
                                            + "a.ACCOUNTID, "
                                            + "a.LOGINCOUNT, "
                                            + "a.LOGOUTCOUNT, "
                                            + "a.LASTLOGIN, "
                                            + "a.CREATIONDATE, "
                                            + "a.BALANCE, "
                                            + "a.OPENBALANCE, "
                                            + "a.PROFILE_USERID "
                                            + "from accountejb a where a.profile_userid = ?",
                                            org.apache.openjpa.integration.daytrader.AccountDataBean.class);
                findaccountid.setParameter(1, userID);
                AccountDataBean account = (AccountDataBean) findaccountid.getSingleResult();
                Integer accountid = account.getAccountID();
                Query updateStatus = entityManager.createNativeQuery(
                        "UPDATE orderejb o SET o.orderStatus = 'completed' WHERE "
                        + "o.orderStatus = 'closed' AND o.ACCOUNT_ACCOUNTID  = ?");
                updateStatus.setParameter(1, accountid);
                updateStatus.executeUpdate();
            }
            if (entityManager != null) {
                putEm(entityManager);
            }
            return results;
        } catch (Exception e) {
            log.error("TradeJPADirect.getClosedOrders", e);
            putEm(entityManager);
            throw new RuntimeException("TradeJPADirect.getClosedOrders - error", e);

        } finally {
            if (entityManager != null) {
                putEm(entityManager);
            }
        }
    }

    public QuoteDataBean createQuote(String symbol, String companyName,
                                     BigDecimal price) {
        EntityManager entityManager = getEm(emf);
        QuoteDataBean quote = null;
        try {
            quote = new QuoteDataBean(symbol, companyName, 0, price, price, price, price, 0);
            /*
             * managed transaction
             */
            entityManager.getTransaction().begin();
            entityManager.persist(quote);
            entityManager.getTransaction().commit();

            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:createQuote-->" + quote);
            return quote;
        } catch (Exception e) {
            log.error("TradeJPADirect:createQuote -- exception creating Quote=" + quote, e);
            if (entityManager.getTransaction().isActive())
                entityManager.getTransaction().rollback();
            throw new RuntimeException(e);
        } finally {
            if (entityManager != null) {
                putEm(entityManager);
            }
        }

    }

    public QuoteDataBean getQuote(String symbol) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getQuote - symbol=" + symbol);
        EntityManager entityManager = getEm(emf);

        QuoteDataBean qdb = entityManager.find(QuoteDataBean.class, symbol);

        if (entityManager != null) {
            putEm(entityManager);
        }
        return qdb;
    }

    public Collection<QuoteDataBean> getAllQuotes() {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getAllQuotes");
        EntityManager entityManager = getEm(emf);

        Query query = entityManager.createNamedQuery("quoteejb.allQuotes");

        if (entityManager != null) {
            putEm(entityManager);

        }
        return query.getResultList();
    }

    public QuoteDataBean updateQuotePriceVolume(String symbol,
                                                BigDecimal changeFactor, double sharesTraded) {
        if (!TradeConfig.getUpdateQuotePrices())
            return new QuoteDataBean();

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:updateQuote - symbol=" + symbol + "changeFactor=" + changeFactor);

        /*
         * Add logic to determine JPA layer, because JBoss5' Hibernate 3.3.1GA
         * DB2Dialect and MySQL5Dialect do not work with annotated query
         * "quoteejb.quoteForUpdate" defined in QuoteDatabean
         */
        EntityManager entityManager = getEm(emf);
        QuoteDataBean quote = null;
        if (TradeConfig.jpaLayer == TradeConfig.HIBERNATE) {
            quote = entityManager.find(QuoteDataBean.class, symbol);
        } else if (TradeConfig.jpaLayer == TradeConfig.OPENJPA) {

            Query q = entityManager.createNamedQuery("quoteejb.quoteForUpdate");
            q.setParameter(1, symbol);

            quote = (QuoteDataBean) q.getSingleResult();
        }

        BigDecimal oldPrice = quote.getPrice();

        if (quote.getPrice().equals(TradeConfig.PENNY_STOCK_PRICE)) {
            changeFactor = TradeConfig.PENNY_STOCK_RECOVERY_MIRACLE_MULTIPLIER;
        }

        BigDecimal newPrice = changeFactor.multiply(oldPrice).setScale(2, BigDecimal.ROUND_HALF_UP);

        /*
         * managed transaction
         */

        try {

            quote.setPrice(newPrice);
            quote.setVolume(quote.getVolume() + sharesTraded);
            quote.setChange((newPrice.subtract(quote.getOpen()).doubleValue()));

            entityManager.getTransaction().begin();
            entityManager.merge(quote);
            entityManager.getTransaction().commit();
        }
        catch (Exception e) {
            entityManager.getTransaction().rollback();
        } finally {
            if (entityManager != null) {
                putEm(entityManager);
            }
        }

        this.publishQuotePriceChange(quote, oldPrice, changeFactor, sharesTraded);

        return quote;
    }

    public Collection<HoldingDataBean> getHoldings(String userID) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getHoldings - userID=" + userID);
        EntityManager entityManager = getEm(emf);
        /*
         * managed transaction
         */
        entityManager.getTransaction().begin();

        Query query = entityManager.createNamedQuery("holdingejb.holdingsByUserID");
        query.setParameter("userID", userID);

        entityManager.getTransaction().commit();
        Collection<HoldingDataBean> holdings = query.getResultList();
        /*
         * Inflate the lazy data memebers
         */
        Iterator itr = holdings.iterator();
        while (itr.hasNext()) {
            ((HoldingDataBean) itr.next()).getQuote();
        }

        putEm(entityManager);
        return holdings;
    }

    public HoldingDataBean getHolding(Integer holdingID) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getHolding - holdingID=" + holdingID);
        HoldingDataBean holding;
        EntityManager entityManager = getEm(emf);
        holding = entityManager.find(HoldingDataBean.class, holdingID);
        putEm(entityManager);
        return holding;
    }

    public AccountDataBean getAccountData(String userID) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getAccountData - userID=" + userID);

        EntityManager entityManager = getEm(emf);

        AccountProfileDataBean profile = entityManager.find(AccountProfileDataBean.class, userID);
        /*
         * Inflate the lazy data memebers
         */
        AccountDataBean account = profile.getAccount();
        account.getProfile();

        // Added to populate transient field for account
        account.setProfileID(profile.getUserID());
        putEm(entityManager);

        return account;
    }

    public AccountProfileDataBean getAccountProfileData(String userID) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:getProfileData - userID=" + userID);
        EntityManager entityManager = getEm(emf);

        AccountProfileDataBean apb = entityManager.find(AccountProfileDataBean.class, userID);
        putEm(entityManager);
        return apb;
    }

    public AccountProfileDataBean updateAccountProfile(AccountProfileDataBean profileData) {

        EntityManager entityManager = getEm(emf);

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:updateAccountProfileData - profileData=" + profileData);
        /*
         * // Retrieve the previous account profile in order to get account
         * data... hook it into new object AccountProfileDataBean temp =
         * entityManager.find(AccountProfileDataBean.class,
         * profileData.getUserID()); // In order for the object to merge
         * correctly, the account has to be hooked into the temp object... // -
         * may need to reverse this and obtain the full object first
         *
         * profileData.setAccount(temp.getAccount());
         *
         * //TODO this might not be correct temp =
         * entityManager.merge(profileData); //System.out.println(temp);
         */

        AccountProfileDataBean temp = entityManager.find(AccountProfileDataBean.class, profileData.getUserID());
        temp.setAddress(profileData.getAddress());
        temp.setPassword(profileData.getPassword());
        temp.setFullName(profileData.getFullName());
        temp.setCreditCard(profileData.getCreditCard());
        temp.setEmail(profileData.getEmail());
        /*
         * Managed Transaction
         */
        try {

            entityManager.getTransaction().begin();
            entityManager.merge(temp);
            entityManager.getTransaction().commit();
        }
        catch (Exception e) {
            entityManager.getTransaction().rollback();
        } finally {
            putEm(entityManager);
        }

        return temp;
    }

    public AccountDataBean login(String userID, String password)
    throws Exception {

        EntityManager entityManager = getEm(emf);

        AccountProfileDataBean profile = entityManager.find(AccountProfileDataBean.class, userID);

        if (profile == null) {
            throw new RuntimeException("No such user: " + userID);
        }
        /*
         * Managed Transaction
         */
        entityManager.getTransaction().begin();
        entityManager.merge(profile);

        AccountDataBean account = profile.getAccount();

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:login - userID=" + userID + " password=" + password);

        account.login(password);
        entityManager.getTransaction().commit();
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:login(" + userID + "," + password + ") success" + account);
        putEm(entityManager);
        return account;
    }

    public void logout(String userID) {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:logout - userID=" + userID);
        EntityManager entityManager = getEm(emf);

        AccountProfileDataBean profile = entityManager.find(AccountProfileDataBean.class, userID);
        AccountDataBean account = profile.getAccount();

        /*
         * Managed Transaction
         */
        try {
            entityManager.getTransaction().begin();
            account.logout();
            entityManager.getTransaction().commit();
        }
        catch (Exception e) {
            entityManager.getTransaction().rollback();
        } finally {
            putEm(entityManager);
        }

        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:logout(" + userID + ") success");
    }

    public AccountDataBean register(String userID, String password, String fullname,
                                    String address, String email, String creditcard,
                                    BigDecimal openBalance) {
        AccountDataBean account = null;
        AccountProfileDataBean profile = null;
        EntityManager entityManager = getEm(emf);

        if (log.isTraceEnabled()) {
            //Log.trace("TradeJPADirect:register", userID, password, fullname, address, email, creditcard, openBalance);
            log.trace("TradeJPADirect:register - userID=" + userID);
        }

        // Check to see if a profile with the desired userID already exists
        profile = entityManager.find(AccountProfileDataBean.class, userID);
        if (profile != null) {
            log.error("Failed to register new Account - AccountProfile with userID(" + userID + ") already exists");
            return null;
        }
        else {
        	profile = new AccountProfileDataBean(userID, password, fullname,
                                                 address, email, creditcard);
            account = new AccountDataBean(0, 0, null, new Timestamp(System.currentTimeMillis()),
                    openBalance, openBalance, userID);
            profile.setAccount(account);
            account.setProfile(profile);
            /*
             * managed Transaction
             */
            try {
                entityManager.getTransaction().begin();
                entityManager.persist(profile);
                entityManager.persist(account);
                entityManager.getTransaction().commit();
            }
            catch (Exception e) {
                log.error("Failed to create account and profile for userId=" + userID, e);
                entityManager.getTransaction().rollback();
            } finally {
                putEm(entityManager);
            }

        }

        return account;
    }

    // @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    //public RunStatsDataBean resetTrade(boolean deleteAll) throws Exception {
    public void resetTrade(boolean deleteAll) throws Exception {
        // return(new TradeJDBCDirect(false)).resetTrade(deleteAll);
        new RuntimeException("TradeJPADirect:resetTrade - not implemented in JPA mode");
    }

    /*
     * NO LONGER USE
     */

    private void publishQuotePriceChange(QuoteDataBean quote,
                                         BigDecimal oldPrice, BigDecimal changeFactor, double sharesTraded) {
        if (!TradeConfig.getPublishQuotePriceChange())
            return;
        log.error("TradeJPADirect:publishQuotePriceChange - is not implemented for this runtime mode");
        throw new UnsupportedOperationException(
            "TradeJPADirect:publishQuotePriceChange - is not implemented for this runtime mode");
    }

    /*
     * new Method() that takes EntityManager as a parameter
     */
    private OrderDataBean createOrder(AccountDataBean account,
                                      QuoteDataBean quote, HoldingDataBean holding, String orderType,
                                      double quantity, EntityManager entityManager) {
        OrderDataBean order;
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:createOrder(orderID=" + " account="
                      + ((account == null) ? null : account.getAccountID())
                      + " quote=" + ((quote == null) ? null : quote.getSymbol())
                      + " orderType=" + orderType + " quantity=" + quantity);
        try {
            order = new OrderDataBean(orderType,
                                      "open",
                                      new Timestamp(System.currentTimeMillis()),
                                      null,
                                      quantity,
                                      // quote.getPrice().setScale(FinancialUtils.SCALE, FinancialUtils.ROUND),
                                      quote.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP),
                                      TradeConfig.getOrderFee(orderType),
                                      account,
                                      quote,
                                      holding);
                entityManager.persist(order);
        }
        catch (Exception e) {
            log.error("TradeJPADirect:createOrder -- failed to create Order", e);
            throw new RuntimeException("TradeJPADirect:createOrder -- failed to create Order", e);
        }
        return order;
    }

    private HoldingDataBean createHolding(AccountDataBean account,
                                          QuoteDataBean quote, double quantity, BigDecimal purchasePrice,
                                          EntityManager entityManager) throws Exception {
        HoldingDataBean newHolding = new HoldingDataBean(quantity,
                                                         purchasePrice, new Timestamp(System.currentTimeMillis()),
                                                         account, quote);
        try {
            /*
             * manage transactions
             */
            entityManager.getTransaction().begin();
            entityManager.persist(newHolding);
            entityManager.getTransaction().commit();
            if (log.isTraceEnabled())
                log.trace("TradeJPADirect:createHolding(account=" + ((account == null) ? null : account.getAccountID())
                          + " quote=" + ((quote == null) ? null : quote.getSymbol())
                          + " quantity=" + quantity + " purchasePrice=" + purchasePrice + ")");
        }
        catch (Exception e) {
            log.error("createHolding() failed for newHolding=" + newHolding, e);
            entityManager.getTransaction().rollback();
        }
        return newHolding;
    }

    public double investmentReturn(double investment, double NetValue)
    throws Exception {
        if (log.isTraceEnabled())
            log.trace("TradeJPADirect:investmentReturn");

        double diff = NetValue - investment;
        double ir = diff / investment;
        return ir;
    }

    public QuoteDataBean pingTwoPhase(String symbol) throws Exception {
        log.error("TradeJPADirect:pingTwoPhase - is not implemented for this runtime mode");
        throw new UnsupportedOperationException(
                "TradeJPADirect:pingTwoPhase - is not implemented for this runtime mode");
    }

    class quotePriceComparator implements java.util.Comparator {
        @Override
        public int compare(Object quote1, Object quote2) {
            double change1 = ((QuoteDataBean) quote1).getChange();
            double change2 = ((QuoteDataBean) quote2).getChange();
            return new Double(change2).compareTo(change1);
        }
    }

    /**
     * TradeBuildDB needs this abstracted method
     */
    public String checkDBProductName() throws Exception {
        log.error("TradeJPADirect:checkDBProductName - is not implemented in JPA mode");
        // return(new TradeJDBCDirect(false)).checkDBProductName();
        new RuntimeException("TradeJPADirect:checkDBProductName - is not implemented in JPA mode");
        return null;
    }

    /**
     * TradeBuildDB needs this abstracted method
     */
    public boolean recreateDBTables(Object[] sqlBuffer, java.io.PrintWriter out)
    throws Exception {
        log.error("TradeJPADirect:recreateDBTables - is not implemented in JPA mode");
        //return(new TradeJDBCDirect(false)).recreateDBTables(sqlBuffer, out);
        new RuntimeException("TradeJPADirect:recreateDBTables - is not implemented in JPA mode");
        return false;
    }

    /**
     * Get mode - returns the persistence mode (TradeConfig.JPA)
     *
     * @return int mode
     */
    public int getMode() {
        return TradeConfig.JPA;
    }

}
