// Generated using the following tsjava options:
// tsJavaModulePath:
//   {{ config.tsJavaModulePath}}
// classpath:
{{#each classpath}}
//   {{this}}
{{/each}}
// classes:
{{#if config.classes}}
{{#each config.classes }}
//   {{this}}
{{/each}}
{{/if}}
{{#unless config.classes}}
//   <none>
{{/unless}}
// packages:
{{#if config.packages}}
{{#each config.packages }}
//   {{this}}
{{/each}}
{{/if}}
{{#unless config.packages}}
//   <none>
{{/unless}}

/* tslint:disable:max-line-length class-name */

declare function require(name: string): any;
require('source-map-support').install();

import _java = require('java');
import _ = require('lodash');
import BluePromise = require('bluebird');
import path = require('path');
{{#if debug}}
import debug = require('debug');
var dlog = debug('tsJavaModule:' + __dirname);
dlog('imported');
{{/if}}

_java.asyncOptions = {
    syncSuffix: '',
    asyncSuffix: 'A',
    promiseSuffix: 'P',
    promisify: BluePromise.promisify
};
{{#if debug}}
dlog('asyncOptions:', _java.asyncOptions);
{{/if}}

// JVM initialization callback which adds tsjava.classpath to the JVM classpath.
function beforeJvm(): BluePromise<void> {
  var moduleJars: string[] = [{{#each @root.classpath}}'{{this}}'{{#unless @last}}, {{/unless}}{{/each}}];
  moduleJars.forEach((jarPath: string) => {
    var fullJarPath: string = path.join(__dirname, '{{@root.classpathAdjust}}', jarPath);
{{#if debug}}
    dlog('Adding to classpath:', fullJarPath);
{{/if}}
    _java.classpath.push(fullJarPath);
  });
  return BluePromise.resolve();
}

_java.registerClientP(beforeJvm);

{{#with classes}}
export module Java {
  'use strict';

  interface StringDict {
    [index: string]: string;
  }

  export type NodeJavaAPI = typeof _java;

  export function getJava(): NodeJavaAPI {
    return _java;
  }

  export function ensureJvm(): Promise<void> {
    return _java.ensureJvm();
  }

  {{#hasClass 'java.lang.ClassLoader'}}
  export function getClassLoader(): Java.java.lang.ClassLoader {
    return _java.getClassLoader();
  }
  {{/hasClass}}

  // Return the fully qualified class path for a class name.
  // Returns undefined if the className is ambiguous or not present in the configured classes.
  export function fullyQualifiedName(className: string): string {
    var shortToLongMap: StringDict = {
      {{#each this}}
      {{#if useAlias}}
      '{{alias}}': '{{packageName}}.{{shortName}}'{{#unless @last}},{{/unless}}
      {{/if}}
      {{/each}}
    };
    return shortToLongMap[className];
  }

  {{#each this}}
  {{#if useAlias}}
  export function importClass(className: '{{alias}}'): Java.{{quotedPkgName}}.{{shortName}}.Static;
  {{/if}}
  {{/each}}
  {{#each this}}
  export function importClass(className: '{{packageName}}.{{shortName}}'): Java.{{quotedPkgName}}.{{shortName}}.Static;
  {{/each}}
  export function importClass(className: string): any;
  export function importClass(className: string): any {
    var fullName: string = fullyQualifiedName(className) || className;
    return _java.import(fullName);
  }

  {{#each this}}
  {{#if useAlias}}
  export function asInstanceOf(obj: any, className: '{{alias}}'): Java.{{quotedPkgName}}.{{shortName}};
  {{/if}}
  {{/each}}
  {{#each this}}
  export function asInstanceOf(obj: any, className: '{{packageName}}.{{shortName}}'): Java.{{quotedPkgName}}.{{shortName}};
  {{/each}}
  export function asInstanceOf(obj: any, className: string): any;
  export function asInstanceOf(obj: any, className: string): any {
    var fullName: string = fullyQualifiedName(className) || className;
    if (_java.instanceOf(obj, fullName)) {
      return obj;
    } else {
      throw new Error('asInstanceOf fails, obj is not a ' + fullName);
    }
  }

  export interface Callback<T> {
    (err?: Error, result?: T): void;
  }

  // Returns true if javaObject is an instance of the named class, which may be a short className.
  // Returns false if javaObject is not an instance of the named class.
  // Throws an exception if the named class does not exist, or is an ambiguous short name.
  export function instanceOf(javaObject: any, className: string): boolean {
    var fullName: string = fullyQualifiedName(className) || className;
    return smellsLikeJavaObject(javaObject) && _java.instanceOf(javaObject, fullName);
  }

{{#hasClass 'java.lang.Short'}}  export function newShort(val: number): Java.java.lang.Short { return _java.newShort(val); }{{/hasClass}}
{{#hasClass 'java.lang.Long'}}  export function newLong(val: number): Java.java.lang.Long { return _java.newLong(val); }{{/hasClass}}
{{#hasClass 'java.lang.Float'}}  export function newFloat(val: number): Java.java.lang.Float { return _java.newFloat(val); }{{/hasClass}}
{{#hasClass 'java.lang.Double'}}  export function newDouble(val: number): Java.java.lang.Double { return _java.newDouble(val); }{{/hasClass}}

  {{#each this}}
  {{#if useAlias}}
  {{#each constructors}}
  export function newInstanceA(className: '{{../alias}}'{{#if tsParamTypes}}, {{{margs this norest=true}}}{{/if}}, cb: Callback<{{{tsReturns}}}>): void;
  {{/each}}
  {{/if}}
  {{/each}}
  {{#each this}}
  {{#each constructors}}
  export function newInstanceA(className: '{{../packageName}}.{{../shortName}}'{{#if tsParamTypes}}, {{{margs this norest=true}}}{{/if}}, cb: Callback<{{{tsReturns}}}>): void;
  {{/each}}
  {{/each}}
  export function newInstanceA(className: string, ...args: any[]): void;
  export function newInstanceA(className: string, ...args: any[]): any {
    var fullName: string = fullyQualifiedName(className) || className;
    args.unshift(fullName);
    return _java.newInstance.apply(_java, args);
  }

  {{#each this}}
  {{#if useAlias}}
  {{#each constructors}}
  export function newInstance(className: '{{../alias}}'{{#if tsParamTypes}}, {{{margs this}}}{{/if}}): {{{tsReturns}}};
  {{/each}}
  {{/if}}
  {{/each}}
  {{#each this}}
  {{#each constructors}}
  export function newInstance(className: '{{../packageName}}.{{../shortName}}'{{#if tsParamTypes}}, {{{margs this}}}{{/if}}): {{{tsReturns}}};
  {{/each}}
  {{/each}}
  export function newInstance(className: string, ...args: any[]): any;
  export function newInstance(className: string, ...args: any[]): any {
    var fullName: string = fullyQualifiedName(className) || className;
    args.unshift(fullName);
    return _java.newInstanceSync.apply(_java, args);
  }

  {{#each this}}
  {{#if useAlias}}
  {{#each constructors}}
  export function newInstanceP(className: '{{../alias}}'{{#if tsParamTypes}}, {{{margs this}}}{{/if}}): Promise<{{{tsReturns}}}>;
  {{/each}}
  {{/if}}
  {{/each}}
  {{#each this}}
  {{#each constructors}}
  export function newInstanceP(className: '{{../packageName}}.{{../shortName}}'{{#if tsParamTypes}}, {{{margs this}}}{{/if}}): Promise<{{{tsReturns}}}>;
  {{/each}}
  {{/each}}
  export function newInstanceP(className: string, ...args: any[]): Promise<any>;
  export function newInstanceP(className: string, ...args: any[]): Promise<any> {
    var fullName: string = fullyQualifiedName(className) || className;
    args.unshift(fullName);
    return _java.newInstanceP.apply(_java, args);
  }

  {{#each this}}
  {{#if useAlias}}
  export function newArray(className: '{{alias}}', arg: {{tsType}}[]): array_t<{{quotedPkgName}}.{{shortName}}>;
  {{/if}}
  {{/each}}
  {{#each this}}
  export function newArray(className: '{{packageName}}.{{shortName}}', arg: {{tsType}}[]): array_t<{{quotedPkgName}}.{{shortName}}>;
  {{/each}}
  export function newArray<T>(className: string, arg: any[]): array_t<T>;
  export function newArray<T>(className: string, arg: any[]): array_t<T> {
    var fullName: string = fullyQualifiedName(className) || className;
    return _java.newArray(fullName, arg);
  }

  // export module Java {

  // Node-java has special handling for methods that return long or java.lang.Long,
  // returning a Javascript Number but with an additional property longValue.
  export interface longValue_t extends Number {
    longValue: string;
  }

  // Node-java can automatically coerce a javascript string into a java.lang.String.
  // This special type alias allows to declare that possiblity to Typescript.
  export type string_t = string | Java.java.lang.String;

  // Java methods that take java.lang.Object parameters implicitly will take a java.lang.String.
  // But string_t is not sufficient for this case, we need object_t.
  export type object_t = Java.java.lang.Object | string | boolean | number | longValue_t;

  // Java methods that take long or java.lang.Long parameters may take javascript numbers,
  // longValue_t (see above) or java.lang.Long.
  // This special type alias allows to declare that possiblity to Typescript.
  export type long_t = number | longValue_t {{#hasClass 'java.lang.Long'}}| Java.java.lang.Long{{/hasClass}};

  // Handling of other primitive numeric types is simpler, as there is no loss of precision.
  export type boolean_t = boolean {{#hasClass 'java.lang.Boolean'}}| Java.java.lang.Boolean{{/hasClass}};
  export type short_t = number {{#hasClass 'java.lang.Short'}}| Java.java.lang.Short{{/hasClass}};
  export type integer_t = number {{#hasClass 'java.lang.Integer'}}| Java.java.lang.Integer{{/hasClass}};
  export type double_t = number {{#hasClass 'java.lang.Double'}}| Java.java.lang.Double{{/hasClass}};
  export type float_t = number {{#hasClass 'java.lang.Float'}}| Java.java.lang.Float{{/hasClass}};
  export type number_t = number {{#hasClass 'java.lang.Number'}}| Java.java.lang.Number{{/hasClass}};

  export interface array_t<T> extends Java.java.lang.Object {
    // This is an opaque type for a java array_t T[];
    // Use Java.newArray<T>(className, [...]) to create wherever a Java method expects a T[],
    // most notably for vararg parameteters.
    __dummy: T;
  }

  export type object_array_t = array_t<Java.java.lang.Object> | object_t[];

  {{#each this}}
  {{#if useAlias}}
  export import {{alias}} = {{quotedPkgName}}.{{shortName}};
  {{/if}}
  {{/each}}

  {{#each this}}
  export module {{quotedPkgName}} {
    export interface {{shortName}} {{#if tsInterfaces}}extends {{#join tsInterfaces ', '}}{{this}}{{/join}}{{/if}} {
      {{#each fields}}
      {{~#if isStatic~}}
      {{~else}}
      {{name}}: {{{tsType}}};
      {{/if~}}
      {{/each}}
      {{#each variants}}
      {{#each this}}
      {{#unless isStatic}}
      // {{{generic_proto}}}
      {{#ifdef @root.opts.asyncSuffix}}
      {{name}}{{@root.opts.asyncSuffix}}({{#if tsParamTypes}}{{{margs this norest=true}}},{{/if}} cb: Callback<{{{tsReturns}}}>): void;
      {{/ifdef~}}
      {{#ifdef @root.opts.syncSuffix}}
      {{name}}{{@root.opts.syncSuffix}}({{{margs this}}}): {{{tsReturns}}};
      {{#if isVarArgs}}
      {{name}}{{@root.opts.syncSuffix}}({{{margs this norest=true}}}): {{{tsReturns}}};
      {{/if}}
      {{/ifdef~}}
      {{#ifdef @root.opts.promiseSuffix}}
      {{name}}{{@root.opts.promiseSuffix}}({{{margs this}}}): Promise<{{{tsReturns}}}>;
      {{#if isVarArgs}}
      {{name}}{{@root.opts.promiseSuffix}}({{{margs this norest=true}}}): Promise<{{{tsReturns}}}>;
      {{/if}}
      {{/ifdef~}}
      {{/unless}}
      {{/each}}
      {{/each}}
    }
    export module {{shortName}} {
      export interface Static {
        {{#each fields}}
        {{~#if isStatic}}
        {{name}}: {{{tsType}}};
        {{/if~}}
        {{/each}}
        class: Java.{{#hasClass 'java.lang.Class'}}Class{{else}}Object{{/hasClass}};
        {{#each constructors}}
        new ({{{margs this}}}): {{../quotedPkgName}}.{{../shortName}};
        {{/each}}
        {{#each variants}}
        {{#each this}}
        {{#if isStatic}}
        // {{{generic_proto}}}
        {{#ifdef @root.opts.asyncSuffix}}
        {{name}}{{@root.opts.asyncSuffix}}({{#if tsParamTypes}}{{{margs this norest=true}}},{{/if}} cb: Callback<{{{tsReturns}}}>): void;
        {{/ifdef~}}
        {{#ifdef @root.opts.syncSuffix}}
        {{name}}{{@root.opts.syncSuffix}}({{{margs this}}}): {{{tsReturns}}};
        {{#if isVarArgs}}
        {{name}}{{@root.opts.syncSuffix}}({{{margs this norest=true}}}): {{{tsReturns}}};
        {{/if}}
        {{/ifdef~}}
        {{#ifdef @root.opts.promiseSuffix}}
        {{name}}{{@root.opts.promiseSuffix}}({{{margs this}}}): Promise<{{{tsReturns}}}>;
        {{#if isVarArgs}}
        {{name}}{{@root.opts.promiseSuffix}}({{{margs this norest=true}}}): Promise<{{{tsReturns}}}>;
        {{/if}}
        {{/ifdef~}}
        {{/if}}
        {{/each}}
        {{/each}}
      }
    }
  }

  {{/each}}

{{#hasClass 'java.lang.Long'}}
  // ### `function L(n: number)`
  // Produce a longValue_t literal.
  export function L(n: number): Java.longValue_t {
    return Java.newLong(n).longValue();
  }

  // ### `function isLongValue(e: any)`
  // Checks whether an object is a longValue_t, which is the representation of Java long primitives.
  export function isLongValue(obj: any): boolean {
    return _.isObject(obj) && obj instanceof Number && 'longValue' in obj && _.keys(obj).length === 1;
  }
{{/hasClass}}

  // #### `function smellsLikeJavaObject(e: any)`
  // Returns true if the obj 'smells' like a Java object.
  // This is a light-weight test that will return false when `e` is clearly not a Java object,
  // but it may have false positives. To be certain, use `isJavaObject(e)` or `instanceOf(e, classname)` instead.
  function smellsLikeJavaObject(e: any): boolean {
    return _.isObject(e) && !_.isArray(e) {{#hasClass 'java.lang.Long'}}&& !isLongValue(e){{/hasClass}};
  }

  // #### `function isJavaObject(e: any)`
  // Returns true if the obj is a Java object.
  // Useful for determining the runtime type of object_t returned by many java methods.
  export function isJavaObject(e: any): boolean {
    return smellsLikeJavaObject(e) && _java.instanceOf(e, 'java.lang.Object');
  }

{{#hasClass 'java.util.Iterator'}}
  // #### `interface ConsumeObject`
  // A function interface for Java Object consumer.
  // See `forEach` below.
  export interface ConsumeObject {
    (item: Java.object_t): any | BluePromise<any>;
  }

  // #### `forEach(javaIterator: Java.Iterator, consumer: ConsumeObject)`
  // Applies *consumer* to each Java.Object returned by the *javaIterator*.
  // *javaIterator* may be any type that implements java.util.Iterator, including a tinkerpop Traversal.
  // *consumer* is function that will do some work on a Java.Object asychronously, returning a Promise for its completion.
  // Returns a promise that is resolved when all objects have been consumed.
  export function forEach(javaIterator: Java.Iterator, consumer: ConsumeObject): BluePromise<void> {
    function _eachIterator(javaIterator: Java.Iterator, consumer: ConsumeObject): BluePromise<void> {
      return javaIterator.hasNextP()
        .then((hasNext: boolean): BluePromise<void> => {
          if (!hasNext) {
            return BluePromise.resolve();
          } else {
            return javaIterator.nextP()
              .then((obj: Java.object_t) => consumer(obj))
              .then(() => _eachIterator(javaIterator, consumer));
          }
        });
    }
    return _eachIterator(javaIterator, consumer);
  }

{{/hasClass}}
} // module Java
{{/with}}
