/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.
 */

package org.apache.tinkerpop.gremlin.util.function;

import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngine;

import java.io.Serializable;
import java.util.Comparator;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

/**
 * Provides a way to serialize string lambdas as scripts which can be evaluated by a {@link GremlinScriptEngine}.
 *
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 * @author Stephen Mallette (http://stephen.genoprime.com)
 */
public interface Lambda extends Serializable {

    public String getLambdaScript();

    public String getLambdaLanguage();

    public int getLambdaArguments();

    public abstract static class AbstractLambda implements Lambda {
        private final String lambdaSource;
        private final String lambdaLanguage;
        private final int lambdaArguments;

        private AbstractLambda(final String lambdaSource, final String lambdaLanguage, final int lambdaArguments) {
            this.lambdaSource = lambdaSource;
            this.lambdaLanguage = lambdaLanguage;
            this.lambdaArguments = lambdaArguments;
        }

        @Override
        public String getLambdaScript() {
            return this.lambdaSource;
        }

        @Override
        public String getLambdaLanguage() {
            return this.lambdaLanguage;
        }

        @Override
        public int getLambdaArguments() {
            return this.lambdaArguments;
        }

        @Override
        public String toString() {
            return "lambda[" + this.lambdaSource + "]";
        }

        @Override
        public int hashCode() {
            return this.lambdaSource.hashCode() + this.lambdaLanguage.hashCode() + this.lambdaArguments;
        }

        @Override
        public boolean equals(final Object object) {
            return object instanceof Lambda &&
                    ((Lambda) object).getLambdaArguments() == this.lambdaArguments &&
                    ((Lambda) object).getLambdaScript().equals(this.lambdaSource) &&
                    ((Lambda) object).getLambdaLanguage().equals(this.lambdaLanguage);
        }
    }

    public static class UnknownArgLambda extends AbstractLambda {

        public UnknownArgLambda(final String lambdaSource, final String lambdaLanguage, final int lambdaArguments) {
            super(lambdaSource, lambdaLanguage, lambdaArguments);
        }
    }

    public static class ZeroArgLambda<A> extends AbstractLambda implements Supplier<A> {

        public ZeroArgLambda(final String lambdaSource, final String lambdaLanguage) {
            super(lambdaSource, lambdaLanguage, 0);
        }

        @Override
        public A get() {
            return null;
        }

    }

    public static class UnaryLambda<A> extends OneArgLambda<A, A> implements UnaryOperator<A> {
        public UnaryLambda(final String lambdaSource, final String lambdaLanguage) {
            super(lambdaSource, lambdaLanguage);
        }
    }

    public static class OneArgLambda<A, B> extends AbstractLambda implements Function<A, B>, Predicate<A>, Consumer<A> {

        public OneArgLambda(final String lambdaSource, final String lambdaLanguage) {
            super(lambdaSource, lambdaLanguage, 1);
        }

        @Override
        public B apply(final A a) {
            return null;
        }

        @Override
        public boolean test(final A a) {
            return false;
        }

        @Override
        public void accept(final A a) {

        }
    }

    public static class BinaryLambda<A> extends TwoArgLambda<A, A, A> implements BinaryOperator<A> {
        public BinaryLambda(final String lambdaSource, final String lambdaLanguage) {
            super(lambdaSource, lambdaLanguage);
        }
    }

    public static class TwoArgLambda<A, B, C> extends AbstractLambda implements BiFunction<A, B, C>, Comparator<A> {

        public TwoArgLambda(final String lambdaSource, final String lambdaLanguage) {
            super(lambdaSource, lambdaLanguage, 2);
        }

        @Override
        public C apply(final A a, final B b) {
            return null;
        }


        @Override
        public int compare(final A first, final A second) {
            return 0;
        }
    }

    ///

    public static String DEFAULT_LAMBDA_LANGUAGE = "gremlin-groovy";

    public static <A> UnaryOperator<A> unaryOperator(final String lambdaSource, final String lambdaLanguage) {
        return new UnaryLambda<>(lambdaSource, lambdaLanguage);
    }

    public static <A, B> Function<A, B> function(final String lambdaSource, final String lambdaLanguage) {
        return new OneArgLambda<>(lambdaSource, lambdaLanguage);
    }

    public static <A> Predicate<A> predicate(final String lambdaSource, final String lambdaLanguage) {
        return new OneArgLambda<>(lambdaSource, lambdaLanguage);
    }

    public static <A> Consumer<A> consumer(final String lambdaSource, final String lambdaLanguage) {
        return new OneArgLambda<>(lambdaSource, lambdaLanguage);
    }

    public static <A> Supplier<A> supplier(final String lambdaSource, final String lambdaLanguage) {
        return new ZeroArgLambda<>(lambdaSource, lambdaLanguage);
    }

    public static <A> Comparator<A> comparator(final String lambdaSource, final String lambdaLanguage) {
        return new TwoArgLambda<>(lambdaSource, lambdaLanguage);
    }

    public static <A, B, C> BiFunction<A, B, C> biFunction(final String lambdaSource, final String lambdaLanguage) {
        return new TwoArgLambda<>(lambdaSource, lambdaLanguage);
    }

    //

    public static <A> UnaryOperator<A> unaryOperator(final String lambdaSource) {
        return new UnaryLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A, B> Function<A, B> function(final String lambdaSource) {
        return new OneArgLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A> Predicate<A> predicate(final String lambdaSource) {
        return new OneArgLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A> Consumer<A> consumer(final String lambdaSource) {
        return new OneArgLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A> Supplier<A> supplier(final String lambdaSource) {
        return new ZeroArgLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A> Comparator<A> comparator(final String lambdaSource) {
        return new TwoArgLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A, B, C> BiFunction<A, B, C> biFunction(final String lambdaSource) {
        return new TwoArgLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }

    public static <A> BinaryOperator<A> binaryOperator(final String lambdaSource) {
        return new BinaryLambda<>(lambdaSource, DEFAULT_LAMBDA_LANGUAGE);
    }
}
