/*!
 * 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 { Constructor } from "./kinds"
import { HK, Equiv } from "./kinds"
import { Functor, FunctorLaws } from "./functor"
import { Either } from "funfix-core"

export interface Apply<F> extends Functor<F> {
  ap<A, B>(fa: HK<F, A>, ff: HK<F, (a: A) => B>): HK<F, B>;
  map2<A, B, Z>(fa: HK<F, A>, fb: HK<F, B>, f: (a: A, b: B) => Z): HK<F, Z>;
  product<A, B>(fa: HK<F, A>, fb: HK<F, B>): HK<F, [A, B]>;

  // Implements TypeClass<F>
  static +_funTypeId: string;
  static +_funSupertypeIds: string[];
  static +_funErasure: Apply<any>;
}

export interface ApplyLaws<F> extends FunctorLaws<F> {
  +F: Apply<F>;

  applyComposition<A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>): Equiv<HK<F, C>>;
  applyProductConsistency<A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>): Equiv<HK<F, B>>;
  applyMap2Consistency<A, B>(fa: HK<F, A>, f: HK<F, (a: A) => B>): Equiv<HK<F, B>>;
}

declare export function applyOf<F>(c: Constructor<F>): Apply<F>;
declare export function applyLawsOf<F>(instance: Apply<F>): ApplyLaws<F>;

export interface Applicative<F> extends Apply<F> {
  pure<A>(a: A): HK<F, A>;
  unit(): HK<F, void>;

  // Implements TypeClass<F>
  static +_funTypeId: string;
  static +_funSupertypeIds: string[];
  static +_funErasure: Applicative<any>;
}

export interface ApplicativeLaws<F> extends ApplyLaws<F> {
  +F: Applicative<F>;

  applicativeIdentity<A>(fa: HK<F, A>): Equiv<HK<F, A>>;
  applicativeHomomorphism<A, B>(a: A, f: (a: A) => B): Equiv<HK<F, B>>;
  applicativeInterchange<A, B>(a: A, ff: HK<F, (a: A) => B>): Equiv<HK<F, B>>;
  applicativeMap<A, B>(fa: HK<F, A>, f: (a: A) => B): Equiv<HK<F, B>>;
  applicativeComposition<A, B, C>(fa: HK<F, A>, fab: HK<F, (a: A) => B>, fbc: HK<F, (b: B) => C>): Equiv<HK<F, C>>;
  applicativeUnit<A>(a: A): Equiv<HK<F, A>>;
}

declare export function applicativeOf<F>(c: Constructor<F>): Applicative<F>;
declare export function applicativeLawsOf<F>(instance: Applicative<F>): ApplicativeLaws<F>;

export interface ApplicativeError<F, E> extends Applicative<F> {
  raise<A>(e: E): HK<F, A>;
  recoverWith<A>(fa: HK<F, A>, f: (e: E) => HK<F, A>): HK<F, A>;
  recover<A>(fa: HK<F, A>, f: (e: E) => A): HK<F, A>;
  attempt<A>(fa: HK<F, A>): HK<F, Either<E, A>>;

  // Implements TypeClass<F>
  static +_funTypeId: string;
  static +_funSupertypeIds: string[];
  static +_funErasure: ApplicativeError<any, any>;
}

export interface ApplicativeErrorLaws<F, E> extends ApplicativeLaws<F> {
  +F: ApplicativeError<F, E>;

  applicativeErrorRecoverWith<A>(e: E, f: (e: E) => HK<F, A>): Equiv<HK<F, A>>;
  applicativeErrorRecover<A>(e: E, f: (e: E) => A): Equiv<HK<F, A>>;
  recoverWithPure<A>(a: A, f: (e: E) => HK<F, A>): Equiv<HK<F, A>>;
  recoverPure<A>(a: A, f: (e: E) => A): Equiv<HK<F, A>>;
  raiseErrorAttempt(e: E): Equiv<HK<F, Either<E, void>>>;
  pureAttempt<A>(a: A): Equiv<HK<F, Either<E, A>>>;
}

declare export function applicativeErrorOf<F, E>(c: Constructor<F>): ApplicativeError<F, E>;
declare export function applicativeErrorLawsOf<F, E>(instance: ApplicativeError<F, E>): ApplicativeErrorLaws<F, E>;
