import java.util.Random;

public class Spinny extends Repeater {
    public double len = 0.;
    public double lalph = 0.,ralph = 0.,
        last_lalph = 0., last_ralph = 0.;
    public static double DRAG = 0.91;


    Spinny l_,r_;
    // don't actually need to keep pointers to 
    // all of these - spelling it out for clarity
    // (note: spinny/transform trees together form a metagraph: whole/detail)
    RTPolyhedron sphere_, tubeL_, tubeR_;
    Scaler sphereScaler_, tubeScaleL_, tubeScaleR_;
    Rotator turnTuL_,turnTuR_;
    Translator dispTuL_,dispTuR_;
    Scaler lefscal_, righscal_;
    Rotator lefrot_, righrot_;
    Translator lefxlat_,righxlat_;
    int level_;
    static Random rnd_ = new Random();
    private static Vector3D temp_ = new Vector3D();
    static final double DentSize = .1,
        OneOverRt2 = 1./Math.sqrt(2.);
    final double bulbSize_;

    Spinny(int level) {
        level_ = level;
        int detail = Math.max(8-level,4);
        // construct and throw out zbuf-style polyhedra to create rt-style ones
        sphere_ = new RTPolyhedron(new Sphere(detail,detail));
        tubeL_ = new RTPolyhedron(new Tube(detail));
        tubeR_ = new RTPolyhedron(new Tube(detail));
        children_ = new Transformed[5];
        bulbSize_ = .45*(level%2 != 0 ? OneOverRt2 : 1.);
        sphereScaler_ = new Scaler(temp_.set(bulbSize_,bulbSize_,bulbSize_),sphere_);
        tubeScaleL_ = new Scaler(tubeL_);
        tubeScaleR_ = new Scaler(tubeR_);
        turnTuL_ = new Rotator(temp_.set(0,Math.PI/2,0),tubeScaleL_);
        turnTuR_ = new Rotator(temp_.set(0,Math.PI/2,0),tubeScaleR_);
        dispTuL_ = new Translator(turnTuL_);
        dispTuR_ = new Translator(turnTuR_);
        sphere_.matteColor_.set(.1,.1,.1);
        sphere_.matteColor_.set(rnd_.nextInt(3),.9);
        sphere_.transpColor_.copy(sphere_.matteColor_);
        sphere_.mattePortion_ = 0.05;
        sphere_.refractPortion_ = .9;
        sphere_.reflectPortion_ = 0.05;
        sphere_.refractionIndex_ = 1.5;
        sphere_.glareColor_.set(.1,.1,.1);


        tubeL_.matteColor_.set(0,0,0);
        tubeL_.mattePortion_ = 0.9;
        tubeL_.reflectPortion_ = 0.1;
        tubeL_.glareColor_.set(.05,.05,.05);
        tubeR_.matteColor_.set(0,0,0);
        tubeR_.mattePortion_ = 0.9;
        tubeR_.reflectPortion_ = 0.1;
        tubeR_.glareColor_.set(.05,.05,.05);
        
        
        children_[0] = sphereScaler_;
        children_[1] = dispTuL_;
        children_[2] = dispTuR_;
        grow(0);
    }
    public void expand(int n, boolean lonly) {
        if(n==0)
            return;
        else if(l_==null) {
            l_ = new Spinny(level_+1);
            if(!lonly)
                r_ = new Spinny(level_+1);
            if(level_%2 != 0) 
                temp_.set(0.5,0.5,0.5);
            else
                temp_.set(1.,1.,1.);            
            lefscal_ = new Scaler(temp_,l_);
            if(!lonly)
                righscal_ = new Scaler(temp_,r_);
            lefrot_ = new Rotator(temp_.set(0,0,Math.PI/2.),lefscal_);
            if(!lonly)
                righrot_ = new Rotator(temp_.set(0,0,-Math.PI/2.),righscal_);
            lefxlat_ = new Translator(lefrot_); // scale set by grow
            if(!lonly)
                righxlat_ = new Translator(righrot_);
            children_[3] = lefxlat_;
            if(!lonly)
                children_[4] = righxlat_;
                        
            grow(0);
        }
        l_.expand(n-1);
        if(r_!=null)
            r_.expand(n-1);
    }
    public void expand(int n) {
        expand(n,false);
    }
    public void contract(int n) {
        if(l_!=null)
            l_.contract(Math.max(n-1,0));
        if(r_!=null)
            r_.contract(Math.max(n-1,0));
        if(n==0) 
            l_ = r_ = null;
    }   
    private void grow(double dt) {
        len += dt/20.;
        if(len>1.)
            len = 1.;
        //System.out.println("grow l "+l_+" r "+r_);
        if(l_!=null)
            lefxlat_.change(temp_.set(-len,0,0));
        if(r_!=null)
            righxlat_.change(temp_.set(len,0,0));
        double tubedisp = (len-((1-DentSize)*OneOverRt2-1+DentSize)*bulbSize_)/2.,
            tubelen = len-(1-DentSize+(1-DentSize)*OneOverRt2)*bulbSize_;
        if(tubelen<0)
            tubelen = 0;
        dispTuL_.change(temp_.set(-tubedisp,0.,.0));
        dispTuR_.change(temp_.set(tubedisp,0.,.0));
        tubeScaleL_.change(temp_.set(1/16.,1/16.,tubelen));
        tubeScaleR_.change(temp_.set(1/16.,1/16.,tubelen));
    }
    public int register(RTShape shapes[], int n) {
        shapes[n++] = sphere_;
        shapes[n++] = tubeL_;
        shapes[n++] = tubeR_;
        if(l_!=null)
            n = l_.register(shapes,n);
        if(r_!=null)
            n = r_.register(shapes,n);
        return n;
    }
    private void spin() {
        if(l_!=null) {
            double dla = lalph - last_lalph;
            dla *= DRAG;
            last_lalph = lalph;
            lalph += dla;
            lefrot_.change(temp_.set(0,lalph,-Math.PI/2.));
        }
        if(r_!=null) {
            double dra = ralph - last_ralph;
            dra *= DRAG;
            last_ralph = ralph;
            ralph += dra;
            righrot_.change(temp_.set(0,ralph,-Math.PI/2.));
        }
    }
    public void go(double t, double dt) {
        grow(dt);
        spin();
        if(l_!=null)
            l_.go(t,dt);
        if(r_!=null)
            r_.go(t,dt);
    }
    static final double EPS=0.001;
    static double accelerandom(double dl) {
        if(dl<-EPS)
            return .3 + rnd_.nextGaussian()/6.;
        else if(dl>EPS)
            return -.3 - rnd_.nextGaussian()/6.;
        else
            return rnd_.nextGaussian()/3.;
    }
    public void zounds() {
        if(l_!=null) {
            System.out.println("last last "+last_lalph+" lalph "+lalph);
            last_lalph += accelerandom(lalph-last_lalph);
            System.out.println("next last "+last_lalph);
            l_.zounds();
        }
        if(r_!=null) {
            last_ralph += accelerandom(ralph-last_ralph);
            r_.zounds();
        }
    }
}
