import { Batch, Tip, IQueryResponse, Sid, ResponseHeaders, IHeaderValue } from "./models/types";
import { Eno } from "./models/Eno";
import { Observable } from "rxjs";
import { IEnSrvOptions, IEnSrvOptionsLite } from "./IEnSrvOptions";
import { send } from "./send";
import { pull, pullSid, IPullOptions } from "./pull";
import { read, readSid, IReadOptions } from "./read";
import { locale, ILocale } from "./locale";
import { write } from "./write";
import { writeMany } from "./writeMany";
import {
  startProcess,
  IProcessOptions,
  IProcessResponse,
  getProcessStatus,
} from "./process";
import { evalFormula, IFormulaOptions } from "./formula";
import { execute, execute1d, execute1dWithResponseHeaders, IQueryOption } from "./query";
import { tap } from "rxjs/operators";

export class EnSrv {
  options: IEnSrvOptions;
  systemTimezone?: string;
  fn = {
    send,
    pull,
    pullSid,
    read,
    readSid,
    locale,
    startProcess,
    getProcessStatus,
    execute,
    execute1d,
    execute1dWithResponseHeaders,
    write,
    writeMany,
    evalFormula,
  };

  constructor(options: IEnSrvOptions) {
    this.options = options;
  }

  /**
   * Returns true if the abort signal has been received
   * 
   * @returns true if the abort signal has been received
   */
  isAborted(): boolean {
    return !!this.options.abortController?.signal.aborted;
  }

  // Send a batch of enos to EnSrv
  send(batch: Batch, enSrvOptions?: IEnSrvOptionsLite): Observable<Batch> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .send(batch, opts)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Pull a batch of enos from EnSrv
  pull(
    tips: Tip[],
    pullOptions?: IPullOptions,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<Batch> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .pull(tips, opts, pullOptions)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Pull a batch of enos by sid from EnSrv
  pullSid(
    sids: Sid[],
    pullOptions?: IPullOptions,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<Batch> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .pullSid(sids, opts, pullOptions)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Read an eno from EnSrv
  read(
    tip: Tip,
    readOptions?: IReadOptions,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<Eno> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .read(tip, opts, readOptions)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Read an eno from EnSrv
  readSid(
    sid: Sid,
    readOptions?: IReadOptions,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<Eno> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .readSid(sid, opts, readOptions)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Read the locale from EnSrv
  locale(enSrvOptions?: IEnSrvOptionsLite): Observable<ILocale> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn.locale(opts).pipe(
      tap(() => {
        this.options.sessionToken = opts.sessionToken;
        this.options.locale = opts.locale;
      })
    );
  }

  // Start a process on EnSrv
  startProcess(
    tip: Tip,
    processOptions?: IProcessOptions,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<IProcessResponse> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .startProcess(tip, opts, processOptions)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Check the status of a process
  getProcessStatus(
    processOpTip: Tip,
    enSrvOptions: IEnSrvOptions
  ): Observable<IProcessResponse> {
    return this.fn.getProcessStatus(processOpTip, enSrvOptions);
  }

  // Evaluate a formula on EnSrv
  evalFormula(
    formulaStr: string,
    formulaOptions?: IFormulaOptions,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<string[]> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .evalFormula(formulaStr, opts, formulaOptions)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Execute a query on EnSrv
  executeQuery(
    queryTip: Tip,
    queryOptions?: IQueryOption,
    timeoutMs: number = 10000,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<IQueryResponse> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .execute(queryTip, opts, queryOptions, timeoutMs)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Execute a one-dimensional query on EnSrv
  executeQuery1d<T>(
    queryTip: Tip,
    queryOptions?: IQueryOption,
    timeoutMs: number = 10000,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<T[]> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .execute1d<T>(queryTip, opts, queryOptions, timeoutMs)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  executeQuery1dWithResponseHeader<T>(
    queryTip: Tip,
    queryOptions?: IQueryOption,
    timeoutMs: number = 10000,
    enSrvOptions?: IEnSrvOptionsLite
  ): Observable<T[] | { results: T[]; responseHeaders: IHeaderValue[] }> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .execute1dWithResponseHeaders<T>(queryTip, opts, queryOptions, timeoutMs)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Write an eno to EnSrv
  write(eno: Eno, enSrvOptions?: IEnSrvOptionsLite): Observable<any> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .write(eno, opts)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }

  // Write many enos to EnSrv
  writeMany(enos: Eno[], enSrvOptions?: IEnSrvOptionsLite): Observable<any> {
    const opts = { ...this.options, ...enSrvOptions };
    return this.fn
      .writeMany(enos, opts)
      .pipe(tap(() => (this.options.sessionToken = opts.sessionToken)));
  }
}
