import * as DataLoader from 'dataloader';
import * as hash from 'object-hash';
import { Reader, Writer } from 'protobufjs/minimal';


export interface BatchQueryRequest {
  ids: string[];
}

export interface BatchQueryResponse {
  entities: Entity[];
}

export interface BatchMapQueryRequest {
  ids: string[];
}

export interface BatchMapQueryResponse {
  entities: { [key: string]: Entity };
}

export interface BatchMapQueryResponse_EntitiesEntry {
  key: string;
  value: Entity | undefined;
}

export interface GetOnlyMethodRequest {
  id: string;
}

export interface GetOnlyMethodResponse {
  entity: Entity | undefined;
}

export interface WriteMethodRequest {
  id: string;
}

export interface WriteMethodResponse {
}

export interface Entity {
  id: string;
  name: string;
}

const baseBatchQueryRequest: object = {
  ids: "",
};

const baseBatchQueryResponse: object = {
  entities: undefined,
};

const baseBatchMapQueryRequest: object = {
  ids: "",
};

const baseBatchMapQueryResponse: object = {
  entities: undefined,
};

const baseBatchMapQueryResponse_EntitiesEntry: object = {
  key: "",
  value: undefined,
};

const baseGetOnlyMethodRequest: object = {
  id: "",
};

const baseGetOnlyMethodResponse: object = {
  entity: undefined,
};

const baseWriteMethodRequest: object = {
  id: "",
};

const baseWriteMethodResponse: object = {
};

const baseEntity: object = {
  id: "",
  name: "",
};

export interface EntityService<Context extends DataLoaders> {

  BatchQuery(ctx: Context, request: BatchQueryRequest): Promise<BatchQueryResponse>;

  GetQuery(ctx: Context, id: string): Promise<Entity>;

  BatchMapQuery(ctx: Context, request: BatchMapQueryRequest): Promise<BatchMapQueryResponse>;

  GetMapQuery(ctx: Context, id: string): Promise<Entity>;

  /**
   *  Add a method that is not batchable to show it's still cached
   */
  GetOnlyMethod(ctx: Context, request: GetOnlyMethodRequest): Promise<GetOnlyMethodResponse>;

  /**
   *  Add a method that won't get cached
   */
  WriteMethod(ctx: Context, request: WriteMethodRequest): Promise<WriteMethodResponse>;

}

export class EntityServiceClientImpl<Context extends DataLoaders> implements EntityService<Context> {

  private readonly rpc: Rpc<Context>;

  constructor(rpc: Rpc<Context>) {
    this.rpc = rpc;
  }

  GetQuery(ctx: Context, id: string): Promise<Entity> {
    const dl = ctx.getDataLoader("batching.EntityService.BatchQuery", () => {
      return new DataLoader<string, Entity>((ids) => {
        const request = { ids };
        return this.BatchQuery(ctx, request).then(res => res.entities);
      }, { cacheKeyFn: hash });
    });
    return dl.load(id);
  }

  BatchQuery(ctx: Context, request: BatchQueryRequest): Promise<BatchQueryResponse> {
    const data = BatchQueryRequest.encode(request).finish();
    const promise = this.rpc.request(ctx, "batching.EntityService", "BatchQuery", data);
    return promise.then(data => BatchQueryResponse.decode(new Reader(data)));
  }

  GetMapQuery(ctx: Context, id: string): Promise<Entity> {
    const dl = ctx.getDataLoader("batching.EntityService.BatchMapQuery", () => {
      return new DataLoader<string, Entity>((ids) => {
        const request = { ids };
        return this.BatchMapQuery(ctx, request).then(res => {
          return ids.map(key => res.entities[key]);
        })
      }, { cacheKeyFn: hash });
    });
    return dl.load(id);
  }

  BatchMapQuery(ctx: Context, request: BatchMapQueryRequest): Promise<BatchMapQueryResponse> {
    const data = BatchMapQueryRequest.encode(request).finish();
    const promise = this.rpc.request(ctx, "batching.EntityService", "BatchMapQuery", data);
    return promise.then(data => BatchMapQueryResponse.decode(new Reader(data)));
  }

  GetOnlyMethod(ctx: Context, request: GetOnlyMethodRequest): Promise<GetOnlyMethodResponse> {
    const dl = ctx.getDataLoader("batching.EntityService.GetOnlyMethod", () => {
      return new DataLoader<GetOnlyMethodRequest, GetOnlyMethodResponse>((requests) => {
        const responses = requests.map(async request => {
          const data = GetOnlyMethodRequest.encode(request).finish();
          const response = await this.rpc.request(ctx, "batching.EntityService", "GetOnlyMethod", data);
          return GetOnlyMethodResponse.decode(new Reader(response));
        })
        return Promise.all(responses);
      }, { cacheKeyFn: hash });
    });
    return dl.load(request);
  }

  WriteMethod(ctx: Context, request: WriteMethodRequest): Promise<WriteMethodResponse> {
    const data = WriteMethodRequest.encode(request).finish();
    const promise = this.rpc.request(ctx, "batching.EntityService", "WriteMethod", data);
    return promise.then(data => WriteMethodResponse.decode(new Reader(data)));
  }

}

interface Rpc<Context> {

  request(ctx: Context, service: string, method: string, data: Uint8Array): Promise<Uint8Array>;

}

export interface DataLoaders {

  getDataLoader<T>(identifier: string, constructorFn: () => T): T;

}

export const BatchQueryRequest = {
  encode(message: BatchQueryRequest, writer: Writer = Writer.create()): Writer {
    for (const v of message.ids) {
      writer.uint32(10).string(v!);
    }
    return writer;
  },
  decode(reader: Reader, length?: number): BatchQueryRequest {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseBatchQueryRequest) as BatchQueryRequest;
    message.ids = [];
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.ids.push(reader.string());
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): BatchQueryRequest {
    const message = Object.create(baseBatchQueryRequest) as BatchQueryRequest;
    message.ids = [];
    if (object.ids !== undefined && object.ids !== null) {
      for (const e of object.ids) {
        message.ids.push(String(e));
      }
    }
    return message;
  },
  fromPartial(object: DeepPartial<BatchQueryRequest>): BatchQueryRequest {
    const message = Object.create(baseBatchQueryRequest) as BatchQueryRequest;
    message.ids = [];
    if (object.ids !== undefined && object.ids !== null) {
      for (const e of object.ids) {
        message.ids.push(e);
      }
    }
    return message;
  },
  toJSON(message: BatchQueryRequest): unknown {
    const obj: any = {};
    if (message.ids) {
      obj.ids = message.ids.map(e => e || "");
    } else {
      obj.ids = [];
    }
    return obj;
  },
};

export const BatchQueryResponse = {
  encode(message: BatchQueryResponse, writer: Writer = Writer.create()): Writer {
    for (const v of message.entities) {
      Entity.encode(v!, writer.uint32(10).fork()).ldelim();
    }
    return writer;
  },
  decode(reader: Reader, length?: number): BatchQueryResponse {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseBatchQueryResponse) as BatchQueryResponse;
    message.entities = [];
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.entities.push(Entity.decode(reader, reader.uint32()));
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): BatchQueryResponse {
    const message = Object.create(baseBatchQueryResponse) as BatchQueryResponse;
    message.entities = [];
    if (object.entities !== undefined && object.entities !== null) {
      for (const e of object.entities) {
        message.entities.push(Entity.fromJSON(e));
      }
    }
    return message;
  },
  fromPartial(object: DeepPartial<BatchQueryResponse>): BatchQueryResponse {
    const message = Object.create(baseBatchQueryResponse) as BatchQueryResponse;
    message.entities = [];
    if (object.entities !== undefined && object.entities !== null) {
      for (const e of object.entities) {
        message.entities.push(Entity.fromPartial(e));
      }
    }
    return message;
  },
  toJSON(message: BatchQueryResponse): unknown {
    const obj: any = {};
    if (message.entities) {
      obj.entities = message.entities.map(e => e ? Entity.toJSON(e) : undefined);
    } else {
      obj.entities = [];
    }
    return obj;
  },
};

export const BatchMapQueryRequest = {
  encode(message: BatchMapQueryRequest, writer: Writer = Writer.create()): Writer {
    for (const v of message.ids) {
      writer.uint32(10).string(v!);
    }
    return writer;
  },
  decode(reader: Reader, length?: number): BatchMapQueryRequest {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseBatchMapQueryRequest) as BatchMapQueryRequest;
    message.ids = [];
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.ids.push(reader.string());
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): BatchMapQueryRequest {
    const message = Object.create(baseBatchMapQueryRequest) as BatchMapQueryRequest;
    message.ids = [];
    if (object.ids !== undefined && object.ids !== null) {
      for (const e of object.ids) {
        message.ids.push(String(e));
      }
    }
    return message;
  },
  fromPartial(object: DeepPartial<BatchMapQueryRequest>): BatchMapQueryRequest {
    const message = Object.create(baseBatchMapQueryRequest) as BatchMapQueryRequest;
    message.ids = [];
    if (object.ids !== undefined && object.ids !== null) {
      for (const e of object.ids) {
        message.ids.push(e);
      }
    }
    return message;
  },
  toJSON(message: BatchMapQueryRequest): unknown {
    const obj: any = {};
    if (message.ids) {
      obj.ids = message.ids.map(e => e || "");
    } else {
      obj.ids = [];
    }
    return obj;
  },
};

export const BatchMapQueryResponse = {
  encode(message: BatchMapQueryResponse, writer: Writer = Writer.create()): Writer {
    Object.entries(message.entities).forEach(([key, value]) => {
      BatchMapQueryResponse_EntitiesEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).ldelim();
    })
    return writer;
  },
  decode(reader: Reader, length?: number): BatchMapQueryResponse {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseBatchMapQueryResponse) as BatchMapQueryResponse;
    message.entities = {};
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          const entry1 = BatchMapQueryResponse_EntitiesEntry.decode(reader, reader.uint32());
          if (entry1.value) {
            message.entities[entry1.key] = entry1.value;
          }
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): BatchMapQueryResponse {
    const message = Object.create(baseBatchMapQueryResponse) as BatchMapQueryResponse;
    message.entities = {};
    if (object.entities !== undefined && object.entities !== null) {
      Object.entries(object.entities).forEach(([key, value]) => {
        message.entities[key] = Entity.fromJSON(value);
      })
    }
    return message;
  },
  fromPartial(object: DeepPartial<BatchMapQueryResponse>): BatchMapQueryResponse {
    const message = Object.create(baseBatchMapQueryResponse) as BatchMapQueryResponse;
    message.entities = {};
    if (object.entities !== undefined && object.entities !== null) {
      Object.entries(object.entities).forEach(([key, value]) => {
        if (value) {
          message.entities[key] = Entity.fromPartial(value);
        }
      })
    }
    return message;
  },
  toJSON(message: BatchMapQueryResponse): unknown {
    const obj: any = {};
    obj.entities = message.entities || undefined;
    return obj;
  },
};

export const BatchMapQueryResponse_EntitiesEntry = {
  encode(message: BatchMapQueryResponse_EntitiesEntry, writer: Writer = Writer.create()): Writer {
    writer.uint32(10).string(message.key);
    if (message.value !== undefined && message.value !== undefined) {
      Entity.encode(message.value, writer.uint32(18).fork()).ldelim();
    }
    return writer;
  },
  decode(reader: Reader, length?: number): BatchMapQueryResponse_EntitiesEntry {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseBatchMapQueryResponse_EntitiesEntry) as BatchMapQueryResponse_EntitiesEntry;
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.key = reader.string();
          break;
        case 2:
          message.value = Entity.decode(reader, reader.uint32());
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): BatchMapQueryResponse_EntitiesEntry {
    const message = Object.create(baseBatchMapQueryResponse_EntitiesEntry) as BatchMapQueryResponse_EntitiesEntry;
    if (object.key !== undefined && object.key !== null) {
      message.key = String(object.key);
    } else {
      message.key = "";
    }
    if (object.value !== undefined && object.value !== null) {
      message.value = Entity.fromJSON(object.value);
    } else {
      message.value = undefined;
    }
    return message;
  },
  fromPartial(object: DeepPartial<BatchMapQueryResponse_EntitiesEntry>): BatchMapQueryResponse_EntitiesEntry {
    const message = Object.create(baseBatchMapQueryResponse_EntitiesEntry) as BatchMapQueryResponse_EntitiesEntry;
    if (object.key !== undefined && object.key !== null) {
      message.key = object.key;
    } else {
      message.key = "";
    }
    if (object.value !== undefined && object.value !== null) {
      message.value = Entity.fromPartial(object.value);
    } else {
      message.value = undefined;
    }
    return message;
  },
  toJSON(message: BatchMapQueryResponse_EntitiesEntry): unknown {
    const obj: any = {};
    obj.key = message.key || "";
    obj.value = message.value ? Entity.toJSON(message.value) : undefined;
    return obj;
  },
};

export const GetOnlyMethodRequest = {
  encode(message: GetOnlyMethodRequest, writer: Writer = Writer.create()): Writer {
    writer.uint32(10).string(message.id);
    return writer;
  },
  decode(reader: Reader, length?: number): GetOnlyMethodRequest {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseGetOnlyMethodRequest) as GetOnlyMethodRequest;
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.id = reader.string();
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): GetOnlyMethodRequest {
    const message = Object.create(baseGetOnlyMethodRequest) as GetOnlyMethodRequest;
    if (object.id !== undefined && object.id !== null) {
      message.id = String(object.id);
    } else {
      message.id = "";
    }
    return message;
  },
  fromPartial(object: DeepPartial<GetOnlyMethodRequest>): GetOnlyMethodRequest {
    const message = Object.create(baseGetOnlyMethodRequest) as GetOnlyMethodRequest;
    if (object.id !== undefined && object.id !== null) {
      message.id = object.id;
    } else {
      message.id = "";
    }
    return message;
  },
  toJSON(message: GetOnlyMethodRequest): unknown {
    const obj: any = {};
    obj.id = message.id || "";
    return obj;
  },
};

export const GetOnlyMethodResponse = {
  encode(message: GetOnlyMethodResponse, writer: Writer = Writer.create()): Writer {
    if (message.entity !== undefined && message.entity !== undefined) {
      Entity.encode(message.entity, writer.uint32(10).fork()).ldelim();
    }
    return writer;
  },
  decode(reader: Reader, length?: number): GetOnlyMethodResponse {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseGetOnlyMethodResponse) as GetOnlyMethodResponse;
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.entity = Entity.decode(reader, reader.uint32());
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): GetOnlyMethodResponse {
    const message = Object.create(baseGetOnlyMethodResponse) as GetOnlyMethodResponse;
    if (object.entity !== undefined && object.entity !== null) {
      message.entity = Entity.fromJSON(object.entity);
    } else {
      message.entity = undefined;
    }
    return message;
  },
  fromPartial(object: DeepPartial<GetOnlyMethodResponse>): GetOnlyMethodResponse {
    const message = Object.create(baseGetOnlyMethodResponse) as GetOnlyMethodResponse;
    if (object.entity !== undefined && object.entity !== null) {
      message.entity = Entity.fromPartial(object.entity);
    } else {
      message.entity = undefined;
    }
    return message;
  },
  toJSON(message: GetOnlyMethodResponse): unknown {
    const obj: any = {};
    obj.entity = message.entity ? Entity.toJSON(message.entity) : undefined;
    return obj;
  },
};

export const WriteMethodRequest = {
  encode(message: WriteMethodRequest, writer: Writer = Writer.create()): Writer {
    writer.uint32(10).string(message.id);
    return writer;
  },
  decode(reader: Reader, length?: number): WriteMethodRequest {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseWriteMethodRequest) as WriteMethodRequest;
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.id = reader.string();
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): WriteMethodRequest {
    const message = Object.create(baseWriteMethodRequest) as WriteMethodRequest;
    if (object.id !== undefined && object.id !== null) {
      message.id = String(object.id);
    } else {
      message.id = "";
    }
    return message;
  },
  fromPartial(object: DeepPartial<WriteMethodRequest>): WriteMethodRequest {
    const message = Object.create(baseWriteMethodRequest) as WriteMethodRequest;
    if (object.id !== undefined && object.id !== null) {
      message.id = object.id;
    } else {
      message.id = "";
    }
    return message;
  },
  toJSON(message: WriteMethodRequest): unknown {
    const obj: any = {};
    obj.id = message.id || "";
    return obj;
  },
};

export const WriteMethodResponse = {
  encode(message: WriteMethodResponse, writer: Writer = Writer.create()): Writer {
    return writer;
  },
  decode(reader: Reader, length?: number): WriteMethodResponse {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseWriteMethodResponse) as WriteMethodResponse;
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): WriteMethodResponse {
    const message = Object.create(baseWriteMethodResponse) as WriteMethodResponse;
    return message;
  },
  fromPartial(object: DeepPartial<WriteMethodResponse>): WriteMethodResponse {
    const message = Object.create(baseWriteMethodResponse) as WriteMethodResponse;
    return message;
  },
  toJSON(message: WriteMethodResponse): unknown {
    const obj: any = {};
    return obj;
  },
};

export const Entity = {
  encode(message: Entity, writer: Writer = Writer.create()): Writer {
    writer.uint32(10).string(message.id);
    writer.uint32(18).string(message.name);
    return writer;
  },
  decode(reader: Reader, length?: number): Entity {
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = Object.create(baseEntity) as Entity;
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.id = reader.string();
          break;
        case 2:
          message.name = reader.string();
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    return message;
  },
  fromJSON(object: any): Entity {
    const message = Object.create(baseEntity) as Entity;
    if (object.id !== undefined && object.id !== null) {
      message.id = String(object.id);
    } else {
      message.id = "";
    }
    if (object.name !== undefined && object.name !== null) {
      message.name = String(object.name);
    } else {
      message.name = "";
    }
    return message;
  },
  fromPartial(object: DeepPartial<Entity>): Entity {
    const message = Object.create(baseEntity) as Entity;
    if (object.id !== undefined && object.id !== null) {
      message.id = object.id;
    } else {
      message.id = "";
    }
    if (object.name !== undefined && object.name !== null) {
      message.name = object.name;
    } else {
      message.name = "";
    }
    return message;
  },
  toJSON(message: Entity): unknown {
    const obj: any = {};
    obj.id = message.id || "";
    obj.name = message.name || "";
    return obj;
  },
};

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends Array<infer U>
  ? Array<DeepPartial<U>>
  : T[P] extends ReadonlyArray<infer U>
  ? ReadonlyArray<DeepPartial<U>>
  : T[P] extends Date | Function | Uint8Array | undefined
  ? T[P]
  : T[P] extends infer U | undefined
  ? DeepPartial<U>
  : T[P] extends object
  ? DeepPartial<T[P]>
  : T[P]
};