/*!
 * Copyright (c) 2017 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 { Throwable } from "funfix-core"
import { Option, Try, Either } from "funfix-core"
import { Future } from "funfix-exec"
import { Eval, IO } from "funfix-effect"
import { HK } from "./kinds"

export type OptionK<A> = HK<Option<any>, A>;

declare export class OptionInstances {
  eqv(lh: Option<any>, rh: Option<any>): boolean;
  pure<A>(a: A): Option<A>;
  unit(): Option<void>;
  ap<A, B>(fa: OptionK<A>, ff: OptionK<(a: A) => B>): Option<B>;
  map<A, B>(fa: OptionK<A>, f: (a: A) => B): Option<B>;
  map2<A, B, Z>(fa: OptionK<A>, fb: OptionK<B>, f: (a: A, b: B) => Z): Option<Z>;
  product<A, B>(fa: OptionK<A>, fb: OptionK<B>): Option<[A, B]>;
  flatMap<A, B>(fa: OptionK<A>, f: (a: A) => OptionK<B>): Option<B>;
  tailRecM<A, B>(a: A, f: (a: A) => OptionK<Either<A, B>>): Option<B>;
  coflatMap<A, B>(fa: OptionK<A>, ff: (a: OptionK<A>) => B): Option<B>;
  coflatten<A>(fa: OptionK<A>): Option<Option<A>>;
  followedBy<A, B>(fa: OptionK<A>, fb: OptionK<B>): Option<B>;
  followedByL<A, B>(fa: OptionK<A>, fb: () => OptionK<B>): Option<B>;
  forEffect<A, B>(fa: OptionK<A>, fb: OptionK<B>): Option<A>;
  forEffectL<A, B>(fa: OptionK<A>, fb: () => OptionK<B>): Option<A>;

  static global: OptionInstances;
}

export type TryK<A> = HK<Try<any>, A>;

declare export class TryInstances {
  eqv(lh: Try<any>, rh: Try<any>): boolean;
  pure<A>(a: A): Try<A>;
  unit(): Try<void>;
  ap<A, B>(fa: TryK<A>, ff: TryK<(a: A) => B>): Try<B>;
  map<A, B>(fa: TryK<A>, f: (a: A) => B): Try<B>;
  map2<A, B, Z>(fa: TryK<A>, fb: TryK<B>, f: (a: A, b: B) => Z): Try<Z>;
  product<A, B>(fa: TryK<A>, fb: TryK<B>): Try<[A, B]>;
  flatMap<A, B>(fa: TryK<A>, f: (a: A) => TryK<B>): Try<B>;
  tailRecM<A, B>(a: A, f: (a: A) => TryK<Either<A, B>>): Try<B>;
  coflatMap<A, B>(fa: TryK<A>, ff: (a: TryK<A>) => B): Try<B>;
  coflatten<A>(fa: TryK<A>): Try<Try<A>>;
  followedBy<A, B>(fa: TryK<A>, fb: TryK<B>): Try<B>;
  followedByL<A, B>(fa: TryK<A>, fb: () => TryK<B>): Try<B>;
  forEffect<A, B>(fa: TryK<A>, fb: TryK<B>): Try<A>;
  forEffectL<A, B>(fa: TryK<A>, fb: () => TryK<B>): Try<A>;
  raise<A>(e: Throwable): Try<A>;
  attempt<A>(fa: TryK<A>): Try<Either<Throwable, A>>;
  recoverWith<A>(fa: TryK<A>, f: (e: Throwable) => TryK<A>): Try<A>;
  recover<A>(fa: TryK<A>, f: (e: Throwable) => A): Try<A>;

  static global: TryInstances;
}

export type EitherK<L, R> = HK<Either<L, any>, R>;

declare export class EitherInstances<L> {
  eqv(lh: Either<L, any>, rh: Either<L, any>): boolean;
  pure<A>(a: A): Either<L, A>;
  unit(): Either<L, void>;
  ap<A, B>(fa: EitherK<L, A>, ff: EitherK<L, (a: A) => B>): Either<L, B>;
  map<A, B>(fa: EitherK<L, A>, f: (a: A) => B): Either<L, B>;
  map2<A, B, Z>(fa: EitherK<L, A>, fb: EitherK<L, B>, f: (a: A, b: B) => Z): Either<L, Z>;
  product<A, B>(fa: EitherK<L, A>, fb: EitherK<L, B>): Either<L, [A, B]>;
  flatMap<A, B>(fa: EitherK<L, A>, f: (a: A) => EitherK<L, B>): Either<L, B>;
  tailRecM<A, B>(a: A, f: (a: A) => EitherK<L, Either<A, B>>): Either<L, B>;
  coflatMap<A, B>(fa: EitherK<L, A>, ff: (a: EitherK<L, A>) => B): Either<L, B>;
  coflatten<A>(fa: EitherK<L, A>): Either<L, Either<L, A>>;
  followedBy<A, B>(fa: EitherK<L, A>, fb: EitherK<L, B>): Either<L, B>;
  followedByL<A, B>(fa: EitherK<L, A>, fb: () => EitherK<L, B>): Either<L, B>;
  forEffect<A, B>(fa: EitherK<L, A>, fb: EitherK<L, B>): Either<L, A>;
  forEffectL<A, B>(fa: EitherK<L, A>, fb: () => EitherK<L, B>): Either<L, A>;

  static global: EitherInstances<any>;
}

export type EvalK<A> = HK<Eval<any>, A>;

declare export class EvalInstances {
  pure<A>(a: A): Eval<A>;
  unit(): Eval<void>;
  ap<A, B>(fa: EvalK<A>, ff: EvalK<(a: A) => B>): Eval<B>;
  map<A, B>(fa: EvalK<A>, f: (a: A) => B): Eval<B>;
  map2<A, B, Z>(fa: EvalK<A>, fb: EvalK<B>, f: (a: A, b: B) => Z): Eval<Z>;
  product<A, B>(fa: EvalK<A>, fb: EvalK<B>): Eval<[A, B]>;
  flatMap<A, B>(fa: EvalK<A>, f: (a: A) => EvalK<B>): Eval<B>;
  tailRecM<A, B>(a: A, f: (a: A) => EvalK<Either<A, B>>): Eval<B>;
  followedBy<A, B>(fa: EvalK<A>, fb: EvalK<B>): Eval<B>;
  followedByL<A, B>(fa: EvalK<A>, fb: () => EvalK<B>): Eval<B>;
  forEffect<A, B>(fa: EvalK<A>, fb: EvalK<B>): Eval<A>;
  forEffectL<A, B>(fa: EvalK<A>, fb: () => EvalK<B>): Eval<A>;
  coflatMap<A, B>(fa: EvalK<A>, ff: (a: EvalK<A>) => B): Eval<B>;
  coflatten<A>(fa: EvalK<A>): Eval<Eval<A>>;
  extract<A>(fa: EvalK<A>): A;

  static global: EvalInstances;
}

export type FutureK<A> = HK<Future<any>, A>;

declare export class FutureInstances {
  pure<A>(a: A): Future<A>;
  flatMap<A, B>(fa: FutureK<A>, f: (a: A) => FutureK<B>): Future<B>;
  tailRecM<A, B>(a: A, f: (a: A) => FutureK<Either<A, B>>): Future<B>;
  ap<A, B>(fa: FutureK<A>, ff: FutureK<(a: A) => B>): Future<B>;
  map<A, B>(fa: FutureK<A>, f: (a: A) => B): Future<B>;
  unit(): Future<void>;
  raise<A>(e: Throwable): Future<A>;
  attempt<A>(fa: FutureK<A>): Future<Either<Throwable, A>>;
  recoverWith<A>(fa: FutureK<A>, f: (e: Throwable) => FutureK<A>): Future<A>;
  recover<A>(fa: FutureK<A>, f: (e: Throwable) => A): Future<A>;
  coflatMap<A, B>(fa: FutureK<A>, ff: (a: FutureK<A>) => B): Future<B>;
  coflatten<A>(fa: FutureK<A>): Future<Future<A>>;
  map2: <A, B, Z>(fa: FutureK<A>, fb: FutureK<B>, f: (a: A, b: B) => Z) => Future<Z>;
  product: <A, B>(fa: FutureK<A>, fb: FutureK<B>) => FutureK<[A, B]>;
  followedBy: <A, B>(fa: FutureK<A>, fb: FutureK<B>) => Future<B>;
  followedByL: <A, B>(fa: FutureK<A>, fb: () => FutureK<B>) => Future<B>;
  forEffect: <A, B>(fa: FutureK<A>, fb: FutureK<B>) => Future<A>;
  forEffectL: <A, B>(fa: FutureK<A>, fb: () => FutureK<B>) => Future<A>;

  static global: FutureInstances;
}

export type IOK<A> = HK<IO<any>, A>;

declare export class IOInstances {
  pure<A>(a: A): IO<A>;
  flatMap<A, B>(fa: IOK<A>, f: (a: A) => IOK<B>): IO<B>;
  tailRecM<A, B>(a: A, f: (a: A) => IOK<Either<A, B>>): IO<B>;
  ap<A, B>(fa: IOK<A>, ff: IOK<(a: A) => B>): IO<B>;
  map<A, B>(fa: IOK<A>, f: (a: A) => B): IO<B>;
  unit(): IO<void>;
  raise<A>(e: Throwable): IO<A>;
  attempt<A>(fa: IOK<A>): IO<Either<Throwable, A>>;
  recoverWith<A>(fa: IOK<A>, f: (e: Throwable) => IOK<A>): IO<A>;
  recover<A>(fa: IOK<A>, f: (e: Throwable) => A): IO<A>;
  coflatMap<A, B>(fa: IOK<A>, ff: (a: IOK<A>) => B): IO<B>;
  coflatten<A>(fa: IOK<A>): IO<IO<A>>;
  map2: <A, B, Z>(fa: IOK<A>, fb: IOK<B>, f: (a: A, b: B) => Z) => IO<Z>;
  product: <A, B>(fa: IOK<A>, fb: IOK<B>) => IOK<[A, B]>;
  followedBy: <A, B>(fa: IOK<A>, fb: IOK<B>) => IO<B>;
  followedByL: <A, B>(fa: IOK<A>, fb: () => IOK<B>) => IO<B>;
  forEffect: <A, B>(fa: IOK<A>, fb: IOK<B>) => IO<A>;
  forEffectL: <A, B>(fa: IOK<A>, fb: () => IOK<B>) => IO<A>;

  static global: IOInstances;
}
