{{#with classes}}

declare module 'java' {
  var Java: Java.NodeAPI;
  export = Java;
}

// redseal-java is a fork of java that RedSeal sometimes uses for experimental features.
// We declare redseal-java as an ambient module to allow an application to substitute it for local experiments.
declare module 'redseal-java' {
  var Java: Java.NodeAPI;
  export = Java;
}

declare module JavaAsyncOptions {
  // Promisify must be defined outside of the Java module, because inside the module
  // Function may be redefined to be the interface for java.util.function.Function.
  interface Promisify {
    (funct: Function, receiver?: any): Function;
  }
}

declare 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.
  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.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.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.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.lang.Boolean{{/hasClass}};
  export type short_t = number {{#hasClass 'java.lang.Short'}}| java.lang.Short{{/hasClass}};
  export type integer_t = number {{#hasClass 'java.lang.Integer'}}| java.lang.Integer{{/hasClass}};
  export type double_t = number {{#hasClass 'java.lang.Double'}}| java.lang.Double{{/hasClass}};
  export type float_t = number {{#hasClass 'java.lang.Float'}}| java.lang.Float{{/hasClass}};
  export type number_t = number {{#hasClass 'java.lang.Number'}}| java.lang.Number{{/hasClass}};

  export interface array_t<T> extends 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.lang.Object> | object_t[];

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

  interface AsyncOptions {
    syncSuffix: string;
    asyncSuffix?: string;
    promiseSuffix?: string;
    promisify?: JavaAsyncOptions.Promisify;
  }

  // *NodeAPI* declares methods & members exported by the node java module.
  interface NodeAPI {
    classpath: string[];
    asyncOptions: AsyncOptions;
    callMethod(instance: any, className: string, methodName: string, args: any[], callback: Callback<any>): void;
    callMethodSync(instance: any, className: string, methodName: string, ...args: any[]): any;
    callStaticMethodSync(className: string, methodName: string, ...args: any[]): any;
    instanceOf(javaObject: any, className: string): boolean;
    registerClient(before: (cb: Callback<void>) => void, after?: (cb: Callback<void>) => void): void;
    registerClientP(beforeP: () => Promise<void>, afterP?: () => Promise<void>): void;
    ensureJvm(done: Callback<void>): void;
    ensureJvm(): Promise<void>;

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

    {{#hasClass 'java.lang.ClassLoader'}}getClassLoader(): java.lang.ClassLoader;
    {{/hasClass}}

    {{#each this}}
    newArray(className: '{{packageName}}.{{shortName}}', arg: {{tsType}}[]): array_t<{{quotedPkgName}}.{{shortName}}>;
    {{/each}}
    newArray<T>(className: string, arg: any[]): array_t<T>;

    {{#each this}}
    import(className: '{{packageName}}.{{shortName}}'): {{quotedPkgName}}.{{shortName}}.Static;
    {{/each}}
    import(className: string): any;

    {{#each this}}
    {{#each constructors}}
    newInstance(className: '{{../packageName}}.{{../shortName}}'{{#if tsParamTypes}}, {{{margs this norest=true}}}{{/if}}, cb: Callback<{{{tsReturns}}}>): void;
    {{/each}}
    {{/each}}
    newInstance(className: string, ...args: any[]): void;

    {{#each this}}
    {{#each constructors}}
    newInstanceSync(className: '{{../packageName}}.{{../shortName}}'{{#if tsParamTypes}}, {{{margs this}}}{{/if}}): {{{tsReturns}}};
    {{/each}}
    {{/each}}
    newInstanceSync(className: string, ...args: any[]): any;

    {{#each this}}
    {{#each constructors}}
    newInstancePromise(className: '{{../packageName}}.{{../shortName}}'{{#if tsParamTypes}}, {{{margs this}}}{{/if}}): Promise<{{{tsReturns}}}>;
    {{/each}}
    {{/each}}
    newInstancePromise(className: string, ...args: any[]): Promise<any>;
  }

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

  {{#each this}}
  export module {{quotedPkgName}} {
    export interface {{shortName}} {{#if tsInterfaces}}extends {{tsInterfaces}}{{/if}} {
      {{#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}}
      {{#each fields}}
      {{~#if isStatic~}}
      {{~else}}
      {{name}}: {{{tsType}}};
      {{/if~}}
      {{/each}}
    }
    export module {{shortName}} {
      export interface Static {
        {{#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 fields}}
        {{~#if isStatic}}
        {{name}}: {{{tsType}}};
        {{/if~}}
        {{/each}}
      }
    }
  }

  {{/each}}
}
{{/with}}
