/**
 *   Copyright (c) Rich Hickey. All rights reserved.
 *   The use and distribution terms for this software are covered by the
 *   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
 *   which can be found in the file epl-v10.html at the root of this distribution.
 *   By using this software in any fashion, you are agreeing to be bound by
 * 	 the terms of this license.
 *   You must not remove this notice, or any other, from this software.
 **/

package clojure.lang;

/* Alex Miller, Dec 5, 2014 */

public class Repeat extends ASeq implements IReduce, IDrop {

private static final long serialVersionUID = -5140377547192202551L;

private static final long INFINITE = -1;

private final long count;  // always INFINITE or >0
private final Object val;
private volatile ISeq _next;  // cached

private Repeat(long count, Object val){
    this.count = count;
    this.val = val;
}

private Repeat(IPersistentMap meta, long count, Object val){
    super(meta);
    this.count = count;
    this.val = val;
}

public static Repeat create(Object val){
    return new Repeat(INFINITE, val);
}

public static ISeq create(long count, Object val){
    if(count <= 0)
        return PersistentList.EMPTY;
    return new Repeat(count, val);
}

public Object first(){
    return val;
}

public ISeq next() {
    if(_next == null) {
        if(count > 1)
            _next = new Repeat(count - 1, val);
        else if(count == INFINITE)
            _next = this;
    }
    return _next;
}

public Repeat withMeta(IPersistentMap meta){
    if(meta() == meta)
        return this;
    return new Repeat(meta, count, val);
}

public Object reduce(IFn f){
    Object ret = val;
    if(count == INFINITE) {
        while(true){
            ret = f.invoke(ret, val);
            if(RT.isReduced(ret))
                return ((IDeref)ret).deref();
        }
    } else {
        for(long i=1; i<count; i++) {
            ret = f.invoke(ret, val);
            if(RT.isReduced(ret))
                return ((IDeref)ret).deref();
        }
        return ret;
    }
}

public Object reduce(IFn f, Object start){
    Object ret = start;
    if(count == INFINITE){
        while(true){
            ret = f.invoke(ret, val);
            if(RT.isReduced(ret))
                return ((IDeref)ret).deref();
        }
    } else {
        for(long i=0; i<count; i++){
            ret = f.invoke(ret, val);
            if(RT.isReduced(ret))
                return ((IDeref)ret).deref();
        }
        return ret;
    }
}

public Sequential drop(int n) {
    if(count == INFINITE) {
        return this;
    } else {
        long droppedCount = count-n;
        if(droppedCount > 0) {
            return new Repeat(droppedCount, val);
        } else {
            return null;
        }
    }
}

public int hashCode(){
    if(count <= 0)
        throw new UnsupportedOperationException();
    else
        return super.hashCode();
}

public int hasheq(){
    if(count <= 0)
        throw new UnsupportedOperationException();
    else
        return super.hasheq();
}

}
