Kem.MolecularEditor = Class.create(Base, {
    ctx: null,
    size: null,

    elements: [],
    bonds: [],
    texts: [],

    actionsQueue: [],

    group: null,

    selectedItems: [],

    flagMove: false,

    initialize: function($super, args) {
        $super(args);
    },

    addAction: function(args) {
        this.actionsQueue.push(new Kem.MolecularEditor.Action(Object.extend({editor: this}, args)));
    },

    addElement: function(args) {
        return this.elements.push(new Kem.MolecularEditor.Element(Object.extend({editor: this, ctx: this.ctx}, args)));
    },

    addBond: function(args) {
        this.bonds.push(new Kem.MolecularEditor.Bond(Object.extend({editor: this, ctx: this.ctx}, args)));
    },

    addText: function(args) {
        this.texts.push(new Kem.MolecularEditor.Text(Object.extend({editor: this, ctx: this.ctx}, args)));
    },

    addStructuredElement: function(args) {
        var editor = this;
        var sEl = args.structuredElement;
        var elIndexes = [];
        sEl.elements.each(function(el) { elIndexes.push(editor.addElement({x: el.x + args.offset.x, y: el.y + args.offset.y, r: 4, name: el.name})); });
        sEl.bonds.each(function(bnd) { editor.addBond({elements: [editor.elements[elIndexes[bnd.el1] - 1], editor.elements[elIndexes[bnd.el2] - 1]], type: bnd.type}); });
    },

    clear: function() {
        this.elements = [];
        this.bonds = [];
        this.ctx.clear();
        
        this.group = this.ctx.createGroup();
        new dojox.gfx.Moveable(this.group, {mover: Kem.MolecularEditor.Mover});

        this.selectedItems = [];
    },

    draw: function() {
        this.elements.each(function(el) { el.draw(); });
        this.bonds.each(function(bnd) { bnd.draw(); });
        this.texts.each(function(txt) { txt.draw(); });
    },

    zoom: function(args) {
        this.elements.each(function(el) { 
            el.setShape({
                x: el.x + el.x * 0.1 * (args.zoomType == "zoomIn" ? 1 : -1), 
                y: el.y + el.y * 0.1 * (args.zoomType == "zoomIn" ? 1 : -1)
            });
        });
        this.draw();
    },

    move: function(args) {
        this.elements.each(function(el) { 
            if(el.flagSelected) {
                el.setShape({x: el.x + args.dx, y: el.y + args.dy});
            }
        });
    },

    rotate: function(args) {
        var angle = 5;
        if(args.rotateType == "rotateLeft") angle = -5;
        var kemUtils = new Kem.Utils();
        var rotatePoints = [];
        for(var eCnt = 0; eCnt < this.elements.length; eCnt++) {
            var el = this.elements[eCnt];
            if(el.flagSelected) rotatePoints.push({x: el.x, y: el.y});
        }
        var cnt = kemUtils.polygonCenter({points: rotatePoints});
        for(var eCnt = 0; eCnt < this.elements.length; eCnt++) {
            var el = this.elements[eCnt];
            if(el.flagSelected) {
                el.setShape({x: (cnt.x + (el.x - cnt.x) * Math.cos(kemUtils.degToRad({angle: angle})) - (el.y - cnt.y) * Math.sin(kemUtils.degToRad({angle: angle}))), y: (cnt.y + (el.x - cnt.x) * Math.sin(kemUtils.degToRad({angle: angle})) + (el.y - cnt.y) * Math.cos(kemUtils.degToRad({angle: angle})))});
                el.draw();
//                el.circle.setTransform(t);
//                el.text.setTransform(t);
//                el.getPos();
//                el.setShape({x: el.x + t.dx, y: el.y + t.dy});
            }
        }
    },

    deleteSelectedItems: function() {
        this.selectedItems.each(function(it) {
            if(it.type == "element") {
                it.editor.bonds.each(function(bnd) {
                    if(bnd.elements[0].id == it.id || bnd.elements[1].id == it.id) bnd.destroy();
                });
            }
            it.destroy();
        });
        this.selectedItems = [];
        this.draw();
    }
});

dojo.declare("Kem.MolecularEditor.Moveable", dojox.gfx.Moveable, {
    onMoving: function(mover, shift) {
        for(var sCnt = 0; sCnt < this.shape.children.length; sCnt++) {
            var child = this.shape.children[sCnt];
            var childShape = this.shape.children[sCnt].shape;
            if(childShape.type == "circle") {
                //child.el.setShape({x: child.el.x + shift.dx, y: child.el.y + shift.dy});
            }
        }
    }
});

dojo.declare("Kem.MolecularEditor.Mover", dojox.gfx.Mover, {
    onMouseMove: function(e) {
        var x = e.clientX;
        var y = e.clientY;
        var dx = x - this.lastX;
        var dy = y - this.lastY;
        for(var sCnt = 0; sCnt < this.shape.children.length; sCnt++) {
            var children = this.shape.children[sCnt].children;
            for(cCnt = 0; cCnt < children.length; cCnt++) {
                var child = children[cCnt];
                var childShape = children[cCnt].shape;
                if(childShape && child.el) {
                    child.el.setShape({x: child.el.x + dx, y: child.el.y + dy});
                    child.el.draw();
                } 
            }
        }
        this.lastX = x;
        this.lastY = y;
        dojo.stopEvent(e);
    },

    _move: function(args) {

    }
});

Kem.MolecularEditor.Item = Class.create(Base, {
    type: "",
    id: 0,
    editor: null,
    ctx: null,
    flagSelected: false,

    selector: null,
    group: null,
    textGroup: null
});

Kem.MolecularEditor.Element = Class.create(Kem.MolecularEditor.Item, {
    type: "element",
    
    name: "",
    balancedName: "",

    x: 0, y: 0, r: 0,
    circle: null,
    crossLine1: null,
    crossLine2: null,
    
    initialize: function($super, args) {
        $super(args);
        this.id = 100 + Math.floor(Math.random() * 10000);
        eval("var elInfo = elements."+this.name+";");
        this.info = elInfo;

        this.group = this.ctx.createGroup(); 

        this.init();
        
        this.group.connect("onclick", this, "onClick"); 
        this.group.connect("ondblclick", this, "onDblClick"); 
        this.group.connect("onmouseover", this, "onMouseOver"); 
        this.group.connect("onmouseleave", this, "onMouseLeave"); 
        this.draw();
    },

    init: function() {
        if(this.circle) { this.ctx.remove(this.circle); this.circle = null; }
        this.circle = this.ctx.createCircle({cx : this.x, cy : this.y, r: this.r}).setFill("red");
        if(this.selector) { this.ctx.remove(this.selector); this.selector = null; }
        this.selector = this.ctx.createCircle({cx : this.x, cy : this.y, r: this.r + 16}).setFill([0, 0, 255, 0.1]);

        this.group.clear().add(this.circle).add(this.selector); 
        this.ctx.remove(this.group).add(this.group);

        this.circle.el = this;
    },

    onClick: function() {
        if(!this.flagSelected) this.select();
        $j("#div_text_edit").fadeOut("slow");
    },

    onDblClick: function() {
        if(this.flagSelected) this.deselect();
    },

    onMouseOver: function() {
        $j(document.body).css("cursor", "pointer");
    },

    onMouseLeave: function() {
        $j(document.body).css("cursor", "default");
    },

    select: function() {
        this.flagSelected = true;
        this.editor.group.add(this.group);
        editor.selectedItems.push(this);
        this.draw();
    },

    deselect: function() {
        this.flagSelected = false;
        this.editor.group.remove(this.group);
        this.init();
        editor.selectedItems = editor.selectedItems.without(this);
        this.draw();
    },

    setShape: function(args) {
        this.x = (args.x ? args.x : this.x);
        this.y = (args.y ? args.y : this.y);
        //debug("x: "+this.x+", y: "+this.y);
    },

    draw: function() {
        this.circle.setShape({cx: this.x, cy: this.y});
        this.selector.setShape({cx: this.x, cy: this.y});

        var fillColor = "red";
        if(this.flagSelected) fillColor = "blue";
        
        if(this.textGroup) { this.textGroup.clear(); this.textGroup.removeShape(); this.textGroup = null; }
        var kemUtils = new Kem.Utils();
        this.textGroup = kemUtils.text({text: this.getBalance(), ctx: this.ctx, editor: this.editor, x: this.x, y: this.y, r: this.r, fillColor: fillColor});
        this.group.add(this.textGroup);

        if(this.crossLine1) this.crossLine1.removeShape();
        if(this.crossLine2) this.crossLine2.removeShape();
        if(this.getBondsVal() > this.info.criticalBonds) {
            this.crossLine1 = this.ctx.createLine({x1: this.x + 7, x2: this.x - 7, y1: this.y, y2: this.y}).setStroke({color: [255, 0, 198], width: 3}).setTransform(dojox.gfx.matrix.rotategAt(45, this.x, this.y));
            this.crossLine2 = this.ctx.createLine({x1: this.x, x2: this.x, y1: this.y + 7, y2: this.y - 7}).setStroke({color: [255, 0, 198], width: 3}).setTransform(dojox.gfx.matrix.rotategAt(45, this.x, this.y));
            this.group.add(this.crossLine1).add(this.crossLine2);
        }

        if(this.flagSelected) { 
            this.circle.setFill(fillColor);
        } else { 
            this.circle.setFill(fillColor);
        }

        this.selector.moveToFront();
    },

    getPos: function() {
        if(dojox.gfx.renderer == "svg") {
            this.x = this.circle.getNode().getAttribute("cx");
            this.y = this.circle.getNode().getAttribute("cy");
            debug("x: "+this.x+", y: "+this.y+", rand: "+Math.random());
        } else if(dojo.gfx.renderer == "vml") {
            this.x = this.circle.getNode().style.left;
            this.y = this.circle.getNode().style.top;
        }
        this.setShape({});
    },

    getBondsNum: function() { 
        var bondsNum = 0;
        var el = this;
        this.editor.bonds.each(function(bnd) {
            if(bnd.elements[0] == el || bnd.elements[1] == el) { 
                bondsNum++; 
            }
        });
        return bondsNum;
    },

    getBondsVal: function() { 
        var bondsVal = 0;
        var el = this;
        this.editor.bonds.each(function(bnd) {
            if(bnd.elements[0] == el || bnd.elements[1] == el) { 
                if(bnd.type == "double") bondsVal += 2; 
                else if(bnd.type == "triple") bondsVal += 3; 
                else bondsVal++;
            }
        });
        return bondsVal;
    },

    getBalance: function() {
        var bal = this.name;
        var bondsVal = this.getBondsVal();
        if(this.info.bonds.max) {
            var addBondsVal = this.info.bonds.max - bondsVal;
            if(bal == "H") { if(addBondsVal >= 1) bal = "H<sub>2</sub>"; }
            else if(addBondsVal == 1) bal += "H";
            else if(addBondsVal > 1)  bal += "H<sub>"+addBondsVal+"</sub>";
            if(bondsVal - this.info.bonds.max > 0) bal += "<sup>"+(bondsVal - this.info.bonds.max)+"-</sup>";
            if((bal == "C" || bal == "CH" || bal == "CH<sub>2</sub>") && this.getBondsNum() > 1) return "";
        } else {
            var minBondsVal = this.info.bonds.fixed[0];
            var maxBondsVal = this.info.bonds.fixed[this.info.bonds.fixed.length - 1];
            if(bondsVal < minBondsVal) {
                var addBondsVal = minBondsVal - bondsVal;
                if(addBondsVal == 1) bal += "H";
                else if(addBondsVal > 1)  bal += "H<sub>"+addBondsVal+"</sub>";
            } else if(bondsVal > maxBondsVal) {
                bal += "<sup>"+(bondsVal - maxBondsVal)+"-</sup>";
            } else {
                for(var bfCnt = 0; bfCnt < this.info.bonds.fixed.length; bfCnt++) {
                    if(bondsVal == this.info.bonds.fixed[bfCnt]) return bal;
                }
                for(var bfCnt = 0; bfCnt < this.info.bonds.fixed.length; bfCnt++) {
                    if(bondsVal < this.info.bonds.fixed[bfCnt]) return bal+"<sup>"+(this.info.bonds.fixed[bfCnt] - bondsVal)+"+</sup>";
                }
                
            }
        }
//        else if(this.info.bonds.max - bondsVal > 0) bal += "<sup>"+(this.info.bonds.max - bondsVal)+"+</sup>";
        return bal;
    },

    destroy: function() {
        var id = this.id;
        this.group.removeShape();
        this.circle.removeShape();
        this.selector.removeShape();
        this.textGroup.removeShape();
        this.editor.elements.each(function(el, eCnt) {
            if(el.id == id) el.editor.elements.splice(eCnt, 1);  
        });
    }
});

Kem.MolecularEditor.Text = Class.create(Kem.MolecularEditor.Item, {
    type: "text",

    text: "",

    x: 0, y: 0,
    
    initialize: function($super, args) {
        $super(args);
        this.id = 100 + Math.floor(Math.random() * 10000);

        this.group = this.ctx.createGroup(); 

        this.init();
        
        this.group.connect("onclick", this, "onClick"); 
        this.group.connect("ondblclick", this, "onDblClick"); 
        this.group.connect("onmouseover", this, "onMouseOver"); 
        this.group.connect("onmouseleave", this, "onMouseLeave"); 
        this.draw();
    },

    init: function() {
        if(this.selector) { this.ctx.remove(this.selector); this.selector = null; }
        this.selector = this.ctx.createRect({x : this.x, y : this.y - 5, width: 10 * this.text.length, height: 20}).setFill([255, 255, 255, 0.1]);

        this.group.clear().add(this.selector); 
        this.ctx.remove(this.group).add(this.group);

        this.selector.el = this;
    },

    onClick: function() {
        if(!this.flagSelected) {
            this.select();
            $j("#text_id").val(this.id);
            $j("#text_content").val(this.text);
        }
        $j("#div_text_edit").fadeIn("slow");
    },

    onDblClick: function() {
        if(this.flagSelected) this.deselect();
    },

    select: function() {
        this.flagSelected = true;
        this.editor.group.add(this.group);
        editor.selectedItems.push(this);
        this.draw();
    },

    deselect: function() {
        this.flagSelected = false;
        this.editor.group.remove(this.group);
        this.init();
        editor.selectedItems = editor.selectedItems.without(this);
        this.draw();
    },

    onMouseOver: function() {
        $j(document.body).css("cursor", "pointer");
    },

    onMouseLeave: function() {
        $j(document.body).css("cursor", "default");
    },

    setShape: function(args) {
        this.x = (args.x ? args.x : this.x);
        this.y = (args.y ? args.y : this.y);
        //debug("x: "+this.x+", y: "+this.y);
    },

    draw: function() {
        this.selector.setShape({cx: this.x, cy: this.y});

        var fillColor = "red";
        if(this.flagSelected) fillColor = "blue";
        
        if(this.textGroup) { this.textGroup.clear(); this.textGroup.removeShape(); this.textGroup = null; }
        var kemUtils = new Kem.Utils();
        this.textGroup = kemUtils.text({text: this.text, ctx: this.ctx, editor: this.editor, x: this.x, y: this.y, r: 5, fillColor: fillColor});
        this.group.add(this.textGroup);

        this.selector.moveToFront();
    },

    destroy: function() {
        var id = this.id;
        this.group.removeShape();
        this.selector.removeShape();
        this.textGroup.removeShape();
        this.editor.texts.each(function(txt, tCnt) {
            if(txtl.id == id) txt.editor.texts.splice(tCnt, 1);  
        });
    }
});

Kem.MolecularEditor.Bond = Class.create(Base, {
    type: "single",
    
    id: 0,

    elements: [],

    ctx: null,
    editor: null,
    line1: null,
    line2: null,
    line3: null,
    wedge: null,

    initialize: function($super, args) {
        $super(args);
        
        this.id = 100 + Math.floor(Math.random() * 10000);

        this.line1 = this.ctx.createLine();
        if(this.type == "double" || this.type == "triple") {
            this.line2 = this.ctx.createLine();
        }
        if(this.type == "triple") {
            this.line3 = this.ctx.createLine();
        }
        if(this.type == "wedge" || this.type == "wedge_empty") {
            this.wedge = this.ctx.createPolyline();
        }
        this.draw();
    },

    draw: function() { 
        this.line1.setShape({x1: this.elements[0].x, x2: this.elements[1].x, y1: this.elements[0].y, y2: this.elements[1].y}).setStroke({color: "black", width: 1});
        var m = this.elements[0].x;
        var p = this.elements[1].x;
        var n = this.elements[0].y;
        var q = this.elements[1].y;
        if(this.type == "double" || this.type == "triple") {
            if(n == q) {
                var x1 = m;
                var x2 = p;
                var y1 = n - 3;
                var y2 = q - 3;
            } else {
                var x1 = -((3*q-3*n)*Math.sqrt(q*q-2*n*q+p*p-2*m*p+n*n+m*m)-m*q*q+2*m*n*q-m*p*p+2*m*m*p-m*n*n-m*m*m)/(q*q-2*n*q+p*p-2*m*p+n*n+m*m);
                var y1 = (-1*(p-m)*(x1-m)/(q-n))+n;
                var x2 = -((3*q-3*n)*Math.sqrt(q*q-2*n*q+p*p-2*m*p+n*n+m*m)-p*q*q+2*n*p*q-p*p*p+2*m*p*p+(-n*n-m*m)*p)/(q*q-2*n*q+p*p-2*m*p+n*n+m*m);
                var y2 = (-1*(p-m)*(x2-p)/(q-n))+q;
            }
            this.line2.setShape({x1: x1, x2: x2, y1: y1, y2: y2}).setStroke({color: "black", width: 1});
        }
        if(this.type == "triple") {
            if(n == q) {
                var x1 = m;
                var x2 = p;
                var y1 = n + 3;
                var y2 = q + 3;
            } else {
                var x1 = ((3*q-3*n)*Math.sqrt(q*q-2*n*q+p*p-2*m*p+n*n+m*m)+m*q*q-2*m*n*q+m*p*p-2*m*m*p+m*n*n+m*m*m)/(q*q-2*n*q+p*p-2*m*p+n*n+m*m);
                var y1 = (-1*(p-m)*(x1-m)/(q-n))+n;
                var x2 = ((3*q-3*n)*Math.sqrt(q*q-2*n*q+p*p-2*m*p+n*n+m*m)+p*q*q-2*n*p*q+p*p*p-2*m*p*p+(n*n+m*m)*p)/(q*q-2*n*q+p*p-2*m*p+n*n+m*m);
                var y2 = (-1*(p-m)*(x2-p)/(q-n))+q;
            }
            this.line3.setShape({x1: x1, x2: x2, y1: y1, y2: y2}).setStroke({color: "black", width: 1});
        }
        if(this.type == "wedge" || this.type == "wedge_empty") {
            var points = [];
            points.push({x: this.elements[0].x, y: this.elements[0].y});
            var x2 = -((3*q-3*n)*Math.sqrt(q*q-2*n*q+p*p-2*m*p+n*n+m*m)-p*q*q+2*n*p*q-p*p*p+2*m*p*p+(-n*n-m*m)*p)/(q*q-2*n*q+p*p-2*m*p+n*n+m*m);
            var y2 = (-1*(p-m)*(x2-p)/(q-n))+q;
            points.push({x: x2, y: y2});
            var x2 = ((3*q-3*n)*Math.sqrt(q*q-2*n*q+p*p-2*m*p+n*n+m*m)+p*q*q-2*n*p*q+p*p*p-2*m*p*p+(n*n+m*m)*p)/(q*q-2*n*q+p*p-2*m*p+n*n+m*m);
            var y2 = (-1*(p-m)*(x2-p)/(q-n))+q;
            points.push({x: x2, y: y2});
            this.wedge.setShape(points);
            if(this.type == "wedge") this.wedge.setStroke({color: "black"}).setFill("black");
            else if(this.type == "wedge_empty") this.wedge.setFill("#999999");
        }
    },

    equals: function(args) {
        if(this.elements.length != args.elements.length) return false;
        var matchesCnt = 0;
        for(var eCnt = 0; eCnt < this.elements.length; eCnt++) {
            for(var e2Cnt = 0; e2Cnt < args.elements.length; e2Cnt++) {
                if(this.elements[eCnt].id == args.elements[e2Cnt].id) matchesCnt++;
            }
        }
        if(matchesCnt != 2) return false;
        return true;
    },

    destroy: function() {
        var id = this.id;
        this.line1.removeShape();
        if(this.type == "double" || this.type == "triple") this.line2.removeShape();
        if(this.type == "triple") this.line3.removeShape();
        if(this.type == "wedge" || this.type == "wedge_empty") this.wedge.removeShape();
        this.editor.bonds.each(function(bnd, bCnt) {
            if(bnd.id == id) { bnd.editor.bonds.splice(bCnt, 1); }
        });
    }
});

Kem.MolecularEditor.Action = Class.create(Base, {
    editor: null,

    type: "",
    params: {}
});

Kem.Utils = Class.create({
        
    polygonCenter: function(args) {
        var points = args.points;
        var xs = 0, ys = 0;
        for (var pCnt = 0; pCnt < points.length; pCnt++) {
            xs += points[pCnt].x;
            ys += points[pCnt].y;
        }
        return { x: xs / points.length, y: ys / points.length };
    },

    degToRad: function(args) {
        return args.angle * Math.PI / 180;
    },

    text: function(args) {
        var textGroup = args.ctx.createGroup();
        var flagInSup = false;
        var flagInSub = false;
        for(var lCnt = 0, pCnt = 0; lCnt < args.text.length; lCnt++) {
            if(args.text.indexOf("<sup>") == lCnt) { flagInSup = true; lCnt += 4; continue; }
            else if(args.text.indexOf("</sup>") == lCnt) { flagInSup = false; lCnt += 5; continue; }
            else if(args.text.indexOf("<sub>") == lCnt) { flagInSub = true; lCnt += 4; continue; }
            else if(args.text.indexOf("</sub>") == lCnt) { flagInSub = false; lCnt += 5; continue; }

            if(flagInSub) textGroup.add(args.ctx.createText({text: args.text.charAt(lCnt)}).setFont({family: "Helvetica", style: "bold", size: "8pt", rotated: true}).setFill(args.fillColor).setShape({x: args.x + args.r + pCnt * 11, y: args.y + args.r + 3}));
            else if(flagInSup) textGroup.add(args.ctx.createText({text: args.text.charAt(lCnt)}).setFont({family: "Helvetica", style: "bold", size: "8pt", rotated: true}).setFill(args.fillColor).setShape({x: args.x + args.r + pCnt * 10, y: args.y + args.r - 8}));
            else textGroup.add(args.ctx.createText({text: args.text.charAt(lCnt)}).setFont({family: "Helvetica", style: "bold", size: "12pt", rotated: true}).setFill(args.fillColor).setShape({x: args.x + args.r + pCnt * 10, y: args.y + args.r}));
            pCnt++;
        }
        return textGroup;
    }
});


