package org.odata4j.examples.producer.inmemory;

import static org.odata4j.examples.JaxRsImplementation.JERSEY;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import org.core4j.Enumerable;
import org.core4j.Enumerables;
import org.core4j.Func;
import org.core4j.Func1;
import org.core4j.Funcs;
import org.core4j.ThrowingFunc;
import org.odata4j.core.NamespacedAnnotation;
import org.odata4j.core.OCollection;
import org.odata4j.core.OCollections;
import org.odata4j.core.OComplexObject;
import org.odata4j.core.OComplexObjects;
import org.odata4j.core.OProperties;
import org.odata4j.core.OProperty;
import org.odata4j.core.OSimpleObjects;
import org.odata4j.core.PrefixedNamespace;
import org.odata4j.edm.EdmAnnotation;
import org.odata4j.edm.EdmAnnotationAttribute;
import org.odata4j.edm.EdmComplexType;
import org.odata4j.edm.EdmDecorator;
import org.odata4j.edm.EdmDocumentation;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmItem;
import org.odata4j.edm.EdmProperty;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmStructuralType;
import org.odata4j.examples.AbstractExample;
import org.odata4j.examples.ODataServerFactory;
import org.odata4j.producer.PropertyPath;
import org.odata4j.producer.inmemory.InMemoryProducer;
import org.odata4j.producer.resources.DefaultODataProducerProvider;

public class InMemoryProducerExample extends AbstractExample {

  public static void main(String[] args) {
    InMemoryProducerExample example = new InMemoryProducerExample();
    example.run(args);
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private void run(String[] args) {

    String endpointUri = "http://localhost:8887/InMemoryProducerExample.svc/";

    // InMemoryProducer is a readonly odata provider that serves up POJOs as entities using bean properties
    // call InMemoryProducer.register to declare a new entity-set, providing a entity source function and a propertyname to serve as the key
    final InMemoryProducer producer = new InMemoryProducer("InMemoryProducerExample", null, 100, new MyEdmDecorator(), null);

    // expose this jvm's thread information (Thread instances) as an entity-set called "Threads"
    producer.register(Thread.class, "Threads", new Func<Iterable<Thread>>() {
      public Iterable<Thread> apply() {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        while (tg.getParent() != null)
          tg = tg.getParent();
        Thread[] threads = new Thread[1000];
        int count = tg.enumerate(threads, true);
        return Enumerable.create(threads).take(count);
      }
    }, "Id");

    // expose current system properties (Map.Entry instances) as an entity-set called "SystemProperties"
    producer.register(Entry.class, "SystemProperties", new Func<Iterable<Entry>>() {
      public Iterable<Entry> apply() {
        return (Iterable<Entry>) (Object) System.getProperties().entrySet();
      }
    }, "Key");

    // expose current environment variables (Map.Entry instances) as an entity-set called "EnvironmentVariables"
    producer.register(Entry.class, "EnvironmentVariables", new Func<Iterable<Entry>>() {
      public Iterable<Entry> apply() {
        return (Iterable<Entry>) (Object) System.getenv().entrySet();
      }
    }, "Key");

    // expose this producer's entity-types (EdmEntityType instances) as an entity-set called "EdmEntityTypes"
    producer.register(EdmEntityType.class, "EdmEntityTypes", new Func<Iterable<EdmEntityType>>() {
      public Iterable<EdmEntityType> apply() {
        return producer.getMetadata().getEntityTypes();
      }
    }, "FullyQualifiedTypeName");

    // expose a current listing of exchange traded funds sourced from an external csv (EtfInfo instances) as an entity-set called "ETFs"
    producer.register(EtfInfo.class, "ETFs", Funcs.wrap(new ThrowingFunc<Iterable<EtfInfo>>() {
      public Iterable<EtfInfo> apply() throws Exception {
        return getETFs();
      }
    }), "Symbol");

    // expose an large list of integers as an entity-set called "Integers"
    producer.register(Integer.class, Integer.class, "Integers", new Func<Iterable<Integer>>() {
      public Iterable<Integer> apply() {
        return Enumerable.range(0, Integer.MAX_VALUE);
      }
    }, Funcs.method(Integer.class, Integer.class, "intValue"));

    // register the producer as the static instance, then launch the http server
    DefaultODataProducerProvider.setInstance(producer);
    new ODataServerFactory(JERSEY).hostODataServer(endpointUri);
  }

  private static Iterable<EtfInfo> getETFs() throws Exception {
    return Enumerables.lines(new URL("http://www.masterdata.com/HelpFiles/ETF_List_Downloads/AllETFs.csv")).select(new Func1<String, EtfInfo>() {
      public EtfInfo apply(String csvLine) {
        return EtfInfo.parse(csvLine);
      }
    }).skip(1); // skip header line
  }

  public static class EtfInfo {

    private final String name;
    private final String symbol;
    private final String fundType;

    private EtfInfo(String name, String symbol, String fundType) {
      this.name = name;
      this.symbol = symbol;
      this.fundType = fundType;
    }

    public static EtfInfo parse(String csvLine) {

      csvLine = csvLine.substring(0, csvLine.lastIndexOf(','));
      int i = csvLine.lastIndexOf(',');
      String type = csvLine.substring(i + 1);
      csvLine = csvLine.substring(0, csvLine.lastIndexOf(','));
      i = csvLine.lastIndexOf(',');
      String sym = csvLine.substring(i + 1);
      csvLine = csvLine.substring(0, csvLine.lastIndexOf(','));
      String name = csvLine;
      name = name.startsWith("\"") ? name.substring(1) : name;
      name = name.endsWith("\"") ? name.substring(0, name.length() - 1) : name;
      name = name.replace("\u00A0", " ");

      return new EtfInfo(name, sym, type);
    }

    public String getName() {
      return name;
    }

    public String getSymbol() {
      return symbol;
    }

    public String getFundType() {
      return fundType;
    }
  }

  public static class MyEdmDecorator implements EdmDecorator {

    public static final String namespace = "http://tempuri.org";
    public static final String prefix = "inmem";

    private final List<PrefixedNamespace> namespaces = new ArrayList<PrefixedNamespace>(1);
    private final EdmComplexType schemaInfoType;

    public MyEdmDecorator() {
      namespaces.add(new PrefixedNamespace(namespace, prefix));
      this.schemaInfoType = createSchemaInfoType().build();
    }

    @Override
    public List<PrefixedNamespace> getNamespaces() {
      return namespaces;
    }

    @Override
    public EdmDocumentation getDocumentationForSchema(String namespace) {
      return new EdmDocumentation("InMemoryProducerExample", "This schema exposes a few example types to demonstrate the InMemoryProducer");
    }

    private EdmComplexType.Builder createSchemaInfoType() {
      List<EdmProperty.Builder> props = new ArrayList<EdmProperty.Builder>();

      EdmProperty.Builder ep = EdmProperty.newBuilder("Author").setType(EdmSimpleType.STRING);
      props.add(ep);

      ep = EdmProperty.newBuilder("SeeAlso").setType(EdmSimpleType.STRING);
      props.add(ep);

      return EdmComplexType.newBuilder().setNamespace(namespace).setName("SchemaInfo").addProperties(props);

    }

    @Override
    public List<EdmAnnotation<?>> getAnnotationsForSchema(String namespace) {
      List<EdmAnnotation<?>> annots = new ArrayList<EdmAnnotation<?>>();
      annots.add(new EdmAnnotationAttribute(namespace, prefix, "Version", "1.0 early experience pre-alpha"));

      List<OProperty<?>> p = new ArrayList<OProperty<?>>();
      p.add(OProperties.string("Author", "Xavier S. Dumont"));
      p.add(OProperties.string("SeeAlso", "InMemoryProducerExample.java"));

      annots.add(EdmAnnotation.element(namespace, prefix, "SchemaInfo", OComplexObject.class,
          OComplexObjects.create(schemaInfoType, p)));

      annots.add(EdmAnnotation.element(namespace, prefix, "Tags", OCollection.class,
          OCollections.newBuilder(EdmSimpleType.STRING)
              .add(OSimpleObjects.create(EdmSimpleType.STRING, "tag1"))
              .add(OSimpleObjects.create(EdmSimpleType.STRING, "tag2"))
              .build()));
      return annots;
    }

    @Override
    public EdmDocumentation getDocumentationForEntityType(String namespace, String typeName) {
      return null;
    }

    @Override
    public List<EdmAnnotation<?>> getAnnotationsForEntityType(String namespace, String typeName) {
      return null;
    }

    @Override
    public Object resolveStructuralTypeProperty(EdmStructuralType st, PropertyPath path) throws IllegalArgumentException {
      return null;
    }

    @Override
    public EdmDocumentation getDocumentationForProperty(String namespace, String typename, String propName) {
      return null;
    }

    @Override
    public List<EdmAnnotation<?>> getAnnotationsForProperty(String namespace, String typename, String propName) {
      return null;
    }

    @Override
    public Object resolvePropertyProperty(EdmProperty st, PropertyPath path) throws IllegalArgumentException {
      return null;
    }

    @Override
    public Object getAnnotationValueOverride(EdmItem item, NamespacedAnnotation<?> annot, boolean flatten, Locale locale, Map<String, String> options) {
      return null;
    }

    @Override
    public void decorateEntity(EdmEntitySet entitySet, EdmItem item, EdmItem originalQueryItem, List<OProperty<?>> props, boolean flatten, Locale locale, Map<String, String> options) {
      // no-op
    }

  }
}
