/*
 * Copyright (c) 2013-2019 Scott Oaks. All rights reserved.
 */

package net.sdo.stockimpl;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.metamodel.Metamodel;

public class MockStockPriceEntityManagerFactory implements EntityManagerFactory {
    static private EntityTransaction dummyTransaction = new EntityTransaction() {
        @Override
        public void begin() {}

        @Override
        public void commit() {}

        @Override
        public void rollback() {}

        @Override
        public void setRollbackOnly() {}

        @Override
        public boolean getRollbackOnly() { return false; }

        @Override
        public boolean isActive() { return false; }
    };

    @Override
    public EntityManager createEntityManager(SynchronizationType st) {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public EntityManager createEntityManager(SynchronizationType st, Map map) {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public void addNamedQuery(String string, Query query) {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public <T> T unwrap(Class<T> type) {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public <T> void addNamedEntityGraph(String string, EntityGraph<T> eg) {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    private static class DataHolder {
        public Random random = ThreadLocalRandom.current();
        public String symbol;
        public BigDecimal lastClose;
        public Calendar calendar = Calendar.getInstance();
    }
    
    private static ThreadLocal<DataHolder> tlDataHolder =
           new ThreadLocal<DataHolder>() {
        @Override
        public DataHolder initialValue() {
            return new DataHolder();
        }
    };
    
    class MockEntityManager implements EntityManager {
        @Override
        public void persist(Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> T merge(T t) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void remove(Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> T find(Class<T> type, Object o) {
            return findWithHistory(tlDataHolder.get(), o);
        }

        protected <T> T findWithHistory(DataHolder dh, Object o) {
            StockPricePK pk = (StockPricePK) o;
            StockPriceEagerLazyImpl sp = new StockPriceEagerLazyImpl(pk);
            dh.calendar.setTime(pk.getDate());
            if (dh.calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY ||
                dh.calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
                return null;
            }
    
            if (dh.symbol == null || !dh.symbol.equals(pk.getSymbol())) {
                dh.symbol = pk.getSymbol();
                sp.setOpeningPrice(
                        new BigDecimal(
                             dh.random.nextDouble() * 100).
                                 setScale(2, BigDecimal.ROUND_HALF_UP));
            }
            else {
                sp.setOpeningPrice(dh.lastClose);
            }
	    double open = sp.getOpeningPrice().doubleValue();
            double pctChange = dh.random.nextDouble() * dh.random.nextDouble() / 10.;
            pctChange *= (dh.random.nextDouble() < 0.53) ? 0.1 : -0.1;
	    pctChange += 1.0;
	    double close = sp.getOpeningPrice().doubleValue() * pctChange;
	    if (close < 0.01) {
	        close = 0.01;
	    }
            sp.setClosingPrice(new BigDecimal(close));
            
	    double max = (open > close) ? open : close;
            pctChange = dh.random.nextDouble() * dh.random.nextDouble() * 0.01;
	    pctChange += 1.0;
	    double high = max * pctChange;
            sp.setHigh(new BigDecimal(high));
            
            pctChange = dh.random.nextDouble() * dh.random.nextDouble() * 0.1;
	    pctChange -= 1.0;
	    double min = (open < close) ? open : close;
	    double low = min * pctChange;
            sp.setLow(new BigDecimal(low));
            dh.lastClose = sp.getClosingPrice();
        
            return (T) sp;
        }

        @Override
        public <T> T find(Class<T> type, Object o, Map<String, Object> map) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> T find(Class<T> type, Object o, LockModeType lmt) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> T find(Class<T> type, Object o,
                  LockModeType lmt, Map<String, Object> map) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> T getReference(Class<T> type, Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void flush() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void setFlushMode(FlushModeType fmt) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public FlushModeType getFlushMode() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void lock(Object o, LockModeType lmt) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void lock(Object o, LockModeType lmt, Map<String, Object> map) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void refresh(Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void refresh(Object o, Map<String, Object> map) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void refresh(Object o, LockModeType lmt) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void refresh(Object o, LockModeType lmt, Map<String, Object> map) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void detach(Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public boolean contains(Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public LockModeType getLockMode(Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void setProperty(String string, Object o) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Map<String, Object> getProperties() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createQuery(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> TypedQuery<T> createQuery(CriteriaQuery<T> cq) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> TypedQuery<T> createQuery(String string, Class<T> type) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createNamedQuery(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> TypedQuery<T> createNamedQuery(String string, Class<T> type) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createNativeQuery(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createNativeQuery(String string, Class type) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createNativeQuery(String string, String string1) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void joinTransaction() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> T unwrap(Class<T> type) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Object getDelegate() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public void close() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public boolean isOpen() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public EntityTransaction getTransaction() {
            return dummyTransaction;
        }

        @Override
        public EntityManagerFactory getEntityManagerFactory() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public CriteriaBuilder getCriteriaBuilder() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Metamodel getMetamodel() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createQuery(CriteriaUpdate cu) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public Query createQuery(CriteriaDelete cd) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public StoredProcedureQuery createNamedStoredProcedureQuery(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public StoredProcedureQuery createStoredProcedureQuery(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public StoredProcedureQuery createStoredProcedureQuery(String string, Class... types) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public StoredProcedureQuery createStoredProcedureQuery(String string, String... strings) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public boolean isJoinedToTransaction() {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> EntityGraph<T> createEntityGraph(Class<T> type) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public EntityGraph<?> createEntityGraph(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public EntityGraph<?> getEntityGraph(String string) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }

        @Override
        public <T> List<EntityGraph<? super T>> getEntityGraphs(Class<T> type) {
            throw new UnsupportedOperationException("Nie obsługiwany.");
        }
    }

    class StdRandomMockEntityManager extends MockEntityManager {
        @Override
        public <T> T find(Class<T> type, Object o) {
            DataHolder dh = tlDataHolder.get();
            dh.random = new Random();
            dh.calendar = Calendar.getInstance();
            return findWithHistory(dh, o);
        }
    }
    
    private String className;
    public MockStockPriceEntityManagerFactory(String s) {
        className = s;
    }

    @Override
    public EntityManager createEntityManager() {
        switch(className) {
            case "MockEntityManager": return new MockEntityManager();
            case "StdRandomMockEntityManager": return new StdRandomMockEntityManager();
            default: throw new IllegalArgumentException("Brak definicji klasy " + className);
        }
    }

    @Override
    public EntityManager createEntityManager(Map map) {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public CriteriaBuilder getCriteriaBuilder() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public Metamodel getMetamodel() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public boolean isOpen() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public void close() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public Map<String, Object> getProperties() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public Cache getCache() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }

    @Override
    public PersistenceUnitUtil getPersistenceUnitUtil() {
        throw new UnsupportedOperationException("Nie obsługiwany.");
    }
}
