Module("svgui", "0.0.1", function(thisMod){
    var SVGNS = "http://www.w3.org/2000/svg"; 
    var focusedComponent;//todo nneed's to be better.
    var clipboard=""; //todo: This should be a clipboard with a well defined interface.
    
    /**
        Prototype of a Multiline TextBox.
    */
    thisMod.TextBox = Class("TextBox", function(thisClass, Super){
        //constants for keys
        var KeyTab=9;
        var KeyDelete=127;
        var KeyBackSpace=8;
        var KeyUp = 38;
        var KeyDown = 40;
        var KeyRight = 39;
        var KeyLeft = 37;
        var KeyHome = 36;
        var KeyEnd = 35;
        var ShiftKey = 16;
        var CtrlKey = 17;
                       
        
        /**
            Initializes a TextBox Skin.
            @param bgnode         The background node which receives mouseevents(usually a rect of the size of the textbox.
            @param node              The text element to use for dispaying the text.
            @param carret            The element to use for a carret.
            @param scrllBtnUp     The element for the scrollbutton up
            @param scrllBtnDwn  The element for the scrollbutton down
            @param scrllBtn           The element for the scrollbutton
        */
        thisClass.prototype.initSkin = function(bgnode, textNode, carret, scrllBtnUp, scrllBtnDwn, scrllBtn){
            this.wordSeperators=[" ", ",", ".", ")", "(", "-"];  //use for selecting words
            this.inSelect=false;       //true if shiftkey is down.
            this.bgnode = bgnode;
            this.firstVisibleLine=20; //the index of the line from this.lines which is the first line in the textbox
            this.currPos = 0;          //position of carret in current line
            this.currLine = 1;         // the current linenumber
            this.spans = new Array(); //all visible lines.
            this.lines = new Array("");//the text of the textbox split up in lines.
            this.node = textNode;              
            this.carret = carret;
            this.scrollbar = new ScrollBar(this,scrllBtnUp,scrllBtnDwn,scrllBtn);
            //create all visible lines.
            for(var i=0;i<20;i++){
                var tsp = document.createElementNS(SVGNS, "tspan");
                tsp.setAttribute("y", 15 * (i+1));
                tsp.setAttribute("x","0");
                this.node.appendChild(tsp);
                this.spans.push(tsp);
            }
            this.setText("hello");
            this.bgnode.addEventListener("mouseup",this, false);
            this.node.addEventListener("mouseup",this, false);
            this.bgnode.addEventListener("mousedown",this, false);
            this.node.addEventListener("mousedown",this, false);
            this.node.addEventListener("click", this,false);
            this.carret.addEventListener("click", this,false);
        }
        
        /**
            Implienting DOM2 EventListener interface.
            It basically dispatches the events to an appropriate method.  
        */
        thisClass.prototype.handleEvent=function(evt){
            if(this[evt.type]){
                this[evt.type](evt);
            }
        }
        ///handles mousedowns into the text and background of the textbox.                
        thisClass.prototype.mousedown=function(evt){
            this.focus();
            if(evt.button!=2){
                this.handleMousePos(evt);
                this.startSelect();
            } 
        }
        ///handles mouseups into the text and background of the textbox.                
        thisClass.prototype.mouseup=function(evt){
            this.handleMousePos(evt);
            this.endSelect();
        }
        
        thisClass.prototype.isWordSeperator=function(s){
            for(var i=0;i<this.wordSeperators.length;i++){
                if(s==this.wordSeperators[i]){
                    return true;
                }  
            }
            return false;
        }
        //handles dblclick to select words
        thisClass.prototype.click = function(evt){
            if(evt.detail == 2){
               evt.stopPropagation();
               evt.preventDefault(); //prevent that ASV is selecting the whole text
               var l = this.lines[this.currLine-1];
               //find the beginning of the word
               for(var i=this.currPos;i>=0;i--){
                   this.selPos = i;
                   if(this.isWordSeperator(l.slice(i,i+1))){
                       this.selPos = i+1;
                       i=0;        
                   }
               }
               //find the end of the word
               for(var i=this.currPos;i<=l.length;i++){
                   this.currPos = i;
                   if(this.isWordSeperator(l.slice(i,i+1))){
                       i=l.length;                               
                   }
               }
               //select it.
               this.inSelect=true;
               this.updateCarret();
               this.inSelect=false;
            }
        }
        ///Calculates the position of the carret. thanx CTM for the code, Bernhard
        thisClass.prototype.handleMousePos=function(evt){
            var pointer=svgDocument.rootElement.createSVGPoint()
            pointer.x=evt.clientX;
            pointer.y=evt.clientY;
            var thenode=this.node;
            var ctmMatrix=this.node.getCTM();
            //ASV3.0 workaround
            if(navigator.appVersion=='3.0') {
                while (( thenode = thenode.parentNode ) != svgDocument ) {
                        ctmMatrix = thenode.getCTM().multiply(ctmMatrix);
                }
                ctmMatrix=ctmMatrix.translate(svgDocument.rootElement.currentTranslate.x,svgDocument.rootElement.currentTranslate.y);
                ctmMatrix=ctmMatrix.scale(svgDocument.rootElement.currentScale);
            }
            ctmMatrix=ctmMatrix.inverse();
            pointer=pointer.matrixTransform(ctmMatrix);
            var y = pointer.y;
            var ln = Math.round(y/ 15);
            ln= ln>this.lines.length?this.lines.length:ln;
            if (ln>0){
                this.currLine = ln;
                this.currPos = this.lines[ln-1].length;
                var p = this.spans[ln-1].getEndPositionOfChar(0);
                p.x = pointer.x  ;
                var pos = this.spans[ln-1].getCharNumAtPosition(p)
                if(pos>0){
                    var startpos=this.spans[ln-1].getStartPositionOfChar(pos).x;
                    var endpos=this.spans[ln-1].getEndPositionOfChar(pos).x;
                    if((pointer.x - startpos) > (endpos -pointer.x))	{
                            this.currPos = pos +1 ;
                    } else {
                            this.currPos = pos  ;	
                    }
                }
                this.updateCarret();
            }
        }
        /**
            handles keypresses.                                
            todo:
            This should definetly be done outside this component and handled by a input handler.
            Keyevents are not DOM compatible!!! DOM3 will be different(SVG 1.2 hopefully too).
        */
        thisClass.prototype.keypress =  function(evt){
                switch(evt.charCode){
                case KeyBackSpace: 
                    //this is needed for backspace to work
                    break;
                case 13:
                    this.insertNewLine();
                    break
                case 22: //"ctrl-v"
                case 24: //"ctrl-x"
                case 1: //"ctrl-a"
                    break;
                default:
                    this.textInput(String.fromCharCode(evt.charCode));
            }
        }
        ///See keypress.
        thisClass.prototype.keydown =  function(evt){
            switch(evt.keyCode){
                case KeyBackSpace:
                    this.deleteL();
                    break;
                case KeyDelete:
                    this.deleteR();
                    break;
                case KeyUp: 
                    this.onUp();
                    break;
                case KeyDown:
                    this.onDown();
                    break;
                case KeyRight:
                    this.onRight();
                    break;
                case KeyLeft:
                    this.onLeft();
                    break;
                case KeyHome:
                    this.onHome();
                    break;
                case KeyEnd:
                    this.onEnd();
                    break
                case ShiftKey:
                    this.startSelect();
                    break;
                case 65://"a" select all
                    if(evt.ctrlKey){
                        this.currLine=1;
                        this.currPos=0;
                        this.selLine=this.lines.length;
                        this.selPos =this.lines[this.lines.length-1].length;
                        this.inSelect =true;
                        this.updateCarret();
                    }
                    break;
                case 67://"c"
                    if(evt.ctrlKey){
                        clipboard=this.getSelectedText();
                    }
                    break;
                case 86:// "v"
                    if(evt.ctrlKey){
                        this.replaceSelection(clipboard); 
                    }
                    break;
                case 88:// "x"
                    if(evt.ctrlKey){
                        clipboard =this.getSelectedText();
                        this.replaceSelection(""); 
                    }
                    break;
                case 17:
                        break;
                case 33: //pgup
                    this.currLine -= this.spans.length;
                    if(this.currLine <1 ){
                        this.currLine = 1;
                    }
                    if(this.currPos > this.lines[this.currLine-1].length){
                        this.currPos = this.lines[this.currLine-1].length;
                    }
                    this.setScroll(this.firstVisibleLine - this.spans.length);
                    this.updateCarret();
                    break;
                case 34: //pgdown
                    this.currLine += this.spans.length;
                    if(this.currLine > this.lines.length){
                        this.currLine = this.lines.length;
                    }
                    if(this.currPos > this.lines[this.currLine-1].length){
                        this.currPos = this.lines[this.currLine-1].length;
                    }
                    this.setScroll(this.firstVisibleLine + this.spans.length);
                    this.updateCarret();
                    break;
                default:
                    break;
            }
        }
        ///See keypress.
        thisClass.prototype.keyup =  function(evt){
            switch(evt.keyCode){
                case KeyTab:
                    break;
                case ShiftKey:
                    this.endSelect();
                    break;
                case 67://ctrl-c workds only on key up
                    if(evt.ctrlKey){
                        clipboard=this.getSelectedText();
                    }
                    break;
                default:
                     break;
            }
        }
        /**Handles new text which is coming in by inserting it at the current position replacing selected text.
            @param text   A string containing the text.
        */
        thisClass.prototype.textInput = function(text){
            if(this.inSelect){
                this.inSelect=false;
                this.replaceSelection(text);
                this.inSelect=true;
            }else{
                this.replaceSelection(text);
            }
        }
        /**
            Deletes one Char from the right of the current position(del).
        */
        thisClass.prototype.deleteR = function(){
            if(this.currLine==this.selLine && this.currPos == this.selPos){
                if(this.currPos < this.lines[this.currLine-1].length){
                    this.selPos++;
                }else if(this.currLine < this.lines.length){
                    this.selLine++;
                    this.selPos=0;
                }
            }
            this.replaceSelection("");
        }
        /**
            Deletes one Char from the left of the current position(backspace).
        */
        thisClass.prototype.deleteL = function(evt){
            if(this.currLine==this.selLine && this.currPos == this.selPos){
                if(this.currPos > 0){
                    this.selPos--;
                }else if(this.currLine>1){
                    this.selLine--;
                    this.selPos=this.lines[this.selLine-1].length;
                }
            }
            this.replaceSelection("");
        }
        //shiftkey handling
        thisClass.prototype.startSelect=function(){
            this.inSelect = true;
            this.selPos = this.currPos;
            this.selLine = this.currLine;
        }
        //shiftkey handling
        thisClass.prototype.endSelect=function(){
            this.inSelect = false;
        }
        
        //navigation keys.
        thisClass.prototype.onLeft = function(){
            this.currPos=this.currPos>0?this.currPos-1:0;
            this.updateCarret();        
        }
        thisClass.prototype.onRight = function(){
            this.currPos = this.lines[this.currLine-1].length > this.currPos?this.currPos+1: this.lines[this.currLine-1].length;
            this.updateCarret(); 
        }
        thisClass.prototype.onUp = function(){
            this.currLine = this.currLine>1?this.currLine-1:1;
            this.currPos= this.lines[this.currLine-1].length >= this.currPos?this.currPos: this.lines[this.currLine-1].length;
            this.updateCarret();      
        }
        thisClass.prototype.onDown = function(){
            this.currLine = this.currLine<this.lines.length?this.currLine+1:this.lines.length;
            this.currPos= this.lines[this.currLine-1].length >= this.currPos?this.currPos: this.lines[this.currLine-1].length;
            this.updateCarret();
        }
        thisClass.prototype.onHome = function(){
            this.currPos= 0;
            this.updateCarret();
        }                               
        thisClass.prototype.onEnd = function(){
            this.currPos= this.lines[this.currLine-1].length;
            this.updateCarret();
        }
                       
                       
        /**
            Handles newLine(enter key).
            uncomment this and you have a single line TextBox.
        */
        thisClass.prototype.insertNewLine=function(){
            this.replaceSelection("\n")
         }
        
        /**
            Updates the position of the carret.
        */
        thisClass.prototype.updateCarret=function(){
            try{// todo : scrollbar handling should not be done like this!!!
                document.getElementById("scrollbtn").setAttribute("y", this.firstVisibleLine/((this.lines.length-this.spans.length)/250));
            }catch(e){
            }
            //make sure the line to go to is visible
            var ln = this.currLine-this.firstVisibleLine;
            if(ln > this.spans.length-1){
                this.setScroll(this.firstVisibleLine +1);
                return;
            } 
            if(ln <= 0 ){
                this.setScroll(this.firstVisibleLine -1);
                return;
            }
            
            //get the current line
            var tspan = this.spans[ln-1];
            try{
                var x = tspan.getEndPositionOfChar(this.currPos-1).x;
            }catch(e){
                var x =0;
            }
            this.carret.setAttribute("y", tspan.getAttribute("y") )
            this.carret.setAttribute("x", x)
            try{//selecting the text todo: SVG based selection would be nicer thatn ASV's
                if(this.inSelect){
                    var p1=this.getPosInText(this.selLine,this.selPos);
                    var p2=this.getPosInText(this.currLine, this.currPos);
                    if(p1<p2){
                        this.node.selectSubString(p1,p2-p1);                                           
                    }else{
                        this.node.selectSubString(p2,p1-p2);                                           
                    }
                }else{
                    this.node.selectSubString(this.getPosInText(this.currLine,this.currPos),0);  
                    this.selLine=this.currLine;
                    this.selPos=this.currPos;
                }
            }catch(e){
            }
        }
        /**
            Returns the selection as a String with \n at the end of each line.
        */
        thisClass.prototype.getSelectedText=function(){
            if(this.selLine != this.currLine){
                var startLine=this.selLine;
                var startPos=this.selPos;
                var endLine=this.currLine;
                var endPos=this.currPos;
                 //Start has to be before end!!!
                if(startLine>endLine){
                    startLine=endLine;
                    startPos=endPos;
                    endLine=this.selLine;
                    endPos =this.selPos;
                }else if(startLine==endLine && startPos>endPos){
                    startPos=endPos;
                    endPos =this.selPos;
                }
                var lines=new Array();
                var tx=this.lines[startLine-1];
                tx=tx.slice(startPos,tx.length);
                lines.push(tx);
                for(var ln=startLine+1;ln<endLine;ln++){
                    lines.push(this.lines[ln-1]);
                }
                
                lines.push(this.lines[endLine-1].slice(0,endPos));
                return lines.join("\n");
            }else if( this.selLine == this.currLine && this.selPos != this.currPos){
                var startPos=this.selPos;
                var endPos=this.currPos;
                if(startPos>endPos){
                    startPos=endPos;
                    endPos =this.selPos;
                }
                return this.lines[this.selLine-1].slice(startPos, endPos);
            }else{
                return "";
            }
        }
        /**
            Replaces the selected text with new text.
            @param text   The text to overwrite the selection with.
        */
        thisClass.prototype.replaceSelection=function(text){
            var txStart="";
            var txEnd="";
            //check if text is realy selectd
            if(this.selLine != this.currLine || (this.selLine == this.currLine && this.selPos != this.currPos)){
                var startLine=this.selLine;
                var startPos=this.selPos;
                var endLine=this.currLine;
                var endPos=this.currPos;
                //Start has to be before end!!!
                if(startLine>endLine){
                    startLine=endLine;
                    startPos=endPos;
                    endLine=this.selLine;
                    endPos =this.selPos;
                }else if(startLine==endLine && startPos>endPos){
                    startPos=endPos;
                    endPos =this.selPos;
                }
                //remember the parts of partially selected lines which need to be preserved
                var txStart=this.lines[startLine-1].slice(0,startPos);
                var txEnd=this.lines[endLine-1];
                txEnd=txEnd.slice(endPos, txEnd.length);
                this.currLine = startLine;
                this.currPos =startPos;
                //take out all selected text and the lines that are partially selected.
                this.lines.splice(startLine-1,(endLine-startLine)+1);
            }else{
                //take out all selected text and the lines that are partially selected.
                var tx=this.lines[this.currLine-1];
                txStart = tx.slice(0,this.currPos) 
                txEnd = tx.slice(this.currPos,tx.length);
                this.lines.splice(this.currLine-1,1);
            }
            
            var lines=text.split("\n");
            lines[0] = txStart + lines[0];
            lines[lines.length-1] = lines[lines.length-1] + txEnd;
            
            //insert new lines into this.lines stating at the current line.
            for(var i=0;i<lines.length;i++){
                text = lines[i];
                var ln = i + this.currLine;
                if(ln <= this.lines.length){
                    this.lines.splice(ln-1,0,text);
                }else{
                    this.lines.push(text);
                }
            }
            //reset carret.
            this.currLine+=lines.length-1;
            this.currPos=lines[lines.length-1].length - txEnd.length;
            this.setScroll(this.firstVisibleLine);
            this.updateCarret();
        }
        /**
            Scrolls the text.
            @param value  The offset from the first line to scroll the text.
        */
        thisClass.prototype.setScroll=function(value){
            if(value>this.lines.length- this.spans.length){
                value=this.lines.length- this.spans.length;
            }
            if(value<0){
                value=0;
            }
            if (this.lines.length > this.spans.length){
                this.firstVisibleLine =value;    
            }else{
                this.firstVisibleLine = 0;    
            }
            //update the visible lines of text.
            for(var i=0;i<this.spans.length;i++){
                var tsp = document.createElementNS(SVGNS, "tspan");
                tsp.setAttribute("y", 15 * (i+1));
                tsp.setAttribute("x","0");
                if(this.lines.length > i+this.firstVisibleLine){
                    tsp.appendChild(document.createTextNode(this.lines[i+this.firstVisibleLine]));
                }else{
                    tsp.appendChild(document.createTextNode(""));
                }
                this.node.replaceChild(tsp,this.spans[i]);
                this.spans[i] = tsp;
            }
        }
        /**
            Returns the position in the textElement's text.
            @param ln       The line number.
            @param pos    The position in the line(identified by ln).
        */
        thisClass.prototype.getPosInText=function(ln, pos){
            var p=pos+0;
            for(var i=this.firstVisibleLine+1; i<ln;i++){
                p+= this.lines[i-1].length;
            }
            return p;
        }
                     
        //focus the textbox.
        thisClass.prototype.focus=function(){
            if(focusedComponent != this){
                try{
                    focusedComponent.unfocus();
                }catch(e){
                }
                focusedComponent =this;
                this.carret.setAttribute('fill','black');
                //todo CSV does not allow DOM2 EventListner objects like ASV. :(
                //there is a workaround though to make it work.
                document.documentElement.addEventListener("keypress",this, false);
                document.documentElement.addEventListener("keydown",this, false);
                document.documentElement.addEventListener("keyup",this, false);
            }
        }
         //unfocus the textbox.
        thisClass.prototype.unfocus=function(){
             if(focusedComponent == this){
                this.carret.setAttribute("fill","none");
                document.documentElement.removeEventListener("keypress",this, false);
                document.documentElement.removeEventListener("keydown",this, false);
                document.documentElement.removeEventListener("keyup",this, false);
            }
        }
        //set's the text.
        thisClass.prototype.setText=function(text){
            this.currLine =1;
            this.currPos=0;
            this.lines =text.split("\n");
            this.setScroll(0);
            this.updateCarret();
        }
    })
    
    /**
      Hack for for ScrollBar.
    */
    var ScrollBar =Class("ScrollBar",function(thisClass){
        thisClass.prototype.init = function(textBox, btUp,btDown, bt){
            this.bt=bt;
            this.tb =textBox;
            this.btUp = btUp;
            this.btDown = btDown;
            bt.addEventListener("mousedown", this, false);
            btUp.addEventListener("click", this, false);
            btDown.addEventListener("click", this, false);
        }
        thisClass.prototype.mousedown = function(evt){
            evt.stopPropagation();
            var x=this.bt.getAttribute("x");
            var y=this.bt.getAttribute("y");
            this.y=evt.screenY - y;
            document.documentElement.addEventListener("mousemove",this,false)
            document.documentElement.addEventListener("mouseup",this,false)
        }
        
        thisClass.prototype.click=function(evt){
            if(evt.target.parentNode == this.btUp){
                this.tb.setScroll(this.tb.firstVisibleLine-1)
            }else if(evt.target.parentNode == this.btDown) {
                this.tb.setScroll(this.tb.firstVisibleLine+1)
            }
        }
        thisClass.prototype.handleEvent=function(evt){
            if(this[evt.type]){
                this[evt.type](evt);
            }
        }        
        thisClass.prototype.mouseup=function(evt){
            document.documentElement.removeEventListener("mousemove",this,false)
            document.documentElement.removeEventListener("mouseup",this,false)
        }
        thisClass.prototype.mousemove=function(evt){
            evt.stopPropagation();
            //todo This should not be hardcoded.  BAD BAD BAD!!!
            
            var y = evt.screenY - this.y;
            if(y<0){
                y=0;
            }
            if (y>  250){
                y=250;
            }
            this.bt.setAttribute("y",y);//we only care for up and down scrolling right now.
            this.tb.setScroll(Math.round((y*(this.tb.lines.length-this.tb.spans.length))/250));
        }
    });
    
    //========================================================================
    
    /**
        Initialization of the ui.
        All components will be created using skins.
    */
    thisMod.initUI=function(){
       //get all skins and build components which use the skin 
       var skins = document.getElementsByTagNameNS("http://jan.kollhof.net/svgui","skin");
        for(var i=0;i<skins.length;i++){
            buildComponents(skins.item(i));
        }
    }
  
    var  buildComponents= function(skin){
        //get the tagname for the component the skin is for.
        var compElem = skin.getAttributeNS("http://jan.kollhof.net/svgui","compElem");
        //get the component's class 
        //todo : window[..] needs to be replaced with a module.
        var CmpClass =thisMod[skin.getAttributeNS("http://jan.kollhof.net/svgui","componentClass")];
        //get all elements/components which shuld be build using that skin
        var elems = document.getElementsByTagNameNS("http://jan.kollhof.net/svgui",compElem);
        while(elems.length>0){
            //create a g element  for the component.
            var newNode = document.createElementNS(SVGNS, "g");
            //duplicate the skin's child elements to construct the new component
            for(var j=0;j<skin.childNodes.length;j++){
                if(skin.childNodes.item(j).nodeType == 1){
                   var n = skin.childNodes.item(j).cloneNode(true);
                   newNode.appendChild(n);
                }
            }
            //get the params for initialization(nodes that need to be passed to the component class
            var params = findInitParams(newNode);
            //construct the component
            var c = new CmpClass();
            //init the component by passing the parameters
            c.initSkin.apply(c, params);
            var compNode = elems.item(0);
            //get the name for the component
            var cmpName = compNode.getAttributeNS("http://jan.kollhof.net/svgui","name");
            //set x,y by transforming the newNode 
            //todo: what about other propertiess like width, height
            //a ui:applyAttribs="width-2, height-2" in the skin might be an idea
            //or calling the component to set width and height and other attribs. or even text
            newNode.setAttribute("transform", "translate(" + compNode.getAttribute("x") + "," + compNode.getAttribute("y") + ")");
            //replace the compNode with the newNode
            compNode.parentNode.replaceChild(newNode,compNode);
            
            //add component to window(as JS object)
            window[cmpName] = c;
        }
    }
       
    
    //parses for init parameters
    var findInitParams= function(node, params){
        params=(params!=null)?params:new Array();
        var childNodes =node.childNodes;
        for(var i=0;i<childNodes.length;i++){
            if(childNodes.item(i).nodeType == 1){
                if(childNodes.item(i).hasAttributeNS("http://jan.kollhof.net/svgui","initParam")){
                    var index = childNodes.item(i).getAttributeNS("http://jan.kollhof.net/svgui","initParam");
                    params[index] = childNodes.item(i);
                }
                //parse children recursively
                findInitParams(childNodes.item(i),params);
            }
        }
        return params;
    }
})