package org.knowm.xchange;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.meta.ExchangeMetaData;
import org.knowm.xchange.exceptions.ExchangeException;
import org.knowm.xchange.service.BaseExchangeService;
import org.knowm.xchange.service.account.AccountService;
import org.knowm.xchange.service.marketdata.MarketDataService;
import org.knowm.xchange.service.trade.TradeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;

public abstract class BaseExchange implements Exchange {

  protected abstract void initServices();

  protected final Logger logger = LoggerFactory.getLogger(getClass());

  protected ExchangeSpecification exchangeSpecification;
  protected ExchangeMetaData exchangeMetaData;

  protected MarketDataService marketDataService;
  protected TradeService tradeService;
  protected AccountService accountService;

  @Override
  public void applySpecification(ExchangeSpecification exchangeSpecification) {

    ExchangeSpecification defaultSpecification = getDefaultExchangeSpecification();

    // Check if default is for everything
    if (exchangeSpecification == null) {
      this.exchangeSpecification = defaultSpecification;
    } else {
      // Using a configured exchange
      // fill in null params with the default ones
      if (exchangeSpecification.getExchangeName() == null) {
        exchangeSpecification.setExchangeName(defaultSpecification.getExchangeName());
      }
      if (exchangeSpecification.getExchangeDescription() == null) {
        exchangeSpecification.setExchangeDescription(defaultSpecification.getExchangeDescription());
      }
      if (exchangeSpecification.getSslUri() == null) {
        exchangeSpecification.setSslUri(defaultSpecification.getSslUri());
      }
      if (exchangeSpecification.getHost() == null) {
        exchangeSpecification.setHost(defaultSpecification.getHost());
      }
      if (exchangeSpecification.getPlainTextUri() == null) {
        exchangeSpecification.setPlainTextUri(defaultSpecification.getPlainTextUri());
      }
      if (exchangeSpecification.getExchangeSpecificParameters() == null) {
        exchangeSpecification.setExchangeSpecificParameters(defaultSpecification.getExchangeSpecificParameters());
      } else {
        // add default value unless it is overridden by current spec
        for (Map.Entry<String, Object> entry : defaultSpecification.getExchangeSpecificParameters().entrySet()) {
          if (exchangeSpecification.getExchangeSpecificParametersItem(entry.getKey()) == null) {
            exchangeSpecification.setExchangeSpecificParametersItem(entry.getKey(), entry.getValue());
          }
        }
      }

      this.exchangeSpecification = exchangeSpecification;
    }

    if (this.exchangeSpecification.getMetaDataJsonFileOverride() != null) { // load the metadata from the file system

      InputStream is = null;
      try {
        is = new FileInputStream(this.exchangeSpecification.getMetaDataJsonFileOverride());
        loadExchangeMetaData(is);
      } catch (FileNotFoundException e) {
        logger.warn(
            "An exception occured while loading the metadata file from the classpath. This is just a warning and can be ignored, but it may lead to unexpected results, so it's better to address it.",
            e);
      } finally {
        IOUtils.closeQuietly(is);
      }

    } else if (this.exchangeSpecification.getExchangeName() != null) { // load the metadata from the classpath

      InputStream is = null;
      try {
        is = BaseExchangeService.class.getClassLoader().getResourceAsStream(getMetaDataFileName(exchangeSpecification) + ".json");
        loadExchangeMetaData(is);
      } finally {
        IOUtils.closeQuietly(is);
      }

    } else {
      logger.warn(
          "No \"exchange name\" found in the ExchangeSpecification. The name is used to load the meta data file from the classpath and may lead to unexpected results.");
    }

    initServices();

    if (this.exchangeSpecification.isShouldLoadRemoteMetaData()) {
      try {
        logger.info("Calling Remote Init...");
        remoteInit();
      } catch (ExchangeException e) {
        throw e;
      } catch (IOException e) {
        throw new ExchangeException(e);
      }
    }
  }

  @Override
  public void remoteInit() throws IOException, ExchangeException {

    logger.info(
        "No remote initialization implemented for {}. The exchange meta data for this exchange is loaded from a json file containing hard-coded exchange meta-data. This may or may not be OK for you, and you should understand exactly how this works. Each exchange can either 1) rely on the hard-coded json file that comes packaged with XChange's jar, 2) provide your own override json file, 3) properly implement the `remoteInit()` method for the exchange (please submit a pull request so the whole community can benefit) or 4) a combination of hard-coded JSON and remote API calls. For more info see: https://github.com/timmolter/XChange/wiki/Design-Notes#exchange-metadata",
        exchangeSpecification.getExchangeName());
  }

  protected void loadExchangeMetaData(InputStream is) {

    exchangeMetaData = loadMetaData(is, ExchangeMetaData.class);
  }

  protected <T> T loadMetaData(InputStream is, Class<T> type) {

    // Use Jackson to parse it
    ObjectMapper mapper = new ObjectMapper();

    try {
      T result = mapper.readValue(is, type);
      logger.debug(result.toString());
      return result;
    } catch (Exception e) {
      logger.warn(
          "An exception occured while loading the metadata file from the file system. This is just a warning and can be ignored, but it may lead to unexpected results, so it's better to address it.",
          e);
      return null;
    }
  }

  @Override
  public List<CurrencyPair> getExchangeSymbols() {

    return new ArrayList<>(getExchangeMetaData().getCurrencyPairs().keySet());
  }

  public String getMetaDataFileName(ExchangeSpecification exchangeSpecification) {

    return exchangeSpecification.getExchangeName().toLowerCase().replace(" ", "").replace("-", "").replace(".", "");
  }

  @Override
  public ExchangeSpecification getExchangeSpecification() {

    return exchangeSpecification;
  }

  @Override
  public ExchangeMetaData getExchangeMetaData() {

    return exchangeMetaData;
  }

  public MarketDataService getMarketDataService() {

    return marketDataService;
  }

  public TradeService getTradeService() {

    return tradeService;
  }

  public AccountService getAccountService() {

    return accountService;
  }

  @Override
  public String toString() {

    String name = exchangeSpecification != null ? exchangeSpecification.getExchangeName() : getClass().getName();
    return name + "#" + hashCode();
  }
}

@@ -4411,63 +4411,8 @@
 ();%0A
-      %7D catch (ExchangeException e) %7B%0A        throw e;%0A
     

delete unnecessary catch because of exception immediately rethrown