import java.awt.*;

class MyPanel extends Panel {

    Point getOffset() {
        if (p0 != null) return p0;
        Point p0=location();
        Container c=this.getParent();
        if (c instanceof MyPanel) {
            Point p=((MyPanel)c).getOffset();
            p0.translate(p.x, p.y);
        }
        return p0;
    }

    public Dimension preferredSize() {
        return dim;
    }
    void setPreferredSize(Dimension d) { dim=d; }

    protected Dimension dim=new Dimension(40, 40);
    protected Point p0;
}

class IonCompartment extends MyPanel {
    /**
    *   Ions call the contain method of their IonCompartment to see if they should
    *   enter a new compartment or reflect off the walls.  It should be noted that
    *   contain is a verb, and the ion is being contained.  Not to be confused with
    *   the contains() method of Container in JDK 1.1.  This is a template method.
    */
    public void contain(Ion i) {
        if (!compartChange(i) )
			boxIn(i);
        updateField(i);
    }
    boolean compartChange(Ion i) { return false; } //subclasses override this method

    public boolean admit(Ion i) { return true; } //Membrane subclasses override this method

    void boxIn(Ion i) { //Membrane subclass overrides this method
        int x=i.x-location().x;
        int y=i.y-location().y;
        int end;
		if (x<=1) {
			i.x=reflect(x, 2)+location().x;
			i.dx=-i.dx;
		}
		else {
			end = size().width-i.diam;
			if (x>=end) {
				i.x=reflect(x, end-1)+location().x;
				i.dx=-i.dx;
			}
		}
		if (y<=1) {
			i.y=reflect(y, 2)+location().y;
			i.dy=-i.dy;
		}
		else {
			end=size().height-i.diam;
			if (y>=end) {
				i.y=reflect(y, end-1)+location().y;
				i.dy=-i.dy;
			}
		}
    }
    /**
     * Reflects a coordinate (x or y) off a mirror and returns result
     */
    private int reflect(int a, int mirror) {
        return (5*mirror-2*a)/3;    //collision is 2/3 elastic
    }
    static void reset() {
		rho = null;
	}

    synchronized static void updateField(Ion i) {
		if (!fieldInitialized) initField();
		int oldX=i.oldX;
		int newX=i.x;
		int chg=i.charge;
		if (oldX==newX) return;
		rho[oldX] -= chg;
		rho[newX] += chg;
		if (oldX < newX) {
			for (int j=oldX; j<=newX; j++) field[j]-=chg;
			if (oldX==newX-1) return;
			for (int j=oldX+1; j<newX; j++) field[j]-=chg;
			return;
		}
		else {
			for (int j=newX; j<=oldX; j++) field[j]+=chg;
			if (oldX==newX+1) return;
			for (int j=newX+1; j<oldX; j++) field[j]+=chg;
			return;
		}
    }

	private static void initField() {
        int fieldOld=0;
        int rhoOld=rho[0];
        for (int i=1; i<field.length; i++) {
            fieldOld += rhoOld;
            fieldOld += (rhoOld=rho[i]);
            field[i] = fieldOld;
        }
    }

	void addCharge(int x, int chg) {
		if (rho==null) {
			int w=getParent().size().width; //ions MUST be added to a compartment
			rho = new int[w];               //  whose parent is ionPanel
			field = new int[w];
		}
		rho[x]+=chg;
		fieldInitialized=false;
    }

	int getField(int x) {
		while (x > 0) {
			return field[x--];
		}
		return field[0];
	}
    static int[] rho, field;   // charge(x), field(x)
    private static boolean fieldInitialized;
}

class LCompartment extends IonCompartment {
    boolean compartChange(Ion i) {  //overriding parent method
        if (i.x>=location().x+dim.width-i.diam) {
            Component cmp=getParent().locate(i.x, i.y);
// System.out.println("cmp="+cmp+" ion at "+i.x+" "+i.y);
   			if (cmp instanceof Channel)
   				System.out.println("Channel here!");//DEBUG
   			if ( cmp instanceof Membrane) {
   				if ( ((Membrane)cmp).admit(i) ) {
//System.out.println("admitted to memb");
                   	i.setCompartment( (Membrane)cmp );
//                   i.setGraphics( ((Membrane)cmp).getChannel().getGraphics() );
                   	return true;
               	}
            }
        }
        return false;
    }

    void cleanUp() {
		this.setForeground(Color.cyan);
		this.getGraphics().fillRect(0, 0, this.size().width, this.size().height);
	}
}

class RCompartment extends IonCompartment {
    boolean compartChange(Ion i) {  //overriding parent method
        if (i.x<=location().x+1) {
            Component cmp=getParent().locate(i.x, i.y);
            if ( cmp instanceof Membrane) {
                if ( ((Membrane)cmp).admit(i) ) {
                    i.setCompartment( (Membrane)cmp );
//                    i.setGraphics( ((Membrane)cmp).getChannel().getGraphics() );
                    return true;
                }
            }
        }
        return false;
    }
    void cleanUp() {
		this.setForeground(Color.cyan);
		this.getGraphics().fillRect(0, 0, this.size().width, this.size().height);
	}
}

class Membrane extends IonCompartment {
    Membrane(Channel c) {
        super();
        channel=c;
        dim=c.preferredSize();
    }
/*    public void paint(Graphics g) {
        if (channel!=null) {
            Point p=channel.location();
            if (p==null) { initChannel();  p=channel.location(); }
            Dimension d=channel.size();
            g.setColor(Color.cyan);
    System.out.println("painting channel at "+p+" width="+d.width+" height="+d.height);
            g.fillRect(p.x, p.y, d.width, d.height);
        }
    }   */
    boolean compartChange(Ion i) {  //overriding parent method
        int x=i.x-location().x;
        int y=i.y-location().y;
        if ( !(inside(x, y)||inside(x+i.diam, y)) ) {
			Component com=getParent().locate(i.x, i.y);
			if (com instanceof IonCompartment)
				i.setCompartment( (IonCompartment)com );
//			else
//				System.out.println("com="+com+" at x,y="+i.x+","+i.y);
			return true;
        }
        return false;
    }
    public boolean admit(Ion i) {
        if (channel.location()==null)
        	initChannel();
        return channel.admit(i);
    }

    void boxIn(Ion i) {  //overriding parent method
        if (channel.location()==null) initChannel();
        Rectangle r=channel.bounds();
        int yTop=r.y+location().y;
        if (i.y<yTop+1) {
			i.y=yTop+1;
			i.dy=0;
		}
        else {
            int z=yTop+r.height-i.diam;
            if (i.y>z-1) { i.y=z-1; i.dy=0; }
        }
    }

    Channel getChannel() { return channel; }

    private void initChannel() {
        Rectangle r=bounds();
        channel.move(r.x, r.y+(r.height-channel.size().height)/2);
    }

    private Channel channel;
}

class Channel extends MyPanel {
    Channel(Dimension d, String s) { //d.width is channel length, d.height is pore width
        super();
        dim=d;
        try {                       // s is the name of ion that channel will admit
            select=Class.forName(s);
        } catch (ClassNotFoundException e) {}
    }

    boolean admit(Ion i) {
        if (!inside(i))
        	return false;
//System.out.println("ion "+i+" at x,y="+i.x+","+i.y+" seeks admittance");
        if (select==null)
        	return true;
		if (!this.isVisible())
        	return false;
        return (i.getClass() == select);
    }

    boolean inside(Ion i) {
        Point mp = getParent().location();
        int x=i.x-mp.x;
        int y=i.y-mp.y;
        Rectangle r=new Rectangle(location().x-i.diam,
                        location().y, dim.width+i.diam, dim.height-i.diam);
        return (r.inside(x, y) );
    }
    private Class select;
}
