/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.util.fudgemsg;

import java.io.File;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.fudgemsg.AnnotationReflector;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeTypeDictionary;
import org.reflections.Configuration;
import org.reflections.scanners.FieldAnnotationsScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.reflections.vfs.SystemDir;
import org.reflections.vfs.Vfs;
import org.reflections.vfs.Vfs.Dir;
import org.reflections.vfs.Vfs.UrlType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.ExternalIdBundleWithDates;
import com.opengamma.id.ExternalIdWithDates;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.timeseries.date.localdate.ImmutableLocalDateDoubleTimeSeries;
import com.opengamma.timeseries.precise.zdt.ImmutableZonedDateTimeDoubleTimeSeries;

/**
 * Provides a shared singleton {@code FudgeContext} for use throughout OpenGamma.
 * <p>
 * The {@code FudgeContext} is a low-level object necessary to use the Fudge messaging system.
 * Providing the context to Fudge on demand would clutter code and configuration.
 * This class instead provides a singleton that can be used whenever necessary.
 */
public final class OpenGammaFudgeContext {

  /** Logger. */
  private static final Logger s_logger = LoggerFactory.getLogger(OpenGammaFudgeContext.class);

  /**
   * Restricted constructor.
   */
  private OpenGammaFudgeContext() {
  }

  /**
   * Gets the singleton instance of the context, creating it if necessary.
   * @return the singleton instance, not null
   */
  public static FudgeContext getInstance() {
    return ContextHolder.INSTANCE;
  }

  /**
   * Avoid double-checked-locking using the Initialization-on-demand holder idiom.
   */
  static final class ContextHolder {
    static final FudgeContext INSTANCE = constructContext();
    private static FudgeContext constructContext() {
      FudgeContext fudgeContext = new FudgeContext();
      ExtendedFudgeBuilderFactory.init(fudgeContext.getObjectDictionary());
      InnerClassFudgeBuilderFactory.init(fudgeContext.getObjectDictionary());
      
      // hack to handle non-existent classpath directory entries
      List<UrlType> urlTypes = Lists.newArrayList(Vfs.getDefaultUrlTypes());
      urlTypes.add(0, new OGFileUrlType());
      Vfs.setDefaultURLTypes(urlTypes);
      
      // init annotation reflector, which needs this class loader
      Set<ClassLoader> loaders = new HashSet<>();
      loaders.add(OpenGammaFudgeContext.class.getClassLoader());
      try {
        loaders.add(Thread.currentThread().getContextClassLoader());
      } catch (Exception ex) {
        // ignore
      }
      Configuration config = new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forManifest(ClasspathHelper.forJavaClassPath()))
        .setScanners(new TypeAnnotationsScanner(), new FieldAnnotationsScanner())
        .filterInputsBy(FilterBuilder.parse(AnnotationReflector.DEFAULT_ANNOTATION_REFLECTOR_FILTER))
        .addClassLoaders(loaders)
        .useParallelExecutor();
      AnnotationReflector.initDefaultReflector(new AnnotationReflector(config));
      AnnotationReflector reflector = AnnotationReflector.getDefaultReflector();
      
      fudgeContext.getObjectDictionary().addAllAnnotatedBuilders(reflector);
      fudgeContext.getTypeDictionary().addAllAnnotatedSecondaryTypes(reflector);
      
      FudgeTypeDictionary td = fudgeContext.getTypeDictionary();
      td.registerClassRename("com.opengamma.util.timeseries.zoneddatetime.ArrayZonedDateTimeDoubleTimeSeries", ImmutableZonedDateTimeDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.timeseries.zoneddatetime.ArrayZonedDateTimeDoubleTimeSeries", ImmutableZonedDateTimeDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.util.timeseries.zoneddatetime.ListZonedDateTimeDoubleTimeSeries", ImmutableZonedDateTimeDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.timeseries.zoneddatetime.ListZonedDateTimeDoubleTimeSeries", ImmutableZonedDateTimeDoubleTimeSeries.class);
      
      td.registerClassRename("com.opengamma.util.timeseries.localdate.ArrayLocalDateDoubleTimeSeries", ImmutableLocalDateDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.timeseries.localdate.ArrayLocalDateDoubleTimeSeries", ImmutableLocalDateDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.util.timeseries.localdate.ListLocalDateDoubleTimeSeries", ImmutableLocalDateDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.timeseries.localdate.ListLocalDateDoubleTimeSeries", ImmutableLocalDateDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.util.timeseries.localdate.MapLocalDateDoubleTimeSeries", ImmutableLocalDateDoubleTimeSeries.class);
      td.registerClassRename("com.opengamma.timeseries.localdate.MapLocalDateDoubleTimeSeries", ImmutableLocalDateDoubleTimeSeries.class);

      td.registerClassRename("com.opengamma.id.Identifier", ExternalId.class);
      td.registerClassRename("com.opengamma.id.IdentifierBundleWithDates", ExternalIdBundleWithDates.class);
      td.registerClassRename("com.opengamma.id.IdentifierBundle", ExternalIdBundle.class);
      td.registerClassRename("com.opengamma.id.IdentifierWithDates", ExternalIdWithDates.class);
      td.registerClassRename("com.opengamma.id.ObjectIdentifier", ObjectId.class);
      td.registerClassRename("com.opengamma.id.UniqueIdentifier", UniqueId.class);
      return fudgeContext;
    }
  }

  //-------------------------------------------------------------------------
  // handle non-existent classpath directory entries
  private static final class OGFileUrlType implements UrlType {

    @Override
    public boolean matches(URL url) throws Exception {
      return url.getProtocol().equals("file") && !url.toExternalForm().contains(".jar");
    }

    @Override
    public Dir createDir(URL url) throws Exception {
      File file = Vfs.getFile(url);
      if (file == null || file.exists() == false) {
        s_logger.warn("URL could not be resolved to a file: " + url);
        return new EmptyDir(file);
      } else {
        return new SystemDir(file);
      }
    }

    @Override
    public String toString() {
      return "directories (OGFileUrlType fix)";
    }
  }

  //-------------------------------------------------------------------------
  // handle non-existent classpath directory entries
  private static final class EmptyDir implements Vfs.Dir {
    private final File _file;

    private EmptyDir(File file) {
      this._file = file;
    }

    public String getPath() {
      return _file.getPath().replace("\\", "/");
    }


    public Iterable<Vfs.File> getFiles() {
      return Collections.emptyList();  // just return no files
    }

    public void close() {
    }

    @Override
    public String toString() {
      return _file.toString();
    }
  }
}

@@ -516,32 +516,81 @@
 tationsScanner;%0A
+import org.reflections.scanners.SubTypesScanner;%0A
 import org.refle
@@ -3463,16 +3463,44 @@
 canner()
+, new SubTypesScanner(false)
 )%0A      

Add SubTypesScanner to reflections classpath scanning