package org.knowm.xchange.simulated;

import java.io.IOException;

import org.knowm.xchange.BaseExchange;
import org.knowm.xchange.ExchangeSpecification;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.meta.CurrencyPairMetaData;
import org.knowm.xchange.exceptions.CurrencyPairNotValidException;

import si.mazi.rescu.SynchronizedValueFactory;

/**
 * A simple, in-memory implementation which mocks out the main elements of the XChange
 * generic API in a consistent way.  The effect is to create a virtual "exchange"
 * that you can connect to from multiple threads and simulate a real exchange. Intended
 * for integration testing of higher order components.
 *
 * <p>This is not remotely suitable for use as a real-world exchange. The
 * concurrency model is extremely slow and most data transforms involve mutation
 * and no transactional control.  If any errors occur midway through a trade, they
 * are likely to leave the system in an inconsistent state. And nothing gets saved
 * to a database anyway.<p>
 *
 * <p>If you start using this for running a real exchange, you will
 * <a href="https://www.reddit.com/r/nanotrade/comments/83hosf/we_have_suffered_a_stolen/">suffer
 * a stolen</a>.</p>
 *
 * @author Graham Crockford
 */
public class SimulatedExchange extends BaseExchange {

  /**
   * Allows the scope of the simulated exchange to be controlled.  Pass to
   * {@link ExchangeSpecification#setExchangeSpecificParametersItem(String, Object)}
   * to choose one of:
   *
   * <ul>
   *  <li>{@code new MatchingEngineFactory()} - <strong>default</strong>. Each instance
   *  of {@link SimulatedExchange} works on a different virtual exchange with its own
   *  set of order books, thus the simulated exchange is single-user. Recommended
   *  for unit testing.</li>
   *  <li>An existing, shared instance of {@code MatchingEngineFactory} - Create your
   *  own factory and share it between {@link SimulatedExchange} instances to allow
   *  those specific instances to share the same order books and thus trade against
   *  each other. Recommended for integration testing.</li>
   * </ul>
   */
  public static final String ENGINE_FACTORY_PARAM = "MatchingEngineFactory";

  /**
   * As with {@link #ENGINE_FACTORY_PARAM}, provides a default unshared but optionally
   * shared instance of {@link AccountFactory}.
   */
  public static final String ACCOUNT_FACTORY_PARAM = "AccountFactory";

  /**
   * Provides an {@link ExceptionThrower}.
   */
  public static final String EXCEPTION_THROWER_PARAM = "ExceptionThrower";

  private MatchingEngineFactory engineFactory;
  private AccountFactory accountFactory;
  private ExceptionThrower exceptionThrower;

  @Override
  public SynchronizedValueFactory<Long> getNonceFactory() {
    throw new UnsupportedOperationException("Nonce factory is not used.");
  }

  @Override
  public ExchangeSpecification getDefaultExchangeSpecification() {
    ExchangeSpecification exchangeSpecification =
        new ExchangeSpecification(this.getClass().getCanonicalName());
    exchangeSpecification.setExchangeName("Simulated");
    exchangeSpecification.setExchangeDescription("A simulated exchange for integration testing purposes.");
    AccountFactory accountFactory = new AccountFactory();
    exchangeSpecification.setExchangeSpecificParametersItem(ENGINE_FACTORY_PARAM, new MatchingEngineFactory(accountFactory));
    exchangeSpecification.setExchangeSpecificParametersItem(ACCOUNT_FACTORY_PARAM, accountFactory);
    exchangeSpecification.setExchangeSpecificParametersItem(EXCEPTION_THROWER_PARAM, (ExceptionThrower) () -> {});
    return exchangeSpecification;
  }

  @Override
  protected void initServices() {
    engineFactory = (MatchingEngineFactory) exchangeSpecification.getExchangeSpecificParametersItem(ENGINE_FACTORY_PARAM);
    accountFactory = (AccountFactory) exchangeSpecification.getExchangeSpecificParametersItem(ACCOUNT_FACTORY_PARAM);
    exceptionThrower = (ExceptionThrower) exchangeSpecification.getExchangeSpecificParametersItem(EXCEPTION_THROWER_PARAM);
    getAccount().initialize(getExchangeMetaData().getCurrencies().keySet());
    tradeService = new SimulatedTradeService(this);
    marketDataService = new SimulatedMarketDataService(this);
    accountService = new SimulatedAccountService(this);
  }

  Account getAccount() {
    return accountFactory.get(exchangeSpecification.getApiKey());
  }

  void maybeThrow() throws IOException {
    exceptionThrower.run();
  }

  MatchingEngine getEngine(CurrencyPair currencyPair) {
    CurrencyPairMetaData currencyPairMetaData = getExchangeMetaData()
        .getCurrencyPairs()
        .get(currencyPair);
    if (currencyPairMetaData == null) {
      throw new CurrencyPairNotValidException("Currency pair " + currencyPair + " not known", currencyPair);
    }
    return engineFactory.create(
        currencyPair,
        currencyPairMetaData == null
            ? 8
            : currencyPairMetaData.getPriceScale());
  }

  @Override
  public SimulatedMarketDataService getMarketDataService() {
    return (SimulatedMarketDataService) super.getMarketDataService();
  }

  @Override
  public SimulatedAccountService getAccountService() {
    return (SimulatedAccountService) super.getAccountService();
  }

  @Override
  public SimulatedTradeService getTradeService() {
    return (SimulatedTradeService) super.getTradeService();
  }
}

@@ -56,24 +56,69 @@
 Exception;%0A%0A
+import org.apache.commons.lang3.StringUtils;%0A
 import org.k
@@ -354,32 +354,150 @@
 tValidException;
+%0Aimport org.knowm.xchange.exceptions.ExchangeException;%0Aimport org.knowm.xchange.exceptions.ExchangeSecurityException;
 %0A%0Aimport si.mazi
@@ -4229,85 +4229,8 @@
 M);%0A
-    getAccount().initialize(getExchangeMetaData().getCurrencies().keySet());%0A
     
@@ -4406,30 +4406,408 @@
 %0A%0A  
-Account getAccount() %7B
+@Override%0A  public void remoteInit() throws IOException, ExchangeException %7B%0A    if (StringUtils.isNotEmpty(exchangeSpecification.getApiKey()))%0A      getAccount().initialize(getExchangeMetaData().getCurrencies().keySet());%0A  %7D%0A%0A  Account getAccount() %7B%0A    if (StringUtils.isEmpty(exchangeSpecification.getApiKey()))%0A        throw new ExchangeSecurityException(%22API key required for account access%22);
 %0A   

Fix NPE on exchange init