/**
 * Based on typescript definitions found at the url below
 * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/google-protobuf
 */

declare module 'google-protobuf' {
  declare type ByteSource = ArrayBuffer | Uint8Array | number[] | string;
  declare type ScalarFieldType = boolean | number | string;
  declare type RepeatedFieldType = ScalarFieldType[] | Uint8Array[];
  declare type AnyFieldType = ScalarFieldType | RepeatedFieldType | Uint8Array;
  declare type FieldValue =
    | string
    | number
    | boolean
    | Uint8Array
    | FieldValueArray
    | typeof undefined;
  declare type FieldValueArray = Array<FieldValue>;

  declare type Message$MessageArray = any[]; // This type needs to reference itself
  declare type Message$StaticToObject = (
    includeInstance: boolean,
    msg: Message
  ) => {};

  declare export class Message {
    getJsPbMessageId: () => ?string;
    static initialize: (
      msg: Message,
      data: Message$MessageArray,
      messageId: string | number,
      suggestedPivot: number,
      repeatedFields?: number[],
      oneofFields?: number[][] | null
    ) => void;
    static toObjectList: <T /* extends Message */>(
      field: T[],
      toObjectFn: (includeInstance: boolean, data: T) => {},
      includeInstance?: boolean
    ) => Array<{}>;
    static toObjectExtension: (
      msg: Message,
      obj: {},
      extensions: { [key: number]: ExtensionFieldInfo<Message> },
      getExtensionFn: (fieldInfo: ExtensionFieldInfo<Message>) => Message,
      includeInstance?: boolean
    ) => void;
    serializeBinaryExtensions: (
      proto: Message,
      writer: BinaryWriter,
      extensions: { [key: number]: ExtensionFieldBinaryInfo<Message> },
      getExtensionFn: <T>(fieldInfo: ExtensionFieldInfo<T>) => T
    ) => void;
    readBinaryExtension: (
      proto: Message,
      reader: BinaryReader,
      extensions: { [key: number]: ExtensionFieldBinaryInfo<Message> },
      setExtensionFn: <T>(fieldInfo: ExtensionFieldInfo<T>, val: T) => void
    ) => void;
    static getField: (msg: Message, fieldNumber: number) => FieldValue | null;
    static getOptionalFloatingPointField: (
      msg: Message,
      fieldNumber: number
    ) => ?number;
    static getRepeatedFloatingPointField: (
      msg: Message,
      fieldNumber: number
    ) => number[];
    static bytesAsB64: (bytes: Uint8Array) => string;
    static bytesAsU8: (str: string) => Uint8Array;
    static bytesListAsB64: (bytesList: Uint8Array[]) => string[];
    static bytesListAsU8: (strList: string[]) => Uint8Array[];
    static getFieldWithDefault: <T>(
      msg: Message,
      fieldNumber: number,
      defaultValue: T
    ) => T;
    static getMapField: (
      msg: Message,
      fieldNumber: number,
      noLazyCreate: boolean,
      valueCtor: typeof Message
    ) => Map<any, any>;
    static setField: (
      msg: Message,
      fieldNumber: number,
      value: FieldValue
    ) => void;
    static addToRepeatedField: (
      msg: Message,
      fieldNumber: number,
      value: any,
      index?: number
    ) => void;
    static setOneofField: (
      msg: Message,
      fieldNumber: number,
      oneof: number[],
      value: FieldValue
    ) => void;
    static computeOneofCase: (msg: Message, oneof: number[]) => number;
    static getWrapperField: <T /* extends Message */>(
      msg: Message,
      ctor: { new(): T },
      fieldNumber: number,
      required?: number
    ) => T;
    static getRepeatedWrapperField: <T /* extends Message */>(
      msg: Message,
      ctor: { new(): T },
      fieldNumber: number
    ) => T[];
    static setWrapperField: <T /*extends Message*/>(
      msg: Message,
      fieldNumber: number,
      value?: T | Map<any, any>
    ) => void;
    static setOneofWrapperField: (
      msg: Message,
      fieldNumber: number,
      oneof: number[],
      value: any
    ) => void;
    static setRepeatedWrapperField: <T /*extends Message*/>(
      msg: Message,
      fieldNumber: number,
      value?: T[]
    ) => void;
    static addToRepeatedWrapperField: <T /*extends Message*/>(
      msg: Message,
      fieldNumber: number,
      value: ?T,
      ctor: { new(): T },
      index?: number
    ) => T;
    static toMap: (
      field: any[],
      mapKeyGetterFn: (field: any) => string,
      toObjectFn?: Message$StaticToObject,
      includeInstance?: boolean
    ) => void;
    toArray: () => Message$MessageArray;
    toString: () => string;
    getExtension: <T>(fieldInfo: ExtensionFieldInfo<T>) => T;
    setExtension: <T>(fieldInfo: ExtensionFieldInfo<T>, value: T) => void;
    static difference: <T /*extends Message*/>(m1: T, m2: T) => T;
    static equals: (m1: Message, m2: Message) => boolean;
    static compareExtensions: (extension1: {}, extension2: {}) => boolean;
    static compareFields: (field1: any, field2: any) => boolean;
    cloneMessage: () => Message;
    clone: () => Message;
    static clone: <T /*extends Message*/>(msg: T) => T;
    static cloneMessage: <T /*extends Message*/>(msg: T) => T;
    static copyInto: (fromMessage: Message, toMessage: Message) => void;
    static registerMessageType: (
      id: number,
      constructor: typeof Message
    ) => void;

    // $FlowFixMe: flow doesn't support abstract instance method definitions.
    // /*abstract */ serializeBinary: () => Uint8Array;
    // /*abstract */ toObject: (includeInstance?: boolean) => {};

    // These are `abstract static`, but that isn't allowed. Subclasses of Message will have these methods and properties
    // and not having them on Message makes using this class for its intended purpose quite difficult.
    // static deserializeBinary: (bytes: Uint8Array) => Message;
    // static deserializeBinaryFromReader: (
    //   message: Message,
    //   reader: BinaryReader
    // ) => Message;
    // static serializeBinaryToWriter: (
    //   message: Message,
    //   writer: BinaryWriter
    // ) => void;
    // static toObject: (includeInstance: boolean, msg: Message) => {};
    // static extensions: { [key: number]: ExtensionFieldInfo<Message> };
    // static extensionsBinary: {
    //   [key: number]: ExtensionFieldBinaryInfo<Message>
    // };
  }

  declare export class ExtensionFieldInfo<T> {
    fieldIndex: number;
    fieldName: number;
    ctor: typeof Message;
    toObjectFn: Message$StaticToObject;
    isRepeated: number;
    constructor(
      fieldIndex: number,
      fieldName: { [key: string]: number },
      ctor: typeof Message,
      toObjectFn: Message$StaticToObject,
      isRepeated: number
    ): ExtensionFieldInfo<T>;
    isMessageType: () => boolean;
  }

  declare export class ExtensionFieldBinaryInfo<T> {
    fieldInfo: ExtensionFieldInfo<T>;
    binaryReaderFn: BinaryRead;
    binaryWriterFn: BinaryWrite;
    opt_binaryMessageSerializeFn: (msg: Message, writer: BinaryWriter) => void;
    opt_binaryMessageDeserializeFn: (
      msg: Message,
      reader: BinaryReader
    ) => Message;
    opt_isPacked: boolean;
    constructor(
      fieldInfo: ExtensionFieldInfo<T>,
      binaryReaderFn: BinaryRead,
      binaryWriterFn: BinaryWrite,
      opt_binaryMessageSerializeFn: (
        msg: Message,
        writer: BinaryWriter
      ) => void,
      opt_binaryMessageDeserializeFn: (
        msg: Message,
        reader: BinaryReader
      ) => Message,
      opt_isPacked: boolean
    ): ExtensionFieldBinaryInfo<T>;
  }

  declare export class Map<K, V> {
    constructor(
      arr: Array<[K, V]>,
      valueCtor?: { new(init: any): V }
    ): Map<K, V>;
    toArray: () => Array<[K, V]>;
    toObject: (includeInstance?: boolean) => Array<[K, V]>;
    toObject: <VO>(
      includeInstance: boolean,
      valueToObject: (includeInstance: boolean, valueWrapper: V) => VO
    ) => Array<[K, VO]>;
    static fromObject: <TK, TV>(
      entries: Array<[TK, TV]>,
      valueCtor: any,
      valueFromObject: any
    ) => Map<TK, TV>;
    getLength: () => number;
    clear: () => void;
    del: (key: K) => boolean;
    getEntryList: () => Array<[K, V]>;
    entries: () => Map$Iterator<[K, V]>;
    keys: () => Map$Iterator<K>;
    forEach: (callback: (entry: V, key: K) => void, thisArg?: {}) => void;
    set: (key: K, value: V) => this;
    get: (key: K) => ?V;
    has: (key: K) => boolean;
  }

  // This is implemented by jspb.Map.ArrayIteratorIterable_, but that class shouldn't be exported
  declare type Map$Iterator<T> = {
    next(): IteratorResult<T>,
  };

  declare type Map$IteratorResult<T> = {
    done: boolean,
    value: T,
  };

  declare type BinaryReadReader = (
    msg: any,
    binaryReader: BinaryReader
  ) => void;

  declare type BinaryRead = (msg: any, reader: BinaryReadReader) => void;

  declare type BinaryWriteCallback = (
    value: any,
    binaryWriter: BinaryWriter
  ) => void;

  declare type BinaryWrite = (
    fieldNumber: number,
    value: any,
    writerCallback: BinaryWriteCallback
  ) => void;

  declare export class BinaryReader {
    constructor(
      bytes?: ByteSource,
      start?: number,
      length?: number
    ): BinaryReader;
    static alloc: (
      bytes?: ByteSource,
      start?: number,
      length?: number
    ) => BinaryReader;
    alloc: (
      bytes?: ByteSource,
      start?: number,
      length?: number
    ) => BinaryReader;
    free: () => void;
    getFieldCursor: () => number;
    getCursor: () => number;
    getBuffer: () => Uint8Array;
    getFieldNumber: () => number;
    getWireType: () => BinaryConstants$WireType;
    isEndGroup: () => boolean;
    getError: () => boolean;
    setBlock: (bytes?: ByteSource, start?: number, length?: number) => void;
    reset: () => void;
    advance(count: number): void;
    nextField: () => boolean;
    unskipHeader: () => void;
    skipMatchingFields: () => void;
    skipVarintField: () => void;
    skipDelimitedField: () => void;
    skipFixed32Field: () => void;
    skipFixed64Field: () => void;
    skipGroup: () => void;
    skipField: () => void;
    registerReadCallback(
      callbackName: string,
      callback: (binaryReader: BinaryReader) => any
    ): void;
    runReadCallback(callbackName: string): any;
    readAny(fieldType: BinaryConstants$FieldType): AnyFieldType;
    readMessage: BinaryRead;
    readGroup(field: number, message: Message, reader: BinaryReadReader): void;
    getFieldDecoder: () => BinaryDecoder;
    readInt32: () => number;
    readInt32String: () => string;
    readInt64: () => number;
    readInt64String: () => string;
    readUint32: () => number;
    readUint32String: () => string;
    readUint64: () => number;
    readUint64String: () => string;
    readSint32: () => number;
    readSint64: () => number;
    readSint64String: () => string;
    readFixed32: () => number;
    readFixed64: () => number;
    readFixed64String: () => string;
    readSfixed32: () => number;
    readSfixed32String: () => string;
    readSfixed64: () => number;
    readSfixed64String: () => string;
    readFloat: () => number;
    readDouble: () => number;
    readBool: () => boolean;
    readEnum: () => number;
    readString: () => string;
    readBytes: () => Uint8Array;
    readVarintHash64: () => string;
    readFixedHash64: () => string;
    readPackedInt32: () => number[];
    readPackedInt32String: () => string[];
    readPackedInt64: () => number[];
    readPackedInt64String: () => string[];
    readPackedUint32: () => number[];
    readPackedUint32String: () => string[];
    readPackedUint64: () => number[];
    readPackedUint64String: () => string[];
    readPackedSint32: () => number[];
    readPackedSint64: () => number[];
    readPackedSint64String: () => string[];
    readPackedFixed32: () => number[];
    readPackedFixed64: () => number[];
    readPackedFixed64String: () => string[];
    readPackedSfixed32: () => number[];
    readPackedSfixed64: () => number[];
    readPackedSfixed64String: () => string[];
    readPackedFloat: () => number[];
    readPackedDouble: () => number[];
    readPackedBool: () => boolean[];
    readPackedEnum: () => number[];
    readPackedVarintHash64: () => string[];
    readPackedFixedHash64: () => string[];
  }

  declare export class BinaryWriter {
    constructor(): BinaryWriter;
    writeSerializedMessage(bytes: Uint8Array, start: number, end: number): void;
    maybeWriteSerializedMessage(
      bytes?: Uint8Array,
      start?: number,
      end?: number
    ): void;
    reset: () => void;
    getResultBuffer: () => Uint8Array;
    getResultBase64String: () => string;
    beginSubMessage(field: number): void;
    endSubMessage(field: number): void;
    writeAny(
      fieldType: BinaryConstants$FieldType,
      field: number,
      value: AnyFieldType
    ): void;
    writeInt32(field: number, value?: number): void;
    writeInt32String(field: number, value?: string): void;
    writeInt64(field: number, value?: number): void;
    writeInt64String(field: number, value?: string): void;
    writeUint32(field: number, value?: number): void;
    writeUint32String(field: number, value?: string): void;
    writeUint64(field: number, value?: number): void;
    writeUint64String(field: number, value?: string): void;
    writeSint32(field: number, value?: number): void;
    writeSint64(field: number, value?: number): void;
    writeSint64String(field: number, value?: string): void;
    writeFixed32(field: number, value?: number): void;
    writeFixed64(field: number, value?: number): void;
    writeFixed64String(field: number, value?: string): void;
    writeSfixed32(field: number, value?: number): void;
    writeSfixed64(field: number, value?: number): void;
    writeSfixed64String(field: number, value?: string): void;
    writeFloat(field: number, value?: number): void;
    writeDouble(field: number, value?: number): void;
    writeBool(field: number, value?: boolean): void;
    writeEnum(field: number, value?: number): void;
    writeString(field: number, value?: string): void;
    writeBytes(field: number, value?: ByteSource): void;
    writeMessage: BinaryWrite;
    writeGroup(
      field: number,
      value: any,
      writeCallback: BinaryWriteCallback
    ): void;
    writeFixedHash64(field: number, value?: string): void;
    writeVarintHash64(field: number, value?: string): void;
    writeRepeatedInt32(field: number, value?: number[]): void;
    writeRepeatedInt32String(field: number, value?: string[]): void;
    writeRepeatedInt64(field: number, value?: number[]): void;
    writeRepeatedInt64String(field: number, value?: string[]): void;
    writeRepeatedUint32(field: number, value?: number[]): void;
    writeRepeatedUint32String(field: number, value?: string[]): void;
    writeRepeatedUint64(field: number, value?: number[]): void;
    writeRepeatedUint64String(field: number, value?: string[]): void;
    writeRepeatedSint32(field: number, value?: number[]): void;
    writeRepeatedSint64(field: number, value?: number[]): void;
    writeRepeatedSint64String(field: number, value?: string[]): void;
    writeRepeatedFixed32(field: number, value?: number[]): void;
    writeRepeatedFixed64(field: number, value?: number[]): void;
    writeRepeatedFixed64String(field: number, value?: string[]): void;
    writeRepeatedSfixed32(field: number, value?: number[]): void;
    writeRepeatedSfixed64(field: number, value?: number[]): void;
    writeRepeatedSfixed64String(field: number, value?: string[]): void;
    writeRepeatedFloat(field: number, value?: number[]): void;
    writeRepeatedDouble(field: number, value?: number[]): void;
    writeRepeatedBool(field: number, value?: boolean[]): void;
    writeRepeatedEnum(field: number, value?: number[]): void;
    writeRepeatedString(field: number, value?: string[]): void;
    writeRepeatedBytes(field: number, value?: ByteSource[]): void;
    writeRepeatedMessage(
      field: number,
      value: Message[],
      writerCallback: BinaryWriteCallback
    ): void;
    writeRepeatedGroup(
      field: number,
      value: Message[],
      writerCallback: BinaryWriteCallback
    ): void;
    writeRepeatedFixedHash64(field: number, value?: string[]): void;
    writeRepeatedVarintHash64(field: number, value?: string[]): void;
    writePackedInt32(field: number, value?: number[]): void;
    writePackedInt32String(field: number, value?: string[]): void;
    writePackedInt64(field: number, value?: number[]): void;
    writePackedInt64String(field: number, value?: string[]): void;
    writePackedUint32(field: number, value?: number[]): void;
    writePackedUint32String(field: number, value?: string[]): void;
    writePackedUint64(field: number, value?: number[]): void;
    writePackedUint64String(field: number, value?: string[]): void;
    writePackedSint32(field: number, value?: number[]): void;
    writePackedSint64(field: number, value?: number[]): void;
    writePackedSint64String(field: number, value?: string[]): void;
    writePackedFixed32(field: number, value?: number[]): void;
    writePackedFixed64(field: number, value?: number[]): void;
    writePackedFixed64String(field: number, value?: string[]): void;
    writePackedSfixed32(field: number, value?: number[]): void;
    writePackedSfixed64(field: number, value?: number[]): void;
    writePackedSfixed64String(field: number, value?: string[]): void;
    writePackedFloat(field: number, value?: number[]): void;
    writePackedDouble(field: number, value?: number[]): void;
    writePackedBool(field: number, value?: boolean[]): void;
    writePackedEnum(field: number, value?: number[]): void;
    writePackedFixedHash64(field: number, value?: string[]): void;
    writePackedVarintHash64(field: number, value?: string[]): void;
  }

  declare export class BinaryEncoder {
    constructor(): BinaryEncoder;
    length: () => number;
    end: () => number[];
    writeSplitVarint64(lowBits: number, highBits: number): void;
    writeSplitFixed64(lowBits: number, highBits: number): void;
    writeUnsignedVarint32(value: number): void;
    writeSignedVarint32(value: number): void;
    writeUnsignedVarint64(value: number): void;
    writeSignedVarint64(value: number): void;
    writeZigzagVarint32(value: number): void;
    writeZigzagVarint64(value: number): void;
    writeZigzagVarint64String(value: string): void;
    writeUint8(value: number): void;
    writeUint16(value: number): void;
    writeUint32(value: number): void;
    writeUint64(value: number): void;
    writeInt8(value: number): void;
    writeInt16(value: number): void;
    writeInt32(value: number): void;
    writeInt64(value: number): void;
    writeInt64String(value: string): void;
    writeFloat(value: number): void;
    writeDouble(value: number): void;
    writeBool(value: boolean): void;
    writeEnum(value: number): void;
    writeBytes(bytes: Uint8Array): void;
    writeVarintHash64(hash: string): void;
    writeFixedHash64(hash: string): void;
    writeString(value: string): number;
  }

  declare export class BinaryDecoder {
    constructor(
      bytes?: ByteSource,
      start?: number,
      length?: number
    ): BinaryDecoder;
    static alloc: (
      bytes?: ByteSource,
      start?: number,
      length?: number
    ) => BinaryDecoder;
    free: () => void;
    clone: () => BinaryDecoder;
    clear: () => void;
    getBuffer: () => Uint8Array;
    setBlock(data: ByteSource, start?: number, length?: number): void;
    getEnd: () => number;
    setEnd(end: number): void;
    reset: () => void;
    getCursor: () => number;
    setCursor(cursor: number): void;
    advance(count: number): void;
    atEnd: () => boolean;
    pastEnd: () => boolean;
    getError: () => boolean;
    skipVarint: () => void;
    unskipVarint(value: number): void;
    readUnsignedVarint32: () => number;
    readSignedVarint32: () => number;
    readUnsignedVarint32String: () => number;
    readSignedVarint32String: () => number;
    readZigzagVarint32: () => number;
    readUnsignedVarint64: () => number;
    readUnsignedVarint64String: () => number;
    readSignedVarint64: () => number;
    readSignedVarint64String: () => number;
    readZigzagVarint64: () => number;
    readZigzagVarint64String: () => number;
    readUint8: () => number;
    readUint16: () => number;
    readUint32: () => number;
    readUint64: () => number;
    readUint64String: () => string;
    readInt8: () => number;
    readInt16: () => number;
    readInt32: () => number;
    readInt64: () => number;
    readInt64String: () => string;
    readFloat: () => number;
    readDouble: () => number;
    readBool: () => boolean;
    readEnum: () => number;
    readString(length: number): string;
    readStringWithLength: () => string;
    readBytes(length: number): Uint8Array;
    readVarintHash64: () => string;
    readFixedHash64: () => string;
  }

  declare export class BinaryIterator {
    constructor(
      decoder?: BinaryDecoder,
      next?: () => number | boolean | string | null,
      elements?: Array<number | boolean | string>
    ): BinaryIterator;
    static alloc: (
      decoder?: BinaryDecoder,
      next?: () => number | boolean | string | null,
      elements?: Array<number | boolean | string>
    ) => BinaryIterator;
    free: () => void;
    clear: () => void;
    get: () => ?ScalarFieldType;
    atEnd: () => boolean;
    next: () => ?ScalarFieldType;
  }

  declare type BinaryConstants$FieldType = {
    INVALID: -1,
    DOUBLE: 1,
    FLOAT: 2,
    INT64: 3,
    UINT64: 4,
    INT32: 5,
    FIXED64: 6,
    FIXED32: 7,
    BOOL: 8,
    STRING: 9,
    GROUP: 10,
    MESSAGE: 11,
    BYTES: 12,
    UINT32: 13,
    ENUM: 14,
    SFIXED32: 15,
    SFIXED64: 16,
    SINT32: 17,
    SINT64: 18,
    FHASH64: 30,
    VHASH64: 31,
  };

  declare type BinaryConstants$WireType = {
    INVALID: -1,
    VARINT: 0,
    FIXED64: 1,
    DELIMITED: 2,
    START_GROUP: 3,
    END_GROUP: 4,
    FIXED32: 5,
  };

  //   const FieldTypeToWireType: (fieldType: FieldType) => WireType;

  //   const INVALID_FIELD_NUMBER: number;
  //   const FLOAT32_EPS: number;
  //   const FLOAT32_MIN: number;
  //   const FLOAT32_MAX: number;
  //   const FLOAT64_EPS: number;
  //   const FLOAT64_MIN: number;
  //   const FLOAT64_MAX: number;
  //   const TWO_TO_20: number;
  //   const TWO_TO_23: number;
  //   const TWO_TO_31: number;
  //   const TWO_TO_32: number;
  //   const TWO_TO_52: number;
  //   const TWO_TO_63: number;
  //   const TWO_TO_64: number;
  //   const ZERO_HASH: string;
  // }

  // export namespace arith {
  //   class UInt64 {
  //     lo: number;
  //     hi: number;
  //     constructor(lo: number, hi: number);
  //     cmp(other: UInt64): number;
  //     rightShift(): UInt64;
  //     leftShift(): UInt64;
  //     msb(): boolean;
  //     lsb(): boolean;
  //     zero(): boolean;
  //     add(other: UInt64): UInt64;
  //     sub(other: UInt64): UInt64;
  //     static mul32x32(a: number, b: number): UInt64;
  //     mul(a: number): UInt64;
  //     div(divisor: number): [UInt64, UInt64];
  //     toString(): string;
  //     static fromString(str: string): UInt64;
  //     clone(): UInt64;
  //   }

  //   class Int64 {
  //     lo: number;
  //     hi: number;
  //     constructor(lo: number, hi: number);
  //     add(other: Int64): Int64;
  //     sub(other: Int64): Int64;
  //     clone(): Int64;
  //     toString(): string;
  //     static fromString(str: string): Int64;
  //   }
  // }

  // jspb.utils package excluded as it likely shouldn't be called by user code
}
