package haskell.prelude;

import haskell.lang.Data;
import haskell.lang.Exp;
import haskell.prelude.text.Show;

import java.util.ArrayList;

/**
 * data [a] = [] | a : [a]
 *     deriving (Eq, Ord)
 */
public class List<A>
        extends AbstractEq<List<A>>
        implements Data<List<A>>,
                Show<List<A>> {

    final A head;
    final List<A> tail;

    public List() {
        this("[]", null, null);
    }

    public List(final A head, final List<A> tail) {
        this(":", head, tail);
    }

    protected List(
            final java.lang.String name,
            final A head,
            final List<A> tail) {
        super(name);
        this.head = head;
        this.tail = tail;
    }

    protected List(final Exp<List<A>> expression) {
        super(expression);
        this.head = null;
        this.tail = null;
    }

    public static <A> List<A> valueOf(final A... array) {
        List<A> xs = Nil();
        for (int i = array.length - 1; 0 <= i; --i) {
            xs = Cons(array[i], xs);
        }
        return xs;
    }

    public static <A> List<A> valueOf(final java.util.List<A> list) {
        List<A> xs = Nil();
        for (int i = list.size() - 1; 0 <= i; --i) {
            xs = Cons(list.get(i), xs);
        }
        return xs;
    }

    public java.util.List<A> toList() {
        final java.util.List<A> list = new ArrayList<A>();
        List<A> node = eval();
        while (node.head != null) {
            list.add(node.head);
            node = node.tail.eval();
        }
        return list;
    }

    // ---- java.lang.Object

    @Override
    @SuppressWarnings("unchecked")
    protected java.lang.String $description() {
        if (_head_() instanceof Char) {
            return toString((List<Char>) this);
        } else {
            return toList().toString();
        }
    }

    protected static java.lang.String toString(final List<Char> xs) {
        final StringBuilder sb = new StringBuilder();
        //sb.append('"');
        List<Char> node = xs.eval();
        while (node.head != null) {
            sb.append(node.head);
            if (node.tail.$expression() != null) {
                sb.append("...");
                break;
            }
            node = node.tail.eval();
        }
        //sb.append('"');
        return sb.toString();
    }

    // ---- data [a]

    /**
     * []
     */
    public static <A> List<A> Nil() {
        return new List<A>();
    }

    public boolean isNil() {
        return (_head_() == null);
    }

    /**
     * a : [a]
     */
    public static <A> Operator<A, List<A>, List<A>> Cons() {
        return new Operator<A, List<A>, List<A>>(
                ":", List.class, List.class, "Cons");
    }

    @SuppressWarnings("unchecked")
    public static <A> List<A> Cons(final A head, final List<A> tail) {
        if (head instanceof Data) {
            return ((Data<A>) head)._cons_(tail);
        } else {
            return new List<A>(head, tail);
        }
    }

    public boolean isCons() {
        return (_head_() != null);
    }

    public A _head_() {
        return eval().head;
    }

    public List<A> _tail_() {
        return eval().tail;
    }

    // ---- instance Eq []

    // ---- instance Ord []

    // ---- instance Functor []

    // ---- instance Monad []

    // ---- instance Show []

    @SuppressWarnings("unchecked")
    public String _show_() {
        if (_head_() instanceof Char) {
            final Char quot = Char.valueOf('"');
            return String.Cons(quot,
                    append((List<Char>) this, Cons(quot, String.Nil())));
        }
        return String.valueOf(toString());
    }

    // ---- module PreludeList

    /**
     * (++) :: [a] -> [a] -> [a]
     * []       ++ ys = ys
     * (x : xs) ++ ys = x : (xs ++ ys)
     */
    public static <A> List<A> append(final List<A> xs, final List<A> ys) {
        if (xs.isNil()) {
            return ys;
        } else {
            return Cons(xs._head_(), append(xs._tail_(), ys));
        }
    }

    /**
     * map :: (a -> b) -> [a] -> [b]
     * map f []       = []
     * map f (x : xs) = f x : map f xs
     */
    public static <A, B>
            Function2<Function<A, B>, List<A>, List<B>> map() {
        return new Function2<Function<A, B>, List<A>, List<B>>("map") {
            @Override
            public List<B> apply(final Function<A, B> f, final List<A> xs) {
                return map(f, xs);
            }
        };
    }

    public static <A, B> List<B> map(
            final Function<A, B> f, final List<A> xs) {
        if (xs.isNil()) {
            return Nil();
        } else {
            return Cons(f.apply(xs._head_()), map(f, xs._tail_()));
        }
    }

    /**
     * filter :: (a -> Bool) -> [a] -> [a]
     */

    /**
     * concat :: [[a]] -> [a]
     */

    /**
     * concatMap :: (a -> [b]) -> [a] -> [b]
     */

    /**
     * head :: [a] -> a
     * head (x : _) = x
     * head []      = error "Prelude.head: empty list"
     */
    public static <A> Function<List<A>, A> head() {
        return new Function<List<A>, A>("head") {
            @Override
            public A apply(final List<A> xs) {
                return head(xs);
            }
        };
    }

    public static <A> A head(final List<A> xs) {
        return xs._head_();
    }

    /**
     * tail :: [a] -> [a]
     */
    public static <A> List<A> tail(final List<A> xs) {
        return xs._tail_();
    }

    /**
     * last :: [a] -> a
     */
    public static <A> A last(final List<A> xs) {
        if (xs.isNil()) {
            return Function.error("Prelude.last: empty list");
        } else if (xs._tail_().isNil()) {
            return xs._head_();
        } else {
            return last(xs._tail_());
        }
    }

    /**
     * init :: [a] -> [a]
     */

    /**
     * null :: [a] -> Bool
     */
    public static <A> Bool null_(final List<A> xs) {
        if (xs.isNil()) {
            return Bool.True();
        } else {
            return Bool.False();
        }
    }

    /**
     * length :: [a] -> Int
     */
    public static <A> Int length(final List<A> xs) {
        if (xs.isNil()) {
            return Int.Zero();
        } else {
            return Int.One()._plus_(length(xs._tail_()));
        }
    }

    /**
     * iterate :: (a -> a) -> a -> [a]
     */
    public static <A> List<A> iterate(final Function<A, A> f, final A x) {
        return Cons(x, iterate(f, f.apply(x)));
    }

    /**
     * take :: Int -> [a] -> [a]
     */
    public static <A> List<A> take(final Int n, final List<A> xs) {
        if (n._le_(Int.Zero()).isTrue()) {
            return Nil();
        }
        if (xs.isNil()) {
            return Nil();
        } else {
            return Cons(xs._head_(), take(n._minus_(Int.One()), xs._tail_()));
        }
    }

    /**
     * drop :: Int -> [a] -> [a]
     */
    public static <A> List<A> drop(final Int n, final List<A> xs) {
        if (n._le_(Int.Zero()).isTrue()) {
            return tail(xs);
        } else if (xs.isNil()) {
            return Nil();
        } else {
            return drop(n._minus_(Int.One()), xs._tail_());
        }
    }

    /**
     * span :: (a -> Bool) -> [a] -> ([a], [a])
     */
    public static <A> Tuple<List<A>, List<A>> span(
            final Function<A, Bool> p, final List<A> xs) {
        if (xs.isNil()) {
            return Tuple.Tuple_(List.<A>Nil(), List.<A>Nil());
        } else if (p.apply(xs._head_()).isTrue()) {
            final Tuple<List<A>, List<A>> t = span(p, xs._tail_());
            return Tuple.Tuple_(Cons(xs._head_(), Tuple.fst(t)), Tuple.snd(t));
        } else {
            return Tuple.Tuple_(List.<A>Nil(), xs);
        }
    }

    /**
     * break p = span (not . p)
     */
    public static <A> Tuple<List<A>, List<A>> break_(
            final Function<A, Bool> p, final List<A> xs) {
        return span(Bool.not()._dot_(p), xs);
    }

}
