// Copyright 2006 Yusuke Kawasaki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var Wire3D = function () {};

Wire3D.Color = function ( r, g, b ) {
    this.setColor( r, g, b );
    return this;
};
Wire3D.Color.prototype.getStyle = function () {
    var r = Math.floor( this.r );
    var g = Math.floor( this.g );
    var b = Math.floor( this.b );
    return "rgb("+r+","+g+","+b+")";
};
Wire3D.Color.prototype.setColor = function ( r, g, b ) {
    this.r = r || 0;
    this.g = g || 0;
    this.b = b || 0;
    return this;
};
Wire3D.Color.prototype.dark = function (a) {
    if ( a < 0 ) return;
    var r = this.r - this.r * a;
    var g = this.g - this.g * a;
    var b = this.b - this.b * a;
    if ( r < 0 ) r = 0;
    if ( g < 0 ) g = 0;
    if ( b < 0 ) b = 0;
    this.setColor( r, g, b );
    return this;
};
Wire3D.Color.prototype.light = function (a) {
    if ( a < 0 ) return;
    var r = this.r + (255-this.r) * a;
    var g = this.g + (255-this.g) * a;
    var b = this.b + (255-this.b) * a;
    if ( r > 255 ) r = 255;
    if ( g > 255 ) g = 255;
    if ( b > 255 ) b = 255;
    this.setColor( r, g, b );
    return this;
};

Wire3D.Color.prototype.clone = function (a) {
    var copy = new Wire3D.Color( this.r, this.g, this.b );
    return copy;
};

Wire3D.Point = function ( ax, ay, az ) {
    this.x = ax || 0.0;
    this.y = ay || 0.0;
    this.z = az || 0.0;
    return this;
};
Wire3D.Point.prototype.scale = function ( sx, sy, sz ) {
    if ( typeof sy == 'undefined' ) sz = sy = sx;
    this.x *= sx;
    this.y *= sy;
    this.z *= sz;
    return this;
};
Wire3D.Point.prototype.move = function ( ax, ay, az ) {
    this.x += ax;
    this.y += ay;
    this.z += az;
    return this;
};
Wire3D.Point.prototype.rotate = function ( rx, ry, rz ) {
    var tx = this.x;
    var ty = this.y;
    var tz = this.z;
    if ( rx ) {
        var sinx = Math.sin( rx );
        var cosx = Math.cos( rx );
        var ny =   ty * cosx + tz * sinx;
        var nz = - ty * sinx + tz * cosx;
        ty = ny;
        tz = nz;
    }
    if ( ry ) {
        var siny = Math.sin( ry );
        var cosy = Math.cos( ry );
        var nx = tx * cosy - tz * siny;
        var nz = tx * siny + tz * cosy;
        tx = nx;
        tz = nz;
    }
    if ( rz ) {
        var sinz = Math.sin( rz );
        var cosz = Math.cos( rz );
        var nx =   tx * cosz + ty * sinz;
        var ny = - tx * sinz + ty * cosz;
        tx = nx;
        ty = ny;
    }
    this.x = tx;
    this.y = ty;
    this.z = tz;
    return this;
};

Wire3D.Point.prototype.clone = function () {
    var copy = new Wire3D.Point( this.x, this.y, this.z );
    return copy;
};


Wire3D.Template = function () {};

new function () {
    var P = function (x,y,z) {
        return new Wire3D.Point( x,y,z );
    };
    var SQR2 = Math.SQRT2;
    var SQR3 = Math.sqrt(3.0);
    Wire3D.Template.Null = {
        vertex: {},
        line: [],
        bezier: [],
		surface: [],
        scale: 1.0,
		type:	'Null'
    };
    Wire3D.Template.Triangle = {
        vertex: {
            "a": P(  SQR3, -1.0, 0.0 ),
            "b": P( -SQR3, -1.0, 0.0 ),
            "c": P(     0,  2.0, 0.0 )
        },
        line: [[ "a", "b", "c", "a" ]],
        surface: [[ "a", "b", "c" ]],
        bezier: [],
        scale:  SQR3*2,
		type:	'Triangle'
    };
    Wire3D.Template.Box = {
        vertex: {
            "a": P(  1.0,  1.0, 0.0 ),
            "b": P(  1.0, -1.0, 0.0 ),
            "c": P( -1.0, -1.0, 0.0 ),
            "d": P( -1.0,  1.0, 0.0 )
        },
        line: [[ "a", "b", "c", "d", "a" ]],
        surface: [[ "a", "b", "c", "d" ]],
        bezier: [],
        scale:  2.0,
		type:	'Box'
    };
    Wire3D.Template.Cube = {
        vertex: {
            "t1": P( -1.0, -1.0, -1.0 ),
            "t2": P(  1.0, -1.0, -1.0 ),
            "t3": P(  1.0,  1.0, -1.0 ),
            "t4": P( -1.0,  1.0, -1.0 ),
            "b1": P( -1.0, -1.0,  1.0 ),
            "b2": P(  1.0, -1.0,  1.0 ),
            "b3": P(  1.0,  1.0,  1.0 ),
            "b4": P( -1.0,  1.0,  1.0 )
        },
        line: [
            [ "t1", "t2", "t3", "t4", "t1" ],
            [ "b1", "b2", "b3", "b4", "b1" ],
            [ "t1", "b1" ],
            [ "t2", "b2" ],
            [ "t3", "b3" ],
            [ "t4", "b4" ]
        ],
        surface: [
            [ "t1", "t2", "t3", "t4" ],
            [ "t1", "t2", "b2", "b1" ],
            [ "t3", "t4", "b4", "b3" ],
            [ "t1", "t4", "b4", "b1" ],
            [ "t2", "t3", "b3", "b2" ],
            [ "b1", "b2", "b3", "b4" ]
        ],
        bezier: [],
        scale:  2.0,
		type:	'Cube'
    };
    Wire3D.Template.ArrowKey = {
        vertex: {
            "a1": P(  3.0,  1.0,  0.0 ),
            "a2": P(  1.0,  1.0,  0.0 ),
            "a3": P(  1.0,  3.0,  0.0 ),
            "b1": P( -1.0,  3.0,  0.0 ),
            "b2": P( -1.0,  1.0,  0.0 ),
            "b3": P( -3.0,  1.0,  0.0 ),
            "c1": P( -3.0, -1.0,  0.0 ),
            "c2": P( -1.0, -1.0,  0.0 ),
            "c3": P( -1.0, -3.0,  0.0 ),
            "d1": P(  1.0, -3.0,  0.0 ),
            "d2": P(  1.0, -1.0,  0.0 ),
            "d3": P(  3.0, -1.0,  0.0 )
        },
        line: [
            [ "a1", "a2", "a3", "b1", "b2", "b3", 
              "c1", "c2", "c3", "d1", "d2", "d3", "a1" ]
        ],
        surface: [[ "a1", "a2", "a3", "b1", "b2", "b3", 
              "c1", "c2", "c3", "d1", "d2", "d3" ]],
        bezier: [],
        scale:  6.0,
		type:	'ArrowKey'
    };
    Wire3D.Template.Tetrahedron = {
        vertex: {
            "a": P(   SQR3,-1.0, -SQR3/2 ),
            "b": P(   0.0, 2.0,  -SQR3/2 ),
            "c": P(  -SQR3,-1.0, -SQR3/2 ),
            "d": P( 0.0,   0.0,  2*SQR2-SQR3/2 )
        },
        line: [
            [ "a", "b", "c", "a" ],
            [ "a", "d" ],
            [ "b", "d" ],
            [ "c", "d" ]
        ],
        surface: [
            [ "a", "b", "c" ],
            [ "a", "d", "c" ],
            [ "b", "d", "c" ],
            [ "a", "b", "d" ]
        ],
        bezier: [],
        scale:  SQR3*2,
		type:	'Tetrahedron'
    };
    Wire3D.Template.Button = {
        vertex: {
            "aa": P(  1.0,  0.0,  0.0 ),
            "ab": P(  1.0,  1.0,  0.0 ),
            "bb": P(  0.0,  1.0,  0.0 ),
            "bc": P( -1.0,  1.0,  0.0 ),
            "cc": P( -1.0,  0.0,  0.0 ),
            "cd": P( -1.0, -1.0,  0.0 ),
            "dd": P(  0.0, -1.0,  0.0 ),
            "da": P(  1.0, -1.0,  0.0 )
        },
        bezier: [
            [ "aa", "ab", "ab", "bb" ],
            [ "bb", "bc", "bc", "cc" ],
            [ "cc", "cd", "cd", "dd" ],
            [ "dd", "da", "da", "aa" ]
        ],
        scale:  2.0,
		type:	'Button'
    };
    Wire3D.Template.Circle = {
        vertex: {
            "a":   P(  2.0,  0.0, 0.0 ), 
            "aab": P(  2.0,  1.1, 0.0 ), 
            "abb": P(  1.1,  2.0, 0.0 ), 
            "b":   P(  0.0,  2.0, 0.0 ), 
            "bbc": P( -1.1,  2.0, 0.0 ), 
            "bcc": P( -2.0,  1.1, 0.0 ), 
            "c":   P( -2.0,  0.0, 0.0 ), 
            "ccd": P( -2.0, -1.1, 0.0 ), 
            "cdd": P( -1.1, -2.0, 0.0 ), 
            "d":   P(  0.0, -2.0, 0.0 ), 
            "dda": P(  1.1, -2.0, 0.0 ), 
            "daa": P(  2.0, -1.1, 0.0 )
        },
        bezier: [
            [ "a", "aab", "abb", "b" ],
            [ "b", "bbc", "bcc", "c" ],
            [ "c", "ccd", "cdd", "d" ],
            [ "d", "dda", "daa", "a" ]
        ],
        scale:  4.0,
		type:	'Circle'
    };
};

Wire3D.Item = function () {
    this.scale    = 1.0;
    this.template = Wire3D.Template.Null;
    this.init();
    return this;
};

var join_count = 1;

Wire3D.Item.prototype.join = function ( elem ) {
    this.items.push( elem );
	return this;

//	単にマージするだけじゃダメ
//	surfaceとbezierを統合する必要がある

	var prefix = "j_"+join_count+"_";
	join_count++;
    if ( elem.vertex ) {
        for( var key in elem.vertex ) {
//console.log( prefix+key );
            this.vertex[prefix+key] = elem.vertex[key].clone();
        }
    }
    if ( elem.line ) {
        for ( var i = 0; i < elem.line.length; i++ ) {
            var newline = [];
            for( var j=0; j < elem.line[i].length; j++ ) {
                newline.push( prefix+elem.line[i][j] );
            }
            this.line.push( newline );
        }
    }
    if ( elem.surface ) {
        for ( var i = 0; i < elem.surface.length; i++ ) {
            var newsurface = [];
            for( var j=0; j < elem.surface[i].length; j++ ) {
                newsurface.push( prefix+elem.surface[i][j] );
            }
            this.surface.push( newsurface );
        }
    }
    if ( elem.bezier ) {
        for ( var i = 0; i < elem.bezier.length; i++ ) {
            var newbezier = [];
            for( var j=0; j < elem.bezier[i].length; j++ ) {
                newbezier.push( prefix+elem.bezier[i][j] );
            }
            this.bezier.push( newbezier );
        }
    }

    return this;
};
Wire3D.Item.prototype.init = function ( size ) {
    if ( ! size ) size = 1.0;
    this.vertex = {};
    this.line   = [];
    this.surface = [];
    this.bezier = [];
    this.items  = [];
    this.color  = new Wire3D.Color();
    this.center = new Wire3D.Point( 0,0,0 );

    var scale = size / this.template.scale;

	if ( this.template.type ) {
		this.type = this.template.type;
	}

    if ( this.template.vertex ) {
        for( var key in this.template.vertex ) {
            this.vertex[key] = this.template.vertex[key].clone().scale(scale);
        }
    }
    if ( this.template.line ) {
        for ( var i = 0; i < this.template.line.length; i++ ) {
            this.line[i] = this.template.line[i];
        }
    }
    if ( this.template.surface ) {
        for ( var i = 0; i < this.template.surface.length; i++ ) {
            this.surface[i] = this.template.surface[i];
        }
    }
    if ( this.template.bezier ) {
        for ( var i = 0; i < this.template.bezier.length; i++ ) {
            this.bezier[i] = this.template.bezier[i];
        }
    }
    this.scale = size;
    return this;
};
Wire3D.Item.prototype.setColor = function ( r, g, b ) {
    this.color.setColor( r, g, b );
    for ( var i = 0; i < this.items.length; i++ ) {
        this.items[i].setColor( r, g, b );
    }
    return this;
};

Wire3D.Item.prototype.display = function ( camera ) {
    this.fillSurface( camera );
    this.writeBeziers( camera );
    this.writeSpheres( camera );
    this.writeChildren( camera );
};

Wire3D.Item.prototype.fillSurface = function ( camera ) {
    if ( ! this.line ) return;

	var avg_z = [];
    for ( var i = 0; i < this.surface.length; i++ ) {
        var list = this.surface[i];
		var tz = 0;
        for ( var j = 1; j < list.length; j++ ) {
			tz += this.vertex[list[j]].z;
        }
		avg_z[i] = { surface: list, z: tz / list.length };
	}
	avg_z.sort( function(a,b) { return b.z - a.z } );

    for ( var i = 0; i < avg_z.length; i++ ) {
        var list = avg_z[i].surface;
        if ( list.length == 0 ) continue;

        // 最初の点
        var cursor = this.vertex[list[0]];

        var tc = this.color.clone();
		var tz = avg_z[i].z+0.5;
	    tc.light( tz * 0.5 );

        var style = {
            lineCap:     "butt",
            lineJoin:    "bevel",
            fillStyle: tc.getStyle()
        };
        camera.pathBegin( style );

        // 各点を辿って線を描く
        for ( var j = 1; j < list.length; j++ ) {
            var vertex = this.vertex[list[j]];
            camera.pathLineTo( vertex );
        }
        camera.pathLineTo( cursor );
        camera.pathEnd();
    }
    return this;
};
Wire3D.Item.prototype.writeBeziers = function ( camera ) {
    if ( ! this.bezier ) return;

    var tc = this.color.clone();
	var tz = this.center.z+0.5;
    tc.light( tz * 0.5 );
    var style = {
        lineCap:     "butt",
        lineJoin:    "bevel",
        fillStyle: tc.getStyle()
    };
    camera.pathBegin( style );

	var first = true;

    for ( var i = 0; i < this.bezier.length; i++ ) {
        var list = this.bezier[i];
        if ( list.length == 0 ) continue;

        var v0 = this.vertex[list[0]];
        var v1 = this.vertex[list[1]];
        var v2 = this.vertex[list[2]];
        var v3 = this.vertex[list[3]];

		if ( first ) {
			camera.pathMoveTo( v0 );
			first = false;
		}
        camera.pathCurveTo( v1, v2, v3 );
    }

    camera.pathEnd();
    return this;
};
Wire3D.Item.prototype.writeSpheres = function ( camera ) {
    if ( ! this.isSphere ) return;
    var tc = this.color.clone();
    var tz = this.center.z + 0.5;
    tc.light( tz * 0.5 );

    var style = {
        lineCap:     "butt",
        lineJoin:    "bevel",
        fillStyle: tc.getStyle()
    };

    camera.pathBegin( style );
    camera.pathCircle( this.center, this.scale/2 );
    camera.pathEnd();
    return this;
};
Wire3D.Item.prototype.writeChildren = function ( camera ) {
    if ( ! this.items ) return;

	var tmpz = [];
    for ( var i = 0; i < this.items.length; i++ ) {
		var itm = this.items[i];
		var tz = itm.center.z;
		for( var j in itm.vertex ) {
			if ( tz < itm.vertex[j].z ) tz = itm.vertex[j].z;
		}
		tmpz[i] = { item: itm, z: tz };
	}
	tmpz.sort( function(a,b) { return b.z - a.z } );

    for ( var i = 0; i < tmpz.length; i++ ) {
        var item = tmpz[i].item;
        item.display( camera );
    }
    return this;
};

Wire3D.Item.prototype.move = function ( dx, dy, dz ) {
    this.center.move( dx, dy, dz );
    for( var key in this.vertex ) {
        this.vertex[key].move( dx, dy, dz );
    }
    for ( var i = 0; i < this.items.length; i++ ) {
        this.items[i].move( dx, dy, dz );
    }
    return this;
};
Wire3D.Item.prototype.rotate = function ( rx, ry, rz ) {
    var cx = this.center.x;
    var cy = this.center.y;
    var cz = this.center.z;
    for( var key in this.vertex ) {
        var vertex = this.vertex[key];
        vertex.move( -cx, -cy, -cz );
        vertex.rotate( rx, ry, rz );
        vertex.move( cx, cy, cz );
    }
    for ( var i = 0; i < this.items.length; i++ ) {
        var item = this.items[i];
        item.rotate( rx, ry, rz );
        var rc = item.center.clone();
        rc.move( -cx, -cy, -cz );
        rc.rotate( rx, ry, rz );
        rc.move( cx, cy, cz );
        var ix = rc.x - item.center.x;
        var iy = rc.y - item.center.y;
        var iz = rc.z - item.center.z;
        item.move( ix, iy, iz );
    }
    return this;
};
Wire3D.Item.prototype.resize = function ( rx, ry, rz ) {
    var cx = this.center.x;
    var cy = this.center.y;
    var cz = this.center.z;
    for( var key in this.vertex ) {
        var vertex = this.vertex[key];
        vertex.move( -cx, -cy, -cz );
        vertex.scale( rx, ry, rz );
        vertex.move( cx, cy, cz );
    }
    for ( var i = 0; i < this.items.length; i++ ) {
        var item = this.items[i];
        item.resize( rx, ry, rz );
        var rc = item.center.clone();
        rc.move( -cx, -cy, -cz );
        rc.scale( rx, ry, rz );
        rc.move( cx, cy, cz );
        var ix = rc.x - item.center.x;
        var iy = rc.y - item.center.y;
        var iz = rc.z - item.center.z;
        item.move( ix, iy, iz );
    }
    if ( typeof ry == 'undefined' ) rz = ry = rx;
    var rr = ( rx + ry + rz ) / 3;
    this.scale *= rr;
    return this;
};


Wire3D.Item.Null = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Null.prototype = new Wire3D.Item();
Wire3D.Item.Null.prototype.template = Wire3D.Template.Null;

Wire3D.Item.Triangle = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Triangle.prototype = new Wire3D.Item();
Wire3D.Item.Triangle.prototype.template = Wire3D.Template.Triangle;

Wire3D.Item.Cube = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Cube.prototype = new Wire3D.Item();
Wire3D.Item.Cube.prototype.template = Wire3D.Template.Cube;

Wire3D.Item.ArrowKey = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.ArrowKey.prototype = new Wire3D.Item();
Wire3D.Item.ArrowKey.prototype.template = Wire3D.Template.ArrowKey;

Wire3D.Item.Tetrahedron = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Tetrahedron.prototype = new Wire3D.Item();
Wire3D.Item.Tetrahedron.prototype.template = Wire3D.Template.Tetrahedron;

Wire3D.Item.Circle = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Circle.prototype = new Wire3D.Item();
Wire3D.Item.Circle.prototype.template = Wire3D.Template.Circle;

Wire3D.Item.Sphere = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Sphere.prototype = new Wire3D.Item();
Wire3D.Item.Sphere.prototype.isSphere = true;

Wire3D.Item.Box = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Box.prototype = new Wire3D.Item();
Wire3D.Item.Box.prototype.template = Wire3D.Template.Box;

Wire3D.Item.Button = function ( size ) {
    this.init( size );
    return this;
};
Wire3D.Item.Button.prototype = new Wire3D.Item();
Wire3D.Item.Button.prototype.template = Wire3D.Template.Button;

Wire3D.Camera = function ( elem, world ) {
    if ( typeof elem == 'string' ) {
        elem = document.getElementById( elem );
    }
    if ( ! elem ) return;
    if ( ! elem.getContext ) return;
    this.elem   = elem;
    this.canvas = elem.getContext('2d');

    // 縮尺
    var width  = this.width();
    var height = this.height();
    this.scale = ( width < height ) ? width/2 : height/2;

    this.distance = 10;

    // キャンバスの中心に移動する
    this.canvas.translate( width/2, height/2 );

    if ( ! world ) world = new Wire3D.Space();
    this.world  = world;
    return this;
};

Wire3D.Camera.prototype.width = function () {
    return this.elem.clientWidth;
};
Wire3D.Camera.prototype.height = function () {
    return this.elem.clientHeight;
};
Wire3D.Camera.prototype.pathBegin = function ( style ) {
    var ctx = this.canvas;
    ctx.beginPath();
    if ( ! style ) return;
    for( var key in style ) {
        ctx[key] = style[key];
    }
};
Wire3D.Camera.prototype.pathEnd = function () {
    var ctx = this.canvas;
//    ctx.stroke();
	ctx.closePath();
	ctx.fill();
};
Wire3D.Camera.prototype.pathMoveTo = function ( vertex ) {
    var zoom = this.scale;
    var vs = this.distance;
    var vz =  vs / ( vs + vertex.z );
    var vx =  vertex.x * zoom * vz;
    var vy = -vertex.y * zoom * vz;
    this.canvas.moveTo( vx, vy );
};
Wire3D.Camera.prototype.pathLineTo = function ( vertex ) {
    var zoom = this.scale;
    var vs = this.distance;
    var vz =  vs / ( vs + vertex.z );
    var vx =  vertex.x * zoom * vz;
    var vy = -vertex.y * zoom * vz;
    this.canvas.lineTo( vx, vy );
};
Wire3D.Camera.prototype.pathCurveTo = function ( v1, v2, v3 ) {
    var zoom = this.scale;
    var vs = this.distance;
    var z1 =  vs / ( vs + v1.z );
    var x1 =  v1.x * zoom * z1;
    var y1 = -v1.y * zoom * z1;
    var z2 =  vs / ( vs + v2.z );
    var x2 =  v2.x * zoom * z2;
    var y2 = -v2.y * zoom * z2;
    var z3 =  vs / ( vs + v3.z );
    var x3 =  v3.x * zoom * z3;
    var y3 = -v3.y * zoom * z3;
    this.canvas.bezierCurveTo( x1, y1, x2, y2, x3, y3 );
};
Wire3D.Camera.prototype.pathCircle = function ( center, r ) {
    var zoom = this.scale;
    var vs = this.distance;
    var vz =  vs / ( vs + center.z );
    var vx =  center.x * zoom * vz;
    var vy = -center.y * zoom * vz;
    var vr =  r * zoom * vz;
    this.canvas.arc( vx, vy, vr, 0, Math.PI*2.0, true );
};

Wire3D.Camera.prototype.display = function () {
    var x = this.width();
    var y = this.height();
    this.canvas.clearRect(-x/2,-y/2,x,y);
    this.world.display( this );
}

Wire3D.Space = function () {
    this.items = [];
    return this;
};
Wire3D.Space.prototype.append = function ( elem ) {
    this.items.push( elem );
	return this;
}
Wire3D.Space.prototype.remove = function ( elem ) {
	var temp = elem.remove_target;
	elem.remove_target = true;
	while ( this.items.length ) {
		var cnt = 0;
		for( var i=0; i<this.items.length; i++ ) {
			if ( this.items[i].remove_target ) {
				this.items.splice( i, 1 );
				cnt ++;
				continue;
			}
		}
		if ( ! cnt ) break;
	}
	elem.remove_target = temp;
	return this;
}
Wire3D.Space.prototype.display = function ( camera ) {
    for( var i=0; i<this.items.length; i++ ) {
        this.items[i].display( camera );
    }
}

