
function clone(myObj)
{
    if(myObj == null || typeof(myObj) != 'object'){
        return myObj;
    }

    if(myObj.constructor == Array) {

        var temp = [];

        for(var i = 0; i < myObj.length; i++) {

            temp.push(clone(myObj[i]));

        }

        return temp;

    }


    var myNewObj = {};


    for(var x in myObj){
        myNewObj[x] = clone(myObj[x]);
    }

    return myNewObj;
};

function areEqual(obj1, obj2)
{
    if(obj1 == null && obj2 == null){
        return true;
    }

    if(obj1 == null || obj2 == null){
        return false;
    }

    if(typeof(obj1) != typeof(obj2)){
        return false;
    }

    if(typeof(obj1) != 'object'){
        return (obj1 == obj2);
    }

    if(obj1.constructor == Array && obj2.constructor == Array){
        if(obj1.length != obj2.length){
            return false;
        }

        var i = obj1.length - 1;
        for( ; i >= 0; i-- ){
            if(!areEqual(obj1[i], obj2[i])){
                break;
            }
        }

        if(i < 0){
            return true;
        }
        else{
            return false;
        }
    }

    var ret = true;
    for(var x in obj1){
        if(!areEqual(obj1[x], obj2[x])){
            ret = false;
            break;
        }
    }

    return ret;
};

function AdvancedMatrice(matrix, type){

    this.matrice = [];
    this.columns = [];

    this.constructFromRow = function (row){
        this.matrice[0] = {};
        this.matrice[0].i = clone(row.i);
        this.matrice[0].n = clone(row.i);
        this.matrice[0].v = [];
        for(var k = 0; k < row.v.length; k++){
            this.columns[k] = {};
            this.columns[k].i = clone(row.v[k].i);
            this.columns[k].n = clone(row.v[k].n);
            this.matrice[0].v[k] = clone(row.v[k].v);
        }
    };

    this.constructFromColumn = function (column){
        this.columns[0] = {};
        this.columns[0].i = clone(column.i);
        this.columns[0].n = clone(column.n);

        for(var k = 0; k < column.v.length; k++){
            this.matrice[k].i = clone(column.v[k].i);
            this.matrice[k].n = clone(column.v[k].n);
            this.matrice[k].v = [];
            this.matrice[k].v[0] = clone(column.v[k].v);
        }

    };


    if(matrix != null){
        if(typeof(matrix) == 'string')
            matrix = json_parse(matrix, function (key, value){ return value; });

        if(type == "r" || type == "R" ){
            this.constructFromRow(matrix);
        }if(type == "c" || type == "C" ){
            this.constructFromColumn(matrix);
        }
    }

    this.getAbsoluteRowIndex = function(index){
        if(this.matrice.length == 0)
            return false;

        if(index == null)
            return 0;

        var range = this.matrice.length;

        if(index >= range || index <= -range)
            return false;

        index = (index + range) % range;

        return index;
    };

    this.getAbsoluteColumnIndex = function(index){
        if(this.columns.length == 0)
            return false;

        if(index == null)
            return 0;

        var range = this.columns.length;

        if(index >= range || index <= -range)
            return false;

        index = (index + range) % range;

        return index;
    };

    this.getAbsoluteRowBorderIndex = function(index){
        if(index == null || this.matrice.length == 0)
            return 0;

        var range = this.matrice.length + 1;

        if(index >= range || index <= -range)
            return false;

        index = (index + range) % range;

        return index;
    };

    this.getAbsoluteColumnBorderIndex = function(index){
        if(index == null || this.columns.length == 0)
            return 0;

        var range = this.columns.length + 1;

        if(index >= range || index <= -range)
            return false;

        index = (index + range) % range;

        return index;
    };

    this.getRowIndexById = function(id){
        for(var j = this.matrice.length - 1; j >= 0; j--)
            if(areEqual(this.matrice[j].i, id))
                return j;
        return false;
    };

    this.getColumnIndexById = function(id){
        for(var k = this.columns.length - 1; k >= 0; k--)
            if(areEqual(this.columns[k].i, id))
                return k;
        return false;
    };

    this.toString = function(){
        var ret = "\\";
        var n = this.matrice.length;
        var m = this.columns.length;
        var j, k;

        for(k = 0; k < m; k++)
            ret += "\t" + this.columns[k].i + " - " + this.columns[k].n;

        for(j = 0; j < n; j++){
            ret += "\n" + this.matrice[j].i + " - " + this.matrice[j].n;
            for(k = 0; k < m; k++)
                ret += "\t" + this.matrice[j].v[k];
        }

        return ret;
    };
};


AdvancedMatrice.prototype.getRowIds = function(){
    var ret = [];
    var m = this.matrice;
    for(var j = 0; j < m.length; j++)
        ret[j] = clone(m[j].i);
    return ret;
};

AdvancedMatrice.prototype.getColumnIds = function(){
    var ret = [];
    var c = this.columns;
    for(var k = 0; k < c.length; k++)
        ret[k] = clone(c[k].i);
    return ret;
};

AdvancedMatrice.prototype.getTranspose = function(){
    var ret = new AdvancedMatrice();

    var length = this.columns.length;
    for(var k = 0; k < length; k++)
        ret.addRow(this.getColumn(k), -1);

    return ret;
};

AdvancedMatrice.prototype.transpose = function(){
    var temp = this.getTranspose();
    this.columns = clone(temp.columns);
    this.matrice = clone(temp.matrice);
};

AdvancedMatrice.prototype.addRow = function(row, index){
    var j, k;

    index = this.getAbsoluteRowBorderIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    if(typeof(row) == 'string')
        row = json_parse(row, function (key, value){ return value; });

    if(typeof(row) != 'object')
        return false;

    if(row.i == null || row.v == null || row.v.constructor != Array || row.v.length == 0)
        return false;

    for(j = this.matrice.length - 1; j >= 0; j--)
        if(this.matrice[j].i == row.i)
            break;

    if(j >= 0)
        return false;

    var newRow = {};
    newRow.i = clone(row.i);
    newRow.n = clone(row.n);
    newRow.v = [];

    for(j = 0; j < row.v.length - 1; j++)
        row.v[j].next = row.v[j + 1];
    row.v[j].next = null;

    var list = {};
    list.next = row.v[0];
    var temp = {};

    for(j = 0; j < this.columns.length; j++){
        temp = list;
        while(temp.next != null){
//            if(temp.next.i == this.columns[j].i)
//                break;
            if(areEqual(temp.next.i, this.columns[j].i))
                break;
            temp = temp.next;
        }

        if(temp.next == null){
            newRow.v[j] = null;
        } else{
            newRow.v[j] = clone(temp.next.v);
            temp.next = temp.next.next;
        }
    }

    list = list.next;
    while(list != null){
        this.columns[j] = {};
        this.columns[j].i = clone(list.i);
        this.columns[j].n = clone(list.n);
        newRow.v[j] = clone(list.v);
        for(k = 0; k < this.matrice.length; k++)
            this.matrice[k].v[j] = null;
        list = list.next;
        j++;
    }



    for(j = this.matrice.length; j > index; j--)
        this.matrice[j] = this.matrice[j-1];

    this.matrice[index] = newRow;

    return true;

};

AdvancedMatrice.prototype.addColumn = function(column, index){
    var j, k;

    index = this.getAbsoluteColumnBorderIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    if(typeof(column) == 'string')
        column = json_parse(column, function (key, value){ return value; });

    if(typeof(column) != 'object')
        return false;

    if(column.i == null || column.v == null || column.v.constructor != Array || column.v.length == 0)
        return false;

    for(k = this.columns.length - 1; k >= 0; k--)
        if(this.columns[k].i == column.i)
            break;

    if(k >= 0)
        return false;

    for(j = 0; j < column.v.length - 1; j++)
        column.v[j].next = column.v[j + 1];
    column.v[j].next = null;

    var list = {};
    list.next = column.v[0];
    var temp = {};
    var ids = this.getRowIds();

    for(j = 0; j < ids.length; j++){
        for(k = this.columns.length; k >= index; k--)
            this.matrice[j].v[k] = this.matrice[j].v[k-1];

        temp = list;
        while(temp.next != null){
//            if(temp.next.i == ids[j])
//                break;
            if(areEqual(temp.next.i, ids[j]))
                break;
            temp = temp.next;
        }

        if(temp.next == null){
            this.matrice[j].v[index] = null;
        } else{
            this.matrice[j].v[index] = clone(temp.next.v);
            temp.next = temp.next.next;
        }
    }

    for(k = this.columns.length; k > index; k--)
        this.columns[k] = this.columns[k-1];

    this.columns[index]   = {};
    this.columns[index].i = clone(column.i);
    this.columns[index].n = clone(column.n);

    list = list.next;
    while(list != null){
        this.matrice[j] = {};
        var row = this.matrice[j++];
        row.i = clone(list.i);
        row.n = clone(list.n);
        row.v = [];
        for(k = this.columns.length - 1; k >= 0; k--)
            row.v[k] = null;

        row.v[index] = clone(list.v);
        list = list.next;
    }

    return true;

};

AdvancedMatrice.prototype.getRow = function(index){

    index = this.getAbsoluteRowIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    var ret = {};

    ret.i = clone(this.matrice[index].i);
    if(this.matrice[index].n != null)
        ret.n = clone(this.matrice[index].n);
    ret.v = [];

    for(var k = 0; k < this.columns.length; k++){
        ret.v[k]   = {};
        ret.v[k].i = clone(this.columns[k].i);
        if(this.columns[k].n != null)
            ret.v[k].n = clone(this.columns[k].n);
        ret.v[k].v = clone(this.matrice[index].v[k]);
    }

    return ret;
};

AdvancedMatrice.prototype.getRowById = function(id){
    var index = this.getRowIndexById(id);

    if(typeof(index) != 'boolean')
        return this.getRow(index);

    return false;
};

AdvancedMatrice.prototype.getColumn = function(index){

    index = this.getAbsoluteColumnIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    var ret = {};

    ret.i = clone(this.columns[index].i);
    if(this.columns[index].n != null)
        ret.n = clone(this.columns[index].n);
    ret.v = [];

    for(var j = 0; j < this.matrice.length; j++){
        ret.v[j]   = {};
        ret.v[j].i = clone(this.matrice[j].i);
        if(this.matrice[j].n != null)
            ret.v[j].n = clone(this.matrice[j].n);
        ret.v[j].v = clone(this.matrice[j].v[index]);
    }

    return ret;
};

AdvancedMatrice.prototype.getColumnById = function(id){
    var index = this.getColumnIndexById(id);

    if(typeof(index) != 'boolean')
        return this.getColumn(index);

    return false;
};

AdvancedMatrice.prototype.dropRow = function(index){

    index = this.getAbsoluteRowIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    var j, k;

    for(j = index; j < this.matrice.length; j++)
        this.matrice[j] = this.matrice[j + 1];

    this.matrice.length--;

    for(k = this.columns.length - 1; k >= 0; k--){
        for(j = this.matrice.length - 1; j >= 0; j--)
            if(this.matrice[j].v[k] != null)
                break;
        if(j < 0)
            if(!this.dropColumn(k))
                return false;
    }

    return true;
};

AdvancedMatrice.prototype.dropColumn = function(index){

    index = this.getAbsoluteColumnIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    var j, k;

    for(j = this.matrice.length - 1; j >= 0; j--){
        for(k = index; k < this.columns.length; k++)
            this.matrice[j].v[k] = this.matrice[j].v[k + 1];
        this.matrice[j].v.length--;
    }

    for(k = index; k < this.columns.length; k++)
        this.columns[k] = this.columns[k + 1];
    this.columns.length--;

    for(j = this.matrice.length - 1; j >= 0; j--){
        for(k = this.columns.length - 1; k >= 0; k--)
            if(this.matrice[j].v[k] != null)
                break;
        if(k < 0)
            if(!this.dropRow(j))
                return false;
    }

    return true;
};

AdvancedMatrice.prototype.dropRowById = function(id){
    var index = this.getRowIndexById(id);

    if(typeof(index) != 'boolean')
        return this.dropRow(index);

    return false;
};

AdvancedMatrice.prototype.dropColumnById = function(id){
    var index = this.getColumnIndexById(id);

    if(typeof(index) != 'boolean')
        return this.dropColumn(index);

    return false;
};

function getSortReference(v){
    if(v == null)
        return Number.MIN_VALUE;

    if(typeof(v) != 'object')
        return v;

    if(v.s != null)
        return v.s;

    return getSortReference(v.v);
};

function advancedRowCompareFunction(row1, row2){
    var a = getSortReference(row1.v[row1.index]);
    var b = getSortReference(row2.v[row2.index]);

    if(typeof(a) == 'object')
        return a.compareTo(b);

    if(a > b)
        return 1;
    if(a < b)
        return -1;
    return 0;
};

function advancedRowCompareFunctionDesc(row1, row2){
    var a = getSortReference(row1.v[row1.index]);
    var b = getSortReference(row2.v[row2.index]);

    if(typeof(a) == 'object')
        return b.compareTo(a);

    if(a > b)
        return -1;
    if(a < b)
        return 1;
    return 0;
};

AdvancedMatrice.prototype.sortRowsByColumn = function(index){

    index = this.getAbsoluteColumnIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    for(var j = this.matrice.length - 1; j >= 0; j--)
        this.matrice[j].index = index;

    this.matrice.sort(advancedRowCompareFunction);

    return true;

};

AdvancedMatrice.prototype.sortRowsByColumnId = function(id){
    var index = this.getColumnIndexById(id);

    if(typeof(index) == 'boolean')
        return false;

    return this.sortRowsByColumn(index);
};

AdvancedMatrice.prototype.sortRowsByColumnDesc = function(index){

    index = this.getAbsoluteColumnIndex(index);
    if(typeof(index) == 'boolean')
        return false;

    for(var j = this.matrice.length - 1; j >= 0; j--)
        this.matrice[j].index = index;

    this.matrice.sort(advancedRowCompareFunctionDesc);
    return true;
};

AdvancedMatrice.prototype.sortRowsByColumnIdDesc = function(id){
    var index = this.getColumnIndexById(id);

    if(typeof(index) != 'boolean')
        return false;

    return this.sortRowsByColumnDesc(index);
};



AdvancedMatrice.prototype.sortColumnsByRow = function(index){
    this.transpose();
    var ret = this.sortRowsByColumn(index);
    this.transpose();
    return ret;
};

AdvancedMatrice.prototype.sortColumnsByRowId = function(id){
    this.transpose();
    var ret = this.sortRowsByColumnId(id);
    this.transpose();
    return ret;
};


AdvancedMatrice.prototype.sortColumnsByRowDesc = function(index){
    this.transpose();
    var ret = this.sortRowsByColumnDesc(index);
    this.transpose();
    return ret;
};

AdvancedMatrice.prototype.sortColumnsByRowIdDesc = function(id){
    this.transpose();
    var ret = this.sortRowsByColumnIdDesc(id);
    this.transpose();
    return ret;
};

AdvancedMatrice.prototype.getColumnDimension = function(){
    return this.columns.length;
};

AdvancedMatrice.prototype.getRowDimension = function(){
    return this.matrice.length;
};

AdvancedMatrice.prototype.hasRowId = function(id){
    var ids = this.getRowIds();
    for(var j = ids.length - 1; j >= 0; j--)
        if(ids[j] == id)
            break;

    if(j >= 0)
        return true;
    else{
        return false;
    }
};

AdvancedMatrice.prototype.hasColumnId = function(id){
    var ids = this.getColumnIds();
    for(var k = ids.length - 1; k >= 0; k--)
        if(ids[k] == id)
            break;

    if(k >= 0)
        return true;
    else{
        return false;
    }
};

AdvancedMatrice.prototype.moveRowTo = function(r_index, to_index){

    r_index = this.getAbsoluteRowIndex(r_index);
    if(typeof(index) == 'boolean')
        return false;

    to_index = this.getAbsoluteRowIndex(to_index);
    if(typeof(index) == 'boolean')
        return false;


    var j, diff = (r_index > to_index ? -1 : 1);
    var temp = this.matrice[r_index];
    for(j = r_index; j != to_index; j += diff)
        this.matrice[j] = this.matrice[j + diff];

    this.matrice[to_index] = temp;

    return true;
};

AdvancedMatrice.prototype.moveColumnTo = function(c_index, to_index){

    c_index = this.getAbsoluteRowIndex(c_index);
    if(typeof(index) == 'boolean')
        return false;

    to_index = this.getAbsoluteRowIndex(to_index);
    if(typeof(index) == 'boolean')
        return false;


    var j, k, diff = (c_index > to_index ? -1 : 1);
    var temp;

    for(j = this.matrice.length - 1; j >= 0; j--){
        temp = this.matrice[j].v[c_index];

        for(k = c_index; k != to_index; k += diff)
            this.matrice[j].v[k] = this.matrice[j].v[k + diff];

        this.matrice[j].v[to_index] = temp;
    }

    temp = this.columns[c_index];

    for(k = c_index; k != to_index; k += diff)
        this.columns[k] = this.columns[k + diff];

    this.columns[to_index] = temp;

    return true;
};

AdvancedMatrice.prototype.moveRowToById = function(r_id, to_index){
    var r_index = this.getRowIndexById(r_id);

    if(typeof(index) != 'boolean')
        return this.moveRowTo(r_index, to_index);

    return false;
};

AdvancedMatrice.prototype.moveColumnToById = function(c_id, to_index){
    var c_index = this.getColumnIndexById(c_id);

    if(typeof(index) != 'boolean')
        return this.moveColumnTo(c_index, to_index);

    return false;
};

AdvancedMatrice.prototype.getRowSimilarity = function(similar){
    var ret = [];
    var j, k;
    for(j = this.matrice.length -1; j >= 0; j--){
        for(k = this.columns.length - 1; k > 0; k--){
            if(!areEqual(this.matrice[j].v[k], this.matrice[j].v[k - 1]))
                break;
        }

        if(similar){
            if(k == 0)
                ret.push(j);
        }
        else{
            if(k != 0)
                ret.push(j);
        }
    }

    return ret;
};

AdvancedMatrice.prototype.getRowSimilarityTable = function(){
    var ret = {};
    var j, k;
    var m = this.matrice;

    for(j = m.length - 1; j >= 0; j--){
        for(k = this.columns.length - 1; k > 0; k--){
            if(!areEqual(m[j].v[k], m[j].v[k - 1]))
                break;
        }

        ret[m[j].i] = (k == 0);

    }

    return ret;
};









