/*
 * VTPaint.java (very tiny paint program (guestbook))
 * (C) 1996,1997,1998 Hiroshi Okada.
 * You can use this source code without permission.
 * last update 98.5.06
 *
 * (compile it JDK 1.0.2)
 */
 

import java.awt.*;
import java.applet.Applet;
import java.util.*;
import java.io.*;
import java.net.*;

// ------------------------------------------------------------------------
//  * Constants
// ------------------------------------------------------------------------
//
interface VTPConst {
    final static int OFFSET = 16;    // offset value to add 
    final static int RADIX = 32;     // read/write data radix
    final static int CODE_PENDOWN = 9999;    // this vaule must not same as other code.
    final static int TOOL_PEN1 = 0;  // tool code 
    final static int TOOL_PEN2 = 1;
    final static int TOOL_DOT1 = 2;
    final static int TOOL_DOT2 = 3;
    final static int NUMBER_OF_TOOLS = 4;

    final static int BASE_WIDTH = 16; // unit size (tool switch & color switch)
    final static int BASE_HIGHT = 16;
    
    final static int DOT1_SIZE = 8;  // painting dot size
    final static int DOT2_SIZE = 12; // painting dot size
    
    final static Color colors[] = {
        Color.black, Color.blue, Color.cyan, Color.darkGray,
        Color.gray, Color.green, Color.lightGray, Color.magenta,
        Color.orange, Color.pink, Color.red, Color.white, Color.yellow };
    
    final static int CANVAS_W = 360; // canvas width & height 
    final static int CANVAS_H = 40;

    final static int HR_W = 360; // Horizontal line width (edit/view area sepalator)
    final static int HR_H = 4;   //                 height
    final static int READ_N = 5; // How many image read

    final static int MAXSTEP = 800;  // max painting steps
    final static String LEVEL_STR = "*****";
    final static String WRITE_CGI = "write.cgi";
    final static String READ_CGI = "read.cgi";
}


// ------------------------------------------------------------------------
//  * color selection switch
// ------------------------------------------------------------------------
//
final class VTPColorSwich extends Canvas implements VTPConst{
    int select = 0;
    
    public VTPColorSwich() {
       resize( colors.length*BASE_WIDTH, BASE_HIGHT);
    }

    // The number of the chosen color is returned
    int getColorNum() {
        return select;
    }
    
    public void paint( Graphics g) {
        // Each fillRect are drawn by each color. 
        for( int i=0; i<colors.length; i++){
            g.setColor( colors[i] );
            g.fillRect( i*BASE_WIDTH,0,BASE_WIDTH,BASE_HIGHT);
        }
        // The chosen color surrounded by black the others surrounded by white.
        for( int i = 0; i<colors.length; i++){
            if( i==select){
                g.setColor( Color.black);
            }else{
                g.setColor( Color.white);
            }
            g.drawRect( i*BASE_WIDTH,0,BASE_WIDTH-1,BASE_HIGHT-1);
        }
    }

    public boolean mouseUp(Event evt, int x, int y) {
        select = x / BASE_WIDTH;
        repaint();
        return false; // pass event
    }
}

// ------------------------------------------------------------------------
// tool selection switch
// ------------------------------------------------------------------------
//
final class VTPToolSwich extends Canvas implements VTPConst{
    VTPCanvas vtpc;
    int select = 0;
    
    public VTPToolSwich() {
       resize( NUMBER_OF_TOOLS * BASE_WIDTH,BASE_HIGHT);
    }
    
    int getToolNum() {
        return select;
    }
    
    public void paint( Graphics g) {
        g.setColor( Color.black );
         
        // PEN1
        g.drawLine( 2,BASE_HIGHT/2,
                    BASE_WIDTH-3,BASE_HIGHT/2);
        
        // PEN2        
        g.drawLine( BASE_WIDTH+2,  BASE_HIGHT/2-1,
                    BASE_WIDTH*2-3,BASE_HIGHT/2-1);
        g.drawLine( BASE_WIDTH+2,  BASE_HIGHT/2,
                    BASE_WIDTH*2-3,BASE_HIGHT/2);
                    
        // DOT1
        g.fillOval( (BASE_WIDTH*5-DOT1_SIZE)/2,
                    (BASE_HIGHT-DOT1_SIZE)/2,
                    DOT1_SIZE,DOT1_SIZE);
        // DOT2
        g.fillOval( (BASE_WIDTH*7-DOT2_SIZE)/2,
                    (BASE_HIGHT-DOT2_SIZE)/2,
                    DOT2_SIZE,DOT2_SIZE);
        
        for( int i = 0; i<NUMBER_OF_TOOLS; i++){
            if( i==select){
                g.setColor( Color.black);
            }else{
                g.setColor( Color.white);
            }
            g.drawRect( i*BASE_WIDTH,0,
                        BASE_WIDTH-1,BASE_HIGHT-1);
        }
    }

    public boolean mouseUp(Event evt, int x, int y) {
        select = x / BASE_WIDTH;
        repaint();
        return false; // pass event
    }
}    

// ------------------------------------------------------------------------
//  painting area
// ------------------------------------------------------------------------
//
final class VTPCanvas extends Canvas implements VTPConst{
    Image offImage = null; 
    Graphics offGraphics;
    int x0,y0;  // line start position
    int colorNum = 0;   // selected color
    int toolNum = 0;    // selected tool
    int maxstep = MAXSTEP;
    Vector trace = new Vector();    // All paths are recorded.
    
    public VTPCanvas() {
        resize( CANVAS_W, CANVAS_H);
        colorNum = 0;
        toolNum = 0;
        x0 = y0 = 0;
    }

    // When a maximum step is made to minus, user can't edit any more.
    public VTPCanvas( int maxstep) {
        this();
        this.maxstep = maxstep;
    }

    // Change path
    void SetVector( Vector v) {
        trace = v;
        reInterpretation();
     }

    // Create & initialize offscreen image
    void offImage_init(){
            offImage = createImage( CANVAS_W, CANVAS_H);
            offGraphics = offImage.getGraphics();
            offGraphics.setColor( Color.white);
            offGraphics.fillRect( 0, 0, CANVAS_W, CANVAS_H);
    }
    
    void setColor( int cnum){
        colorNum = cnum;
    }
    
    void setTool( int t){
        toolNum = t;
    }
    
    void clearAll(){
        trace = new Vector();
        reInterpretation();
    }
    
    void undo1Step(){
        int sv_colorNum = colorNum; // protected from reInterpretation()
        int sv_toolNum = toolNum; // protected from reInterpretation()
        int i = trace.size()-1;
        
        // They are removed to the last CODE_PENDOWN .
        while( (i>=0) 
               && ( ((Integer)trace.elementAt(i)).intValue() 
                    != CODE_PENDOWN 
                  )
             ){
                 trace.removeElementAt( i);
                 i--;
        }
        if( i>=0){
            trace.removeElementAt( i);
        }
        reInterpretation();
        colorNum = sv_colorNum;
        toolNum = sv_toolNum;
    }
    
    public void update( Graphics g) {
        paint( g);
    }
    
    public void paint( Graphics g) {
        if( offImage == null){
                offImage_init();
        }
        g.drawImage( offImage, 0,0, this);
    }
    
    // A line is drawing on offImage
    void draw_line_to( int x, int y){
        if( offImage == null){
                offImage_init();
        }
        offGraphics.setColor( colors[ colorNum]);
        switch( toolNum){
            case TOOL_PEN1 :
                offGraphics.drawLine( x0,y0,x,y);
                break;
            case TOOL_PEN2 :
                offGraphics.drawLine( x0,y0,x,y);
                offGraphics.drawLine( x0+1,y0,x+1,y);
                offGraphics.drawLine( x0,y0+1,x,y+1);
                offGraphics.drawLine( x0+1,y0+1,x+1,y+1);
                break;
            case TOOL_DOT1 :
                offGraphics.fillOval( x0-DOT1_SIZE/2, y0-DOT1_SIZE/2, 
                                      DOT1_SIZE, DOT1_SIZE);
                break;
            case TOOL_DOT2 :
                offGraphics.fillOval( x0-DOT2_SIZE/2, y0-DOT2_SIZE/2, 
                                      DOT2_SIZE, DOT2_SIZE);
                break;
        }
        x0 = x;
        y0 = y;
    }
    
    // Reevaluate "trace".
    void reInterpretation(){
        int x,y,v;
        offImage_init();
        x0 = y0 = 0;
        for (Enumeration e = trace.elements() ; e.hasMoreElements() ;){
            switch( v = ((Integer)e.nextElement()).intValue() ){
                case CODE_PENDOWN :
                    colorNum = ((Integer)e.nextElement()).intValue();
                    toolNum  = ((Integer)e.nextElement()).intValue();
                    x = ((Integer)e.nextElement()).intValue();
                    y = ((Integer)e.nextElement()).intValue();
                    x0 = x; y0=y;
                    draw_line_to( x,y);
                    break;
                default :
                    x = x0 + v;
                    y = y0 + ((Integer)e.nextElement()).intValue();
                    draw_line_to( x, y);
                    break;
            }// end switch
        }// end for
        repaint();
    }
    
    public boolean mouseDown(Event evt, int x, int y){
        if( maxstep <= trace.size()){
            return true;
        } 
        trace.addElement( new Integer( CODE_PENDOWN));
        trace.addElement( new Integer( colorNum));
        trace.addElement( new Integer( toolNum));
        trace.addElement( new Integer( x));
        trace.addElement( new Integer( y));
        x0 = x; y0=y;
        draw_line_to( x,y);
        repaint();
        return true;
    }
    
    public boolean mouseDrag(Event evt, int x, int y){
        if( maxstep <= trace.size()){
            return true;
        } 
        if( Math.abs(x-x0) + Math.abs(y-y0) < 3){
           return true;
        }
        trace.addElement( new Integer(x-x0));
        trace.addElement( new Integer(y-y0));
        draw_line_to( x,y);
        repaint();
        return true;
    }

    Vector getContents(){
        return trace;
    }
}


public final class VTPaint extends Applet implements Runnable,VTPConst{
    VTPCanvas vtpc = new VTPCanvas();
    VTPCanvas vtpc_r[] = new VTPCanvas[ READ_N];
    VTPColorSwich vtpcs =  new VTPColorSwich();
    VTPToolSwich vtpts = new VTPToolSwich();
    Canvas hr= new Canvas();
    Button prevBtn = new Button("<-");
    Button nextBtn = new Button("->");
    Button undobtn = new Button("Undo");
    Button okbtn = new Button("OK");
    Label steplabel = new Label( LEVEL_STR);
    int readPos = 9999;
    
    public void init() {
        add( vtpc );
        add( vtpcs);
        add( vtpts);
        add( undobtn);
        add( okbtn);
        add( steplabel);
        hr.resize( HR_W, HR_H);
        hr.setBackground( Color.black);
        add( hr);
        add( prevBtn);
        add( nextBtn);
        for( int i=0; i<READ_N; i++){
            add( vtpc_r[i] = new VTPCanvas(-1));
        }
    }
    
    // Reading is practice with another thread
    // so that it can draw a picture even during reading. 
    public void start(){
        new Thread(this).start();
    }

    public void run(){
        yomiKaki( null);
    }

    // corner-cutting :-)
    void setSteplabel(){
        int endIndex = ( (MAXSTEP-(vtpc.getContents()).size()) * LEVEL_STR.length() ) / MAXSTEP;
		if( endIndex <0 ){
			endIndex = 0;
		}
        steplabel.setText( LEVEL_STR.substring( 0, endIndex) );
     }

    // integer to string 
    String i2s( int n){
        if( n == CODE_PENDOWN){
            return "*";
        }
        String s = Integer.toString(  OFFSET + n, RADIX);
        if( s.length() > 1){
            s = "/" + s + "/";
        }
        return s;       
    }

    // vector to string
    String v2s( Vector v){
        int i;
        String s = "";
        for (Enumeration e = v.elements() ; e.hasMoreElements() ;){
            i = ((Integer)e.nextElement()).intValue();
            s = s + i2s( i);
        }
        return s;
    }

    // string to vector
    Vector s2v( String s){
        String nums;
        Vector v = new Vector();
        int n;
        for( int i=0; i<s.length(); i++){
            nums = new String();
            if( s.charAt(i) == '/'){
                i++;
                while( s.charAt(i) != '/'){
                    nums += s.charAt(i);
                    i++;
                }
                n = Integer.valueOf( nums, RADIX).intValue() -OFFSET;
            }else if( s.charAt(i) == '*'){
                n = CODE_PENDOWN;
            }else{
                nums += s.charAt(i);
                n = Integer.valueOf( nums, RADIX).intValue() -OFFSET;
            }
            v.addElement(  new Integer(n));
        }
        return v;
    }

    // kakikomi & yomidashi is only calld from this function
    synchronized void yomiKaki( String s)
    {
        if( s == null){
            yomidashi();
        }else{
            kakikomi( s) ;
        }
    }

	// write data & read result ( new data)
    private void kakikomi( String outs) {
        getAppletContext().showStatus("<Sending data>");
        try {
            URL u = new URL(getCodeBase(), WRITE_CGI);
            URLConnection uc = u.openConnection();
            uc.setDoOutput( true); // use POST method
            PrintStream ps = new PrintStream( 
                                    new BufferedOutputStream(
                                           uc.getOutputStream()
                                        )
                                 );
            ps.println( outs );
            ps.close();
            
	        getAppletContext().showStatus("<Sending data...>");
            int i = 0;
            DataInputStream dis = new DataInputStream( 
                                         new BufferedInputStream(
                                                uc.getInputStream()
                                             )
                                      );
            String ins = dis.readLine();
            readPos = Integer.parseInt( ins);
            while( ins != null){
                ins = dis.readLine();
                if( (i<READ_N) && (ins != null)){
                    vtpc_r[i].SetVector(s2v( ins));
                }
                i++;
            }
            dis.close();
			getAppletContext().showStatus("Send OK");
		} catch (IOException e){
            getAppletContext().showStatus("!Error Sending data!");
        }
     }

    // read data
    private void yomidashi() {
        getAppletContext().showStatus("<Receiving data>");
        try {
            URL u = new URL( getCodeBase(), READ_CGI);
            URLConnection uc = u.openConnection();

            uc.setDoOutput( true); // use POST method
            PrintStream ps = new PrintStream( 
                                    new BufferedOutputStream(
                                           uc.getOutputStream()
                                    )
                                 );
            ps.println( readPos);
            ps.close();
            
            int i = 0;
            DataInputStream dis = new DataInputStream( 
                                         new BufferedInputStream(
                                               uc.getInputStream()
                                             )
                                      );
            String ins = dis.readLine();
            readPos = Integer.parseInt( ins);
            while( ins != null){
                ins = dis.readLine();
                if( (i<READ_N) && (ins != null)){
                    vtpc_r[i].SetVector(s2v( ins));
                }
                i++;
            }
            dis.close();
			getAppletContext().showStatus("Receive OK");
        } catch (IOException e){
             getAppletContext().showStatus("!Error Receiving data!");
        }
    }

    public boolean action(Event evt, Object what) {
       setSteplabel();
       if( evt.target == undobtn){
            vtpc.undo1Step();
            return true;
        }else if( evt.target == prevBtn){
            readPos -= READ_N;
            new Thread(this).start();
        }else if( evt.target == nextBtn){
            readPos += READ_N;
            new Thread(this).start();
        }else if( evt.target == okbtn){
            String s = v2s( vtpc.getContents());
            if( s.length() != 0){
                vtpc.SetVector( new Vector());
                repaint();
                yomiKaki(s);
            }
            return true;
        }
        return false;
    }

    public boolean mouseUp(Event evt, int x, int y){
        setSteplabel();
        if( evt.target == vtpcs){
            vtpc.setColor( vtpcs.getColorNum());
            return true;
        }else if( evt.target == vtpts){
            vtpc.setTool( vtpts.getToolNum());
            return true;
        }
        return false;
    }
}