/*!
 * Copyright (c) 2017-2018 by The Funfix Project Developers.
 * Some rights reserved.
 *
 * 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.
 */

/* @flow */

import type { HK, Monad } from "funland"
import type { Throwable } from "funfix-core"
import { Either, Try, Option } from "funfix-core";
import { ICancelable, StackedCancelable, Scheduler, Future, ExecutionModel, Duration } from "funfix-exec";

declare export class IO<+A> {
  run(ec?: Scheduler): Future<A>;
  runOnComplete(cb: (result: Try<A>) => void, ec?: Scheduler): ICancelable;

  attempt(): IO<Either<Throwable, A>>;
  asyncBoundary(ec?: Scheduler): IO<A>;
  chain<B>(f: (a: A) => IO<B>): IO<B>;
  delayExecution(delay: number | Duration): IO<A>;
  delayResult(delay: number | Duration): IO<A>;
  doOnFinish(f: (e: Option<Throwable>) => IO<void>): IO<A>;
  doOnCancel(callback: IO<void>): IO<A>;
  flatMap<B>(f: (a: A) => IO<B>): IO<B>;
  ap<B>(ff: IO<(a: A) => B>): IO<B>;
  followedBy<B>(fb: IO<B>): IO<B>;
  forEach(cb: (a: A) => void): IO<void>;
  forEffect<B>(fb: IO<B>): IO<A>;
  executeForked(ec?: Scheduler): IO<A>;
  executeWithModel(em: ExecutionModel): IO<A>;
  executeWithOptions(set: IOOptions): IO<A>;
  map<B>(f: (a: A) => B): IO<B>;
  memoize(): IO<A>;
  memoizeOnSuccess(): IO<A>;
  recover<AA>(f: (e: Throwable) => AA): IO<A | AA>;
  recoverWith<AA>(f: (e: Throwable) => IO<AA>): IO<A | AA>;
  timeout(after: number | Duration): IO<A>;
  timeoutTo<AA>(after: number | Duration, fallback: IO<AA>): IO<A | AA>;
  transform<R>(failure: (e: Throwable) => R, success: (a: A) => R): IO<R>;
  transformWith<R>(failure: (e: Throwable) => IO<R>, success: (a: A) => IO<R>): IO<R>;

  +_tag: "pure" | "always" | "once" | "flatMap" | "async" | "memoize";

  static +_Class: IO<any>;
  static always<A>(thunk: () => A): IO<A>;
  static async<A>(register: (ec: Scheduler, cb: (a: Try<A>) => void) => ICancelable | void): IO<A>;
  static asyncUnsafe<A>(register: IORegister<A>): IO<A>;
  static defer<A>(thunk: () => IO<A>): IO<A>;
  static deferAction<A>(f: (ec: Scheduler) => IO<A>): IO<A>;
  static deferFuture<A>(thunk: () => Future<A>): IO<A>;
  static deferFutureAction<A>(f: (ec: Scheduler) => Future<A>): IO<A>;
  static delayedTick<A>(delay: number | Duration): IO<void>;
  static firstCompletedOf<A>(list: Iterable<IO<A>>): IO<A>;
  static fromFuture<A>(fa: Future<A>): IO<A>;
  static fromTry<A>(a: Try<A>): IO<A>;
  static fork<A>(fa: IO<A>, ec?: Scheduler): IO<A>;
  static map2<A1, A2, R>(fa1: IO<A1>, fa2: IO<A2>, f: (a1: A1, a2: A2) => R): IO<R>;
  static map3<A1, A2, A3, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, f: (a1: A1, a2: A2, a3: A3) => R): IO<R>;
  static map4<A1, A2, A3, A4, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, fa4: IO<A4>, f: (a1: A1, a2: A2, a3: A3, a4: A4) => R): IO<R>;
  static map5<A1, A2, A3, A4, A5, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, fa4: IO<A4>, fa5: IO<A5>, f: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => R): IO<R>;
  static map6<A1, A2, A3, A4, A5, A6, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, fa4: IO<A4>, fa5: IO<A5>, fa6: IO<A6>, f: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => R): IO<R>;
  static now<A>(value: A): IO<A>;
  static of<A>(thunk: () => A): IO<A>;
  static once<A>(thunk: () => A): IO<A>;
  static parMap2<A1, A2, R>(fa1: IO<A1>, fa2: IO<A2>, f: (a1: A1, a2: A2) => R): IO<R>;
  static parMap3<A1, A2, A3, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, f: (a1: A1, a2: A2, a3: A3) => R): IO<R>;
  static parMap4<A1, A2, A3, A4, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, fa4: IO<A4>, f: (a1: A1, a2: A2, a3: A3, a4: A4) => R): IO<R>;
  static parMap5<A1, A2, A3, A4, A5, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, fa4: IO<A4>, fa5: IO<A5>, f: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => R): IO<R>;
  static parMap6<A1, A2, A3, A4, A5, A6, R>(fa1: IO<A1>, fa2: IO<A2>, fa3: IO<A3>, fa4: IO<A4>, fa5: IO<A5>, fa6: IO<A6>, f: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => R): IO<R>;
  static pure<A>(value: A): IO<A>;
  static raise(e: Throwable): IO<empty>;
  static sequence<A>(list: Iterable<IO<A>>): IO<A[]>;
  static gather<A>(list: Iterable<IO<A>>): IO<A[]>;
  static shift(ec?: Scheduler): IO<void>;
  static suspend<A>(thunk: () => IO<A>): IO<A>;
  static tailRecM<A, B>(a: A, f: (a: A) => IO<Either<A, B>>): IO<B>;
  static unit(): IO<void>;
  static unsafeStart<A>(source: IO<A>, context: IOContext, cb: (r: Try<A>) => void): void | ICancelable;
}

export type IOURI = <U, L, A>(x: [U, L, A]) => IO<A>;
export type IOK<A> = HK<IOURI, A>;

export type IOTypes = Monad<IOURI>;
declare export var IOModule: IOTypes;

export type IORegister<A> =
  (context: IOContext, callback: (result: Try<A>) => void) => void;

declare export class IOContext {
  +scheduler: Scheduler;
  +connection: StackedCancelable;
  +options: IOOptions;
  constructor(scheduler: Scheduler, connection?: StackedCancelable, options?: IOOptions): IOContext;

  markAsyncBoundary(): void;
  shouldCancel(): boolean;
}

export type IOOptions = {
  autoCancelableRunLoops: boolean;
};
