/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { EmulatorControllerClient } from "../proto/emulator_controller_grpc_web_pb";
import { RtcClient } from "../proto/rtc_service_grpc_web_pb";
import { GrpcWebClientBase } from "grpc-web";
import { EventEmitter } from "events";

export class NopAuthenticator {
  authHeader = () => {
    return {};
  };

  unauthorized = () => { };
}

/**
 * A GrcpWebClientBase that inject authentication headers and intercepts
 * errors. If the errors are 401, the unauthorized method of the authenticator will be invoked.
 *
 * @export
 * @class EmulatorWebClient
 * @extends {GrpcWebClientBase}
 */
class EmulatorWebClient extends GrpcWebClientBase {
  constructor(options, auth) {
    super(options);
    this.auth = auth;
    this.events = new EventEmitter();
    this.events.on("error", e => {console.log("low level gRPC error: " + JSON.stringify(e));})
  }

  on = (name, fn) => {
    this.events.on(name, fn);
  };

  rpcCall = (method, request, metadata, methodinfo, callback) => {
    const authHeader = this.auth.authHeader();
    const meta = { ...metadata, ...authHeader };
    const self = this;
    return super.rpcCall(method, request, meta, methodinfo, (err, res) => {
      if (err) {
        if (err.code === 401) self.auth.unauthorized();
        if (self.events)
          self.events.emit("error", err);
      }
      if (callback) callback(err, res);
    });
  };

  serverStreaming = (method, request, metadata, methodInfo) => {
    const authHeader = this.auth.authHeader();
    const meta = { ...metadata, ...authHeader };
    const stream = super.serverStreaming(method, request, meta, methodInfo);
    const self = this;

    // Intercept errors.
    stream.on("error", e => {
      if (e.code === 401) {
        self.auth.unauthorized();
      }
      self.events.emit("error", e);
    });
    return stream;
  };
}

/**
 * An EmulatorControllerService is an EmulatorControllerClient that inject authentication headers.
 * You can provide your own authenticator service that must implement the following mehtods:
 *
 * - `authHeader()` which must return a set of headers that should be send along with a request.
 * - `unauthorized()` a function that gets called when a 401 was received.
 *
 * You can use this to simplify handling authentication failures.
 *
 * TODO(jansene): Maybe expose error handling? That way it does
 * not have to be repeated at every function call.
 *
 * @export
 * @class EmulatorControllerService
 * @extends {EmulatorControllerClient}
 */
export class EmulatorControllerService extends EmulatorControllerClient {
  /**
   *Creates an instance of EmulatorControllerService.
   * @param {string} uri of the emulator controller endpoint.
   * @param {Authenticator} authenticator used to authenticate with the emulator endpoint.
   * @param onError callback that will be invoked when a low level gRPC error arises.
   * @memberof EmulatorControllerService
   */
  constructor(uri, authenticator, onError) {
    super(uri);
    if (!authenticator) authenticator = new NopAuthenticator();
    this.client_ = new EmulatorWebClient({}, authenticator);
    if (onError) this.client_.on('error', e => { onError(e); });
  }
}


/**
 * An RtcService is an RtcClient that inject authentication headers.
 * You can provide your own authenticator service that must implement the following mehtods:
 *
 * - `authHeader()` which must return a set of headers that should be send along with a request.
 * - `unauthorized()` a function that gets called when a 401 was received.
 *
 * You can use this to simplify handling authentication failures.
 *
 * @export
 * @class EmulatorControllerService
 * @extends {RtcClient}
 */
export class RtcService extends RtcClient {
  /**
   *Creates an instance of RtcService.
   * @param {string} uri of the emulator controller endpoint.
   * @param {Authenticator} authenticator used to authenticate with the emulator endpoint.
   * @param onError callback that will be invoked when a low level gRPC error arises.
   * @memberof RtcService
   */
  constructor(uri, authenticator, onError) {
    super(uri);
    if (!authenticator) authenticator = new NopAuthenticator();
    this.client_ = new EmulatorWebClient({}, authenticator);
    if (onError) this.client_.on('error', e => { onError(e); });
  }
}