function MMath() {
    this.sa = new Array();
    this.ca = new Array();

    for (var i=0; i<360; i+=0.2) {
        this.sa.push(Math.sin(i/57.295779505601047));
        this.ca.push(Math.cos(i/57.295779505601047));
    }


    this.sin = function(n) {
        while (n<0) {
            n=n+2*3.141592654;
        }
        n = Math.round((n*286.47889752800523));
        return this.sa[n % (1800)];
    }

    this.cos = function(n) {
        while (n<0) {
            n=n+2*3.141592654;
        }
        n = Math.round((n*286.47889752800523));
        return this.ca[n % (1800)];
    }
}

var mmathi = new MMath();

function vector(ix, iy, iz) {
    if (ix == undefined) {
        this.x = 0.0;
        this.y = 0.0;
        this.z = 0.0;
    }
    else {
        this.x = ix;
        this.y = iy;
        this.z = iz;
    }

    this.initialize = function(ix, iy, iz) {
        this.x = ix;
        this.y = iy;
        this.z = iz;
    }

    this.crossProduct = function (i) {
        var v = new vector();

        v.x = (this.y * i.z) - (i.y * this.z);
        v.y = (this.z * i.x) - (i.z * this.x);
        v.z = (this.x * i.y) - (i.x * this.y);

        return v;
    }

    this.dotProduct = function (i) {
        return (this.x * i.x) + (this.y * i.y) + (this.z * i.z);
    }

    this.normalize = function() {
        var v = new vector();

        var m = this.magnitude();

        v.x = this.x / m;
        v.y = this.y / m;
        v.z = this.z / m;

        return v;
    }

    this.subtract = function(i) {
        var v = new vector();
        v.x = (this.x - i.x);
        v.y = (this.y - i.y);
        v.z = (this.z - i.z);

        return v;
    }

    this.magnitude = function() {
        return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
    }

    this.toString = function() {
        return "vector("+this.x+","+this.y+","+this.z+")";
    }

    this.sinX = function() {
        if (this.sinXc == undefined) {
            this.sinXc = mmathi.sin(this.x);
        }
        return this.sinXc;
    }

    this.sinY = function() {
        if (this.sinYc == undefined) {
            this.sinYc = mmathi.sin(this.y);
        }
        return this.sinYc;
    }

    this.sinZ = function() {
        if (this.sinZc == undefined) {
            this.sinZc = mmathi.sin(this.z);
        }
        return this.sinZc;
    }

    this.cosX = function() {
        if (this.cosXc == undefined) {
            this.cosXc = mmathi.cos(this.x);
        }
        return this.cosXc;
    }

    this.cosY = function() {
        if (this.cosYc == undefined) {
            this.cosYc = mmathi.cos(this.y);
        }
        return this.cosYc;
    }

    this.cosZ = function() {
        if (this.cosZc == undefined) {
            this.cosZc = mmathi.cos(this.z);
        }
        return this.cosZc;
    }
};

function color(ir, ig, ib, ia) {
    this.r = ir;
    this.g = ig;
    this.b = ib;
    this.a = ia;

    /* Create a shade based on a factor f that is color * factor, so between 0.0 and 1.0, 1.0 being 100% original, 0.0 being 0% - i.e. black */
    this.shade = function(f) {
        return new color(this.r * f, this.g * f, this.b * f, this.a);
    }

    /* Create a tint based on a factor f that is (100 - color) * f + color, so 0.0 is original, and 1.0 is white. */
    this.tint = function(f) {
        var nr = (1.0 - this.r) * f;
        nr = nr + this.r;

        var ng = (1.0 - this.g) * f;
        ng = ng + this.g;

        var nb = (1.0 - this.b) * f;
        nb = nb + this.b;

        return new color(nr, ng, nb, this.a);
    }

    this.html = function() {
        var total = Math.round(this.r*255) << 16;
        total += Math.round(this.g*255) << 8;
        total += Math.round(this.b*255);

        var s = total.toString(16);

        if (s.length<6) {
            var n = s.length;
            for (i=0; i<(6-n); i++) {
                s="0"+s;
            }
        }

        return "#"+s;
    }

    this.rgba = function() {
        return "rgba("+this.r+","+this.g+","+this.b+","+this.a+")";
    }
}

function triangle(iv1, iv2, iv3, icolor, ignoreWinding) {
    // we could put code in here to force positive winding, but that's actually kind of tricky - I think we need to do at create
    this.v1 = iv1;
    this.v2 = iv2;
    this.v3 = iv3;
    if (ignoreWinding == undefined) {
        this.iWinding=0;
    }
    else {
        this.iWinding = ignoreWinding;
    }


    /* Clone here maybe, pretty sure javascript passes by ref for objects */
    this.color = icolor;

    this.surfaceNormal = function() {
        if (this.surfaceNormalc == undefined) {
            var vert1 = this.v2.subtract(this.v1);
            var vert2 = this.v3.subtract(this.v1);

            this.surfaceNormalc = vert1.crossProduct(vert2);
        }

        return this.surfaceNormalc;
    }

    this.minZ = function() {
        if (this.minZc == undefined) {
            var minZ=this.v1.z;
            if (this.v2.z<minZ) {minZ=this.v2.z}
            if (this.v3.z<minZ) {minZ=this.v3.z}

            this.minZc = minZ;
        }

        return this.minZc;
    }

    this.maxZ = function() {
        if (this.maxZc == undefined) {
            var maxZ=this.v1.z;
            if (this.v2.z>maxZ) {maxZ=this.v2.z}
            if (this.v3.z>maxZ) {maxZ=this.v3.z}

            this.maxZc = maxZ;
        }

        return this.maxZc;
    }

    this.minX = function() {
        if (this.minXc == undefined) {
            var minZ=this.v1.x;
            if (this.v2.x<minZ) {minZ=this.v2.x}
            if (this.v3.x<minZ) {minZ=this.v3.x}

            this.minXc = minZ;
        }

        return this.minXc;
    }

    this.maxX = function() {
        if (this.maxXc == undefined) {
            var maxZ=this.v1.x;
            if (this.v2.x>maxZ) {maxZ=this.v2.x}
            if (this.v3.x>maxZ) {maxZ=this.v3.x}

            this.maxXc = maxZ;
        }

        return this.maxXc;
    }

    this.minY = function() {
        if (this.minYc == undefined) {
            var minZ=this.v1.y;
            if (this.v2.y<minZ) {minZ=this.v2.y}
            if (this.v3.y<minZ) {minZ=this.v3.y}

            this.minYc = minZ;
        }

        return this.minYc;
    }

    this.maxY = function() {
        if (this.maxYc == undefined) {
            var maxZ=this.v1.y;
            if (this.v2.y>maxZ) {maxZ=this.v2.y}
            if (this.v3.y>maxZ) {maxZ=this.v3.y}

            this.maxYc = maxZ;
        }

        return this.maxYc;
    }

    this.midZ = function() {
        if (this.midZc == undefined) {
            this.midZc = this.minZ()+(this.maxZ()-this.minZ())/2;
        }

        return this.midZc;
    }

    this.midX = function() {
        if (this.midXc == undefined) {
            this.midXc = this.minX()+(this.maxX()-this.minX())/2;
        }

        return this.midXc;
    }

    this.midY = function() {
        if (this.midYc == undefined) {
            this.midYc = this.minY()+(this.maxY()-this.minY())/2;
        }

        return this.midYc;
    }
}

function space3d() {
    this.projectType="Simple";
    this.cameraRotation = new vector(0,0,0);
    this.cameraLocation = new vector(0,0,-70);
    this.modelRotation=new vector(0,0,0);
    this.removed=0;

    this.lightv = new vector(1,0,1);

    this.e = new Array();
    this.e.z=500;

    /** Takes one argument of type vector to project into 2D space */
    this.applyCamera= function(v) {
        var r = this.modelRotation;
        var c = this.cameraLocation;

        var srx = mmathi.sin(r.x);
        var sry = mmathi.sin(r.y);
        var srz = mmathi.sin(r.z);

        var crx = mmathi.cos(r.x);
        var crz = mmathi.cos(r.z);
        var cry = mmathi.cos(r.y);

        // Rotate about x
        var dx = (v.x);
        var dy = crx * (v.y) - srx * (v.z);
        var dz = srx * (v.y) + crx * (v.z);

        // Rotate about y
        var ndx = cry * dx + sry * dz;
        var ndy = dy;
        var ndz = cry * dz - sry * dx;

        // Rotate about z
        dx = crz * ndx - srz * ndy;
        dy = srz * ndx + crz * ndy;
        dz = ndz;

        /*
        r = this.cameraRotation;

        srx = mmathi.sin(r.x);
        sry = mmathi.sin(r.y);
        srz = mmathi.sin(r.z);

        crx = mmathi.cos(r.x);
        crz = mmathi.cos(r.z);
        cry = mmathi.cos(r.y);

        var deltx = (ndx - c.x);
        var delty = (ndy - c.y);
        var deltz = (ndz - c.z);

        // Rotate about x
        dx = deltx;
        dy = crx * delty - srx * deltz;
        dz = srx * delty + crx * deltz;

        // Rotate about y
        ndx = cry * dx + sry * dz;
        ndy = dy;
        ndz = cry * dz - sry * dx;

        // Rotate about z
        dx = crz * ndx - srz * ndy;
        dy = srz * ndx + crz * ndy;
        dz = ndz;

         */

        dx = dx - c.x;
        dy = dy - c.y;
        dz = dz - c.z;

        return new vector(dx, dy, dz);
    }

    this.project = function(v) {
        if (v.z>0) {
            return [v.x*(this.e.z/v.z), v.y*(this.e.z/v.z)];
        }
        else {
            return null;
        }
    }
}

function render(canvas, data, s) {
    var c = canvas.getContext('2d');
    var w = canvas.width;
    var h = canvas.height;
    /*var w = w.substring(0, w.length-2);
    var h = h.substring(0, h.length-2);*/
    var origin = [w/2, h/2];

    var tra = new Array();

    c.fillStyle="#000000";
    c.fillRect(0,0,w,h);

    c.strokeStyle="#00ff00";

    for (var i = 0; i < data.length; i++) {
        e = data[i];
        var t = new triangle(
                s.applyCamera(e.v1),
                s.applyCamera(e.v2),
                s.applyCamera(e.v3),
                //new vector(e[0][0], e[0][1], e[0][2]),
                //new vector(e[1][0], e[1][1], e[1][2]),
                //new vector(e[2][0], e[2][1], e[2][2]),
                e.color, e.iWinding
                );

        if (t.minZ()>0 && (t.surfaceNormal().normalize().z<0.10 || t.iWinding)) {
            tra.push(t);
        }
    }

    ca = s.cameraLocation;

    tra.sort(function(a,b) {
        return -((a.midZ()-ca.z)*(a.midZ()-ca.z) + (a.midX()-ca.x)*(a.midX()-ca.x) + (a.midY()-ca.y)*(a.midY()-ca.y)) + ((b.midX()-ca.x)*(b.midX()-ca.x) + (b.midY()-ca.y)*(b.midY()-ca.y) + (b.midZ()-ca.z) * (b.midZ()-ca.z));
    });

    for (var i = 0; i < tra.length; i++) {
        var v1 = tra[i].v1;
        var v2 = tra[i].v2;
        var v3 = tra[i].v3;

        var p1 = s.project(v1);
        var p2 = s.project(v2);
        var p3 = s.project(v3);

        if (p1!=null && p2!=null && p3!=null) {
            li = (tra[i].surfaceNormal().normalize().dotProduct(s.lightv.normalize()));

            if (1) {
                // Apply ambient
                li = (0.7*Math.abs(li))+0.3;

                n = tra[i].color.shade(Math.abs(li)).html();

                c.fillStyle=n;
                c.strokeStyle=n;

                c.beginPath();
                c.moveTo((origin[0]+p1[0]), (origin[1]-p1[1]));
                c.lineTo((origin[0]+p2[0]), (origin[1]-p2[1]));
                c.lineTo((origin[0]+p3[0]), (origin[1]-p3[1]));
                c.lineTo((origin[0]+p1[0]), (origin[1]-p1[1]));
                c.fill();
					 c.stroke();
            }
            else {
                s.removed+=1;
            }

        }
    }

    /*
    for (var i =0; i<tra.length; i++) {
        var v1 = tra[i].v1;
        var p1 = s.project(v1);

        c.fileStyle="#ffffff";
        c.fillText(""+tra[i].minZ(), origin[0]+p1[0], origin[1]+p1[1]);
    }
    */

    return s;

}


