var dummy = function(){};
var alwtrue = function(){return true;};
var identy = function(x){return x;};
var alwfalse = function(){return false;};

function _a(obj){
    return Array.prototype.slice.call(obj);
}

function dir(obj){
    var p = [];
    for (k in obj)
        p.push(k);
    return p;
}

function len(obj){
	if(typeof(obj.length) != 'undefined'){
			return obj.length
	}else{
		 var k=0;
		 for(var i in obj) k++;
		 return k; 
	}
}

function filter(predicat, sequence){
	if (typeof(predicat)!="function"){
		var sequence = predicat;
        var predicat = is_true;
    }
	var _new_seq = new Array();
	for(var i = 0; i < len(sequence); i++){
		if(predicat(sequence[i])){
			_new_seq.push(sequence[i]);
		}
	}
	return _new_seq;
}

function _clue_args(){
	return arguments;
}

function max(seq){
	return Math.max.apply(Math, seq);
}

function min(seq){
	return Math.min.apply(Math, seq);
}


function call(func){
    var args = _a(arguments);
    if(len(args) > 1)
        return func(arguments.slice(1));
    else
        return func();
}

function nested(){
    /// compose(f, g, h)(x) -> h(g(f(x)))
    var funcs = _a(arguments);
    var wrap =  function(){
        var res = _a(arguments);
        for(var i=0; i < len(funcs); i++){
			res = funcs[i].apply(funcs[i], concat(res));
        }
        return res;
    }
    return wrap;
}
compose = nested;

function seqf(val){
    var args = _a(arguments);
    var funcs = tail(args);
    return map(caller(val), funcs);
}

function _generalize(){
    var args = _a(arguments);
    var genfunc = head(args);
    var funcs = tail(args);
    return function(){
        var largs = _a(arguments);
        var _r = map(caller(largs), funcs);
                  
        return genfunc.apply(null, _r);
    }
}

function generalize(){
    var a = _a(arguments);
    return _generalize.apply(null, concat(collect, a));
}


function map(func){
    var args = _a(arguments);
	if (typeof(func)!="function")
		func = _clue_args;
	var _ar = new Array();
	var _lengths = [0];
	for(var i = 1; i < len(args); i++){
		_lengths.push(len(args[i]));
    }
	var max_length = max(_lengths);
	var tmp = new Array();
	for(i = 0; i < max_length; i++){
		tmp = [];
		for(var k = 1; k < len(args); k++)
		    tmp.push(args[k][i])

		_ar.push(func.apply(null, tmp));
	}
	return _ar;
}

function zip(){
	var _ar = [];
    var args = _a(arguments);
    var count = len(args);
    var min_length = min(map(len, args));
    for(var i=0; i<min_length;i++){
        var tuple = [];
        for(var k=0; k<count; k++)
            tuple.push(args[k][i]);
        _ar.push(tuple);
    }

    return _ar;
}

function reduce(func, seq){
    var args = _a(arguments);
	if(args[2]!=undefined)
		var _initial = args[2];
	else
		var _initial = 0;
	if(typeof(func)=="function"){
		for(var i = 0; i < len(seq); i++)
			_initial = func(_initial, seq[i]);
	}
	return _initial
}

function iter(seq){
    if(seq.length === undefined){
        var t = [];
        for(key in seq)
            t.push(seq[key]);
        return t;
    } else {
        return seq;
    }
}


function foreach(func){
    var args = _a(arguments);
    args = map(iter, args);

	if (typeof(func)!="function")
		func = _clue_args;
	var _lengths = [0];

	for(var i = 1; i < len(args); i++){
		_lengths.push(len(args[i]));
    }
	var max_length = max(_lengths);

	var tmp = new Array();
	for(i = 0; i < max_length; i++){
		tmp = [];
		for(var k = 1; k < len(args); k++)
		    tmp.push(args[k][i])

		func.apply(null, tmp);
	}
}

function forindex(func){
    var args = _a(arguments);
    args = map(iter, args);

	if (typeof(func)!="function")
		func = _clue_args;
	var _lengths = [0];

	for(var i = 1; i < len(args); i++){
		_lengths.push(len(args[i]));
    }
	var max_length = max(_lengths);

	var tmp = new Array();
	for(i = 0; i < max_length; i++){
		tmp = [i];  // passing index onto function
		for(var k = 1; k < len(args); k++)
		    tmp.push(args[k][i])

		func.apply(null, tmp);
	}
}

function foritem(func, seq){
    for(key in seq)
        func(key, seq[key]);
}

function sum(seq){
	return reduce(plus, seq, _a(arguments)[1]);
}

function collect(){
    var args = _a(arguments);
    var seq = tail(args);
    var init = head(args);
	return reduce(genericplus(init), seq, init);
}

function genericplus(t){
    if(typeof(t)=="object" && t.length!=undefined)
        return concat;
    if(typeof(t)=="object")
        return ounite;
    return plus;
}

function plus(x,y){
    return x+y;
}

function minus(x,y){
    return x-y;
}

function pr(x,y){
    return x*y;
}

function product(seq){
	return reduce(pr, seq, _a(arguments)[1] ? _a(arguments)[1] : 1);
}

function range(){
    var args = _a(arguments);
	var start_v = 0;
	var stop_v  = args[0];
	var step = 1;
	if(len(args)>=2){
		start_v = args[0];
		stop_v = args[1];
	}
	if (len(args) >= 3)
		step = args[2];
	var _ar = [];
	for(var i = start_v; i < stop_v; i+=step)
		_ar.push(i);
	return _ar;
}

function repeat(val, n){
    var _ar = [];
    for(var i=0; i<n; i++)
        _ar.push(val);
    return _ar;
}

function clean(str, pat){
    return str.replace(pat, "", "g");
}

function clean_all(str, seq){
    return reduce(clean, seq, str);
}

String.prototype.join = function(seq){
   var res = "";
    for(var i=0; i < len(seq); i++){
      if(i)
        res += this;
      res += seq[i];
   }
   return res;
}

String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g, "");
};


Array.prototype.piece = function(){
    var args = _a(arguments);
	var start_v = args[0] ? args[0] : 0;
	var stop_v = args[1] ? args[1] : this.length;	

	var _ar = [];
	for(var i=Math.max(0, start_v); i < Math.min(this.length, stop_v); i++){
		_ar.push(this[i]);
	}
	return _ar;
}

function piece(obj){
    var args = _a(arguments);
    var start_v = args[1] ? args[1] : 0;
	var stop_v = args[2] ? args[2] : len(obj);	

    return obj.slice(Math.max(0, start_v), Math.min(len(obj), stop_v));
}

function index(seq, el){
    return seq.indexOf(el);
}

function remove(seq, el){
    return filter(compare(el), seq);
}

function remove_index(seq, ind){
    return remove(seq, seq[ind]);
}

function concat(){
    var args = _a(arguments);
    return [].concat.apply([], args);
}

function duplicate(seq){
    return concat(seq);
}

function reversed(seq){
    return duplicate(seq).reverse();
}

function head(seq){
    return seq[0];
}

function tail(seq){
    return seq.slice(1);
}

function init(seq){
    return seq.slice(0, len(seq)-1);
}

function last(seq){
    return seq[len(seq)-1];
}


function keys(dikt){
    var k = [];
    for (key in dikt)
        k.push(key)
    return k;
}

function values(dikt){
    var v = [];
    for (k in dikt)
        k.push(dikt[k]);
    return v;
}

function ounite(){
    var args = _a(arguments);
    var _o = {};
    for (var i=0; i < len(args); i++){
        for(var key in args[i])
            _o[key] = args[i][key];
    }
    return _o;
}

function substract_from(obj, s_key, d_key){
    var d_key = d_key || s_key;
    var r = {};
    r[d_key] = obj[s_key];
    return r;
}

function substract(s_key, d_key){
    return rcurry(substract_from, s_key, d_key);
}

function unite(){
    var args = _a(arguments);
    //unite given arrays in one
    var _ar = [];
    for(var i=0; i<len(args); i++){
        var x = args[i];
        for(var k=0; k < len(x); k++){
            _ar.push(x[k]);
        }
    }
    return _ar;
}

function oarray(obj){
    var t = [];
    for(it in obj)
        t.push(obj[it]);
    return t;
}

function insert(ar, val, ind){
    var ind = ind || 0;
    var tmp = ar.slice(0, ind);
    tmp.push(val);
    return tmp.concat(ar.slice(ind));
}


function curry(f){
    var args = tail(_a(arguments));
    return function(){
        var wargs = _a(arguments);
        return f.apply(f, concat(args, wargs));
    }
}

function rcurry(f){
    var args = tail(_a(arguments));
    return function(){
        var wargs = _a(arguments);
        return f.apply(f, concat(wargs, args));
    }
}


function wrap(func){
    var args = tail(_a(arguments));
    return function(){
        func.apply(this, args);
    }
}

function methodcaller(method){
	var call_object = function(obj){
        var args = _a(arguments);
		if(obj[method])
			return obj[method].apply(obj, args.piece(1));
		else
			return undefined;
	}
	return call_object;
}

function propertygetter(property){
	var call_object = function(obj){
		return obj[property];
	}
	return call_object;
}

function propertysetter(property, val){
	var call_object = function(obj){
	    obj[property] = val;
	}
	return call_object;
}


function proper_test(){
    var props = _a(arguments);
    return function(obj){
        return obj && all(seqf.apply(this,
                                    concat([obj],
                                           map(propertygetter, props)
                                          )
                                   )
                        );
    }
}


function lxor(l, r){
    return l==r ? false : true;
}

function lnot(v){
    return !v;
}

function lt(val){
    return function(x){
        return x < val;
    }
}

function gt(val){
    return function(x){
        return x > val;
    }
}

function all(sequence){
	for(var i=0; i < len(sequence); i++){
		if(!sequence[i])
			return false;
	}
	return true;
}

function any(sequence){
	for(var i=0; i < len(sequence); i++){
		if(sequence[i])
			return true;
	}
	return false;
}

function caller(x){
    return function(f){
        return f.apply(null, concat(x))
    }
}

function not(func){
    return function(x){
        return !func(x);
    }
}

function or(funcs){
    return function(x){
        return any(map(caller(x), funcs));    
    }
}

function and(funcs){
    return function(x){
        return all(map(caller(x), funcs));    
    }
}

function choice(predicat, true_func, false_func, value){
    if(predicat(value))
        return true_func(value);
    else
        return false_func(value);
}

function condition(predicat, true_func, false_func, check_var){
    return function(value){
        if(predicat(check_var))
            return true_func(value);
        else
            return false_func(value);
    }
}

function intest(seq, x){
    return index(seq, x) != -1;
}

function contain(seq){
    return curry(intest, seq); 
}

function include(test){
    return rcurry(intest, test);
}

function include_any(seq_test){
    return function(seq_x){
        return any(map(caller(seq_x), map(include, seq_test)))
    }
}

function compare(n){
    return curry(equal, n);
}

function compare_not(n){
    return curry(not_equal, n);
}

function equal(x, y){
	return x==y;
}

function not_equal(x, y){
	return x!=y;
}

function if_exist(x){
    if(typeof(x)!='undefined')
        return true;
    else
        return false;
}

function is_true(x){
    if(x)
        return true;
    else
        return false;
}

function is_false(x){
    if(x == false)
        return true;
    else
        return false;
}

function to_bool(x, dflt){
    if(x=="true" || x=="True" || x == true)
        return true;
    if(x=="false" || x=="False" || x == false)
        return false;

    return if_exist(dflt) ? dflt : x;
}

function get_bool_from_str(rec, key){
	if(rec && if_exist(rec[key]) && 
		(rec[key].toLowerCase()=="yes" || rec[key].toLowerCase()=="true"))
				return true;
			else
            	return false;
}

function get_key(param, key, dflt){
	return param!=null && param[key]!=undefined ? param[key] : (dflt!=undefined ? dflt : false);
}



function search(seq, pred){
    for(var i=0; i<len(seq); i++){
        if(pred(seq[i]))
            return seq[i];
    }
    return null;
}




function right_transform(transform, finalf, x){
    /// poor man's  S-combinator
    return finalf(x, transform(x));
} 

function left_transform(transform, finalf, x){
    return finalf(transform(x), x);
}

function prepare_for(transform, finalf, x){
    /// in js, need additional function, because lack of partial appliing fnctns
    return finalf(transform(x))(x);
}
function for_prepare(transform, finalf, x){
    return finalf(x)(transform(x));
}



function copy_dict(obj){
    res = {};
    for(key in obj)
        res[key] = obj[key];
    return res;
}


function partial_dict(obj, fields){
    res = {};
    for(key in obj)
        if(intest(fields, key))
            res[key] = obj[key]
    return res;
}

function extract(fields){
    return rcurry(partial_dict, fields);
}


function pprint(obj){
    var type = typeof(obj);
    switch(type){
    case "string":
        return "'"+ obj +"'";
        break;
    case "number":
        return "" + obj;
        break;
    case "boolean":
        if(obj)
            return "true";
        else
            return "false";
        break;
    case "undefined":
        return "null";
        break;
        
    default:
        if (obj===null)
            return "null";
        if (obj.length)
            return "[" + ",".join(map(pprint, obj))  + "]";
        var tmp = [];
        foritem(function(key, val){
            tmp.push(pprint(key) + ":" + pprint(val));
        }, obj);
        return "{" + ",".join(tmp) + "}";
                                  
        break;
    }
}


