
import java.awt.Graphics;
import java.awt.FontMetrics;
import java.awt.Canvas;
import java.awt.Frame;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Button;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Event;
import java.awt.Font;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.List;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 * La klaso "Padoj" realigas programeton, sed posedas ankaux
 * metodon "main", tiel ke gxi kapablas plenumigxi ankaux ekster
 * HTML-pagxo.  Gxi kreas objekton de la klaso "PadaKadro",
 * en kiu eblas ludi la ludon de "vorto-padoj" tra liter-krado
 * de grandeco 4x4.
 * Jenaj dimensioj tauxgas por la programeto:
 * <PRE>
 * &lt;APPLET CODE="Padoj.class" WIDTH=520 HEIGHT=320&gt;&lt;/APPLET&gt;
 * </PRE>
 *
 * Bedauxrinde mi ne konas UNIKOD-an tiparon kiu enhavas
 * Esperanto-literojn.  Tial mi uzas minusklajn literojn
 * (cghjsu) anstataux la supersignitaj. En la klasoj "Krado"
 * kaj "Vorto" mi desegnas la supersignojn "mane", sed en la
 * listoj ne.
 *
 * @author Reinhard Foessmeier, reinhard.foessmeier@ixos.de
 */
public class Padoj extends java.applet.Applet {
    static private java.applet.Applet miLaApleto = null;
    static private PadaKadro laKadro = null;
    static final String literaro = new String(
           "AAAAAAAABBBBCCCCccDDEEEEEEEEEFFFGGGggHhIIIIIIJJjjKKKKK" +
           "LLLMMNNNNOOOOOOOOPPRRRRRRSSSSSSSSsTTTTTUUUUUuVVZZ");
    static final String vortarnomo = "radikaro.txt";

    /**
     * "main" malfermas la vortaron en dosiero kaj kreas objekton "PadaKadro"
     * @param argv (estas ignorata)
     * @see PadaKadro
     */
    public static void main( String argv[] ) {
        InputStream en = null;
        try {
            en = new FileInputStream(vortarnomo);
        } catch( FileNotFoundException ign ) {
            System.out.println( vortarnomo + " ne trovita." );
        }
        Frame laFenestro = new Frame();
        laFenestro.setTitle("Pada kvizo");
        laKadro = new PadaKadro( laFenestro, literaro, en );
        laFenestro.resize(500, 340);
        laFenestro.show();
    }

    /**
     * "init" malfermas la vortaron tra URL kaj kreas objekton
     * "PadaKadro"
     * @see PadaKadro
     */
    public void init() {
        miLaApleto = this;

        InputStream en = null;
        try {
            URL enUrl = new URL( getCodeBase(), vortarnomo );
            try {
                en = enUrl.openStream();
            } catch( IOException e ) {
                String m = "ne povas malfermi konekton: " + e.getMessage();
                System.out.println( m );
                showStatus( m );
            }
        } catch( MalformedURLException e ) {
            System.out.println("URLo ne en ordo: " + e.getMessage());
        }

        if( laKadro != null ) {  // se jam okazis ludo,
            laKadro.visxu();     // detruu gxian kadron
            laKadro = null;
        }

        laKadro = new PadaKadro( this, literaro, en );
    }

    static void finuLaProgramon() {
        if( miLaApleto != null ) {
            miLaApleto.init();   // ekigu plian ludon
        }
        else {
            System.exit(0);
        }
    }
    /**
     * La uzanto trovis vorton, kiu ne estas en la vortaro.
     * Se ni estas programo, ni storas la vorton en la dosiero "novaj.txt".
     * Se ni estas programeto (applet), ni provas sendi gxin al la servilo.
     * @param vorto la nova vorto
     */
    public static void novaVorto( String vorto ) {
        if( miLaApleto != null ) {
            try {
                char k = 0;
                for(int i= 0; i < vorto.length(); i++) {
                    k += vorto.charAt(i);
                }
                k = (char) ('A'+(k%26));
                URL miaURL = miLaApleto.getCodeBase();
                URL respondaURL = new URL( miaURL.getProtocol(), miaURL.getHost(),
                                       "/cgi-bin/refo/novavorto?"+vorto+k);
                Object nenio = respondaURL.getContent();
            } catch( MalformedURLException mfue ) {
            } catch( IOException ioe ) {   // nenio farebla
            } catch( Exception e ) {
            }
        }
        else {
            try {
                RandomAccessFile d = new RandomAccessFile( "novaj.txt", "rw" );
                d.seek( d.length() );
                d.writeBytes( vorto + "\r\n" );
            }
            catch( IOException e ) {
                System.out.println( "problemo: " + e.getMessage() );
            }
        }
    }
}


/**
 * La klaso "PadaKadro" realigas la ludon.
 * Gxi kreas la liter-kradon (laKrado) kaj krome
 * kelkajn manipulilojn kaj tri listojn:
 * - por la trovitaj vortoj, kiuj estas en la vortaro
 * - por la kromaj trovitaj vortoj, kiuj ne estas en la vortaro
 * - por la netrovitaj vortoj (fine de la ludo)
 */
class PadaKadro {
    // la grafikaj elementoj:
    private java.awt.Container laFenestro;
    protected Krado laKrado = null;
    protected Vorto laVorto = null;
    protected Komandaro laButonoj = null;
    protected List listoTrovitajVortoj = new List();
    protected List listoNetrovitajVortoj = new List();
    protected List listoNovajVortoj = new List();

    protected int cx, cy;
    protected int nx = 4, ny = 4;
    final Color griza = new Color( 224, 224, 224 );
    protected Hashtable eblajVortoj = new Hashtable();
    private int nTrovitajVortoj;
    protected char[][] lit = new char[nx][ny];
    protected boolean[][] uzita = new boolean[nx][ny];
    protected String teksto;
    static Font f = null;
    protected final int minLongeco = 3;  // plej mallonga akceptebla vorto

    /**
     * @param   fen        la fenestro por vidigi la ludon
     * @param   literaro   la uzenda alfabeto, kun diversaj oftecoj
     * @param   en         la fonto por la vortaro
     */
    PadaKadro(
        java.awt.Container fen,
        String literaro,
        InputStream en ) {

        laFenestro = fen;
        f = new Font("Courier", Font.BOLD, 16);
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        laFenestro.setLayout( gridbag );
        cx = laFenestro.size().width;
        cy = laFenestro.size().height;
        if( cx < 160 ) cx = 160;
        if( cy < 160 ) cy = 160;

        int ll = literaro.length();
        for( int x= 0; x < nx; x++ ) {
//            lit[x] = new String[ny];
//            uzita[x] = new boolean[ny];
            for( int y= 0; y < ny; y++ ) {
                double ghaz = Math.random();
                int haz = (int) (ghaz * ll);
                String s = literaro.substring( haz, haz+1 );
                lit[x][y] = s.charAt(0);
            }
        }
        
        laKrado = new Krado( this );
        laKrado.resize( cx/2, cy*2/5);
        laVorto = new Vorto( this );
        laButonoj = new Komandaro( this );
        laButonoj.resize( cx/2, cy*2/5 );
        Label etiTrovitaj   = new Label( "trovitaj" );
        Label etiNetrovitaj = new Label( "ne trovitaj" );
        Label etiNovaj      = new Label( "novaj");

        c.fill       = GridBagConstraints.BOTH;
        c.anchor     = GridBagConstraints.NORTHWEST;
        c.weightx    = 1.0;
        c.weighty    = 5.0;
        c.gridx      = 0;
        c.gridy      = GridBagConstraints.RELATIVE;
        gridbag.setConstraints(laKrado, c);
        laFenestro.add(laKrado);

        c.weighty    = 2.0;
        gridbag.setConstraints(laVorto, c);
        laFenestro.add(laVorto);

        c.weighty    = 2.0;
        gridbag.setConstraints(laButonoj, c);
        laFenestro.add(laButonoj);

        c.gridx      = GridBagConstraints.RELATIVE;
        c.gridy      = 0;
        c.gridwidth  = 1;
        c.gridheight = 3;
        c.weightx    = 0.7;
        c.weighty    = 3.0;
        gridbag.setConstraints(listoTrovitajVortoj, c);
        laFenestro.add(listoTrovitajVortoj);
        gridbag.setConstraints(listoNovajVortoj, c);
        laFenestro.add(listoNovajVortoj);
        gridbag.setConstraints(listoNetrovitajVortoj, c);
        laFenestro.add(listoNetrovitajVortoj);

        listoTrovitajVortoj.resize(80,120);

        gridbag.layoutContainer( laFenestro );

        laButonoj.aktiviguAldonu( false );

        nTrovitajVortoj = 0;
        eblajVortoj.clear();
        listoTrovitajVortoj.clear();
        if( en != null ) {
            DataInputStream den = new DataInputStream( 
                                  new BufferedInputStream( en ) );
            try {
                while( (teksto= den.readLine()) != null ) {
                    if( akceptebla() ) {
                        eblajVortoj.put(teksto, new Boolean(false) );
                    }
                }
                den.close();
                den = null;
            } catch( IOException e ) {
                System.out.println("Escepto dum legado: " + e.getMessage());
            } finally {
            }
            laButonoj.eblajVortoj( eblajVortoj.size() );
        }
        teksto = "";
        laButonoj.layout();
        laFenestro.layout();
    }
    public int nx() { return nx; }
    public int ny() { return ny; }
    public char lit(int x, int y ) { return lit[x][y]; }

    public void visxu() {
        laButonoj.mesagxu( "mi kreas novan ludon..." );
        laFenestro.removeAll();
        laKrado = null;
        laVorto = null;
        laButonoj = null;
    }

    public static Font prenuTiparon() {
        return f;
    }

    /**
     *  agVisxu() visxas la enigitan vorton
     */
    public void agVisxu() {
        teksto = "";
        laVorto.teksto("");
        laButonoj.aktiviguAldonu( false );
    }

    /**
      * agAldonu() provas akcepti la enigitan vorton:
      */
    public void agAldonu() {
        Boolean jam = (Boolean) eblajVortoj.get( teksto );
        if( jam == null ) {
            java.awt.Container ujo = laFenestro;
            while( ujo != null && ! (ujo instanceof Frame) ) {
                ujo = ujo.getParent();
            }
            NovaVorto nv = new NovaVorto( (Frame) ujo, teksto );
            aldonuAlListo(listoNovajVortoj, teksto);
        }
        else if( jam.booleanValue() ) {
            laButonoj.mesagxu( teksto + " jam estis trovita");
        }
        else {
            eblajVortoj.put(teksto, new Boolean(true) );
            aldonuAlListo(listoTrovitajVortoj, teksto);
            nTrovitajVortoj ++;
            laButonoj.vortoj( nTrovitajVortoj );
        }
        agVisxu();
    }

    /**
      * la ludanto volas fini la ludon
      */
    public void agFinu() {
        if( nTrovitajVortoj < eblajVortoj.size() ) {
            // montru la netrovitajn vortojn:
            try {
                Enumeration cxiuj = eblajVortoj.keys();
                while( true ) {
                    String v = (String) cxiuj.nextElement();
                    Boolean trovita = (Boolean) eblajVortoj.get( v );
                    if( ! trovita.booleanValue() )
                        listoNetrovitajVortoj.addItem( v );
                }
            } catch( NoSuchElementException e ) {
            }
            eblajVortoj.clear();
        }
        else {
            Padoj.finuLaProgramon();
        }
    }

    /**
     * aldonu vorton al listo
     */
    private void aldonuAlListo( List l, String vorto ) {
        int i;
        for( i= 0; i < l.countItems(); i++) {
            if( l.getItem(i).compareTo(vorto) > 0 )
                break;
        }
        l.addItem( vorto, i );
    }

    /**
     * nova litero estis enigita;
     * testu, cxu gxi estas geometrie ebla
     * @param n   la nova litero
     * @return    la akceptebleco de la vorto
     */
    boolean novaLitero( char n ) {
        boolean trovita = true;
        teksto += n;
        if( akceptebla() ) {
            laVorto.teksto( teksto );
            laVorto.repaint();
            laButonoj.aktiviguAldonu( teksto.length() >= minLongeco );
            laButonoj.mesagxu("");
        }
        else {
            teksto = teksto.substring( 0, teksto.length()-1 );
            laButonoj.mesagxu("ne ebla");
        }

        return trovita;
    }

    /**
     * cxu la aktuala "vorto" estas geometrie ebla?
     * @return cxu la vorto estas akceptebla
     */
    private boolean akceptebla() {
        for( int x= 0; x < nx; x++ ) {
            for( int y= 0; y < ny; y++ ) {
                uzita[x][y] = false;
            }
        }
        return akceptebla( 0, 0, nx-1, 0, ny-1 );
    }

    /**
     *  cxu la resto de la aktuala vorto (ek de pozicio i0)
     *  estas geometrie ebla, se la sekva pozicio devas esti
     *  inter la koordinatoj [x0..x1] / [y0..y1]?
     *  @param i0   la indico de la unua testenda litero
     *  @param x0   la maldekstra limo de la permesata areo
     *  @param x1   la dekstra limo de la permesata areo
     *  @param y0   la supra limo de la permesata areo
     *  @param y1   la malsupra limo de la permesata areo
     */
    private boolean akceptebla( int i0, int x0, int x1, int y0, int y1 ) {
        boolean trovita = false;
        if( i0 == teksto.length() ) { // fino atingita
            trovita = true;
            return trovita;
        }
        char ti = teksto.charAt(i0);
        if( x0 <  0  ) x0 = 0;
        if( x1 >= nx ) x1 = nx-1;
        if( y0 <  0  ) y0 = 0;
        if( y1 >= ny ) y1 = ny-1;

        sercxu:
        for( int x= x0; x <= x1; x++ ) {
            for( int y = y0; y <= y1; y++ ) {
                if( lit[x][y] == ti && ! uzita[x][y] ) {
                    uzita[x][y] = true;
                    trovita = akceptebla( i0+1, x-1, x+1, y-1, y+1 );
                    uzita[x][y] = false;
                    if( trovita ) break sercxu;
                }
            }
        }
        return trovita;
    }
}

/**
 * la klaso "Komandaro" enhavas kelkajn butonojn kaj krome
 * etikedojn por montri diversajn informojn.
 */
class Komandaro extends Panel {
    static final String etiAldonu = "Aldonu";
    static final String etiVisxu  = "Visxu";
    static final String etiFinu   = "Finu";
    private int nEblajPoentoj;
    PadaKadro patro = null;
    Label laMesagxo = new Label();
    Label laPoentoj = new Label();
    Label eblajPoentoj = new Label();
    Button butAldonu = new Button( etiAldonu );
    Button butVisxu  = new Button( etiVisxu  );
    Button butFinu   = new Button( etiFinu   );

    Komandaro( PadaKadro p ) {
        patro = p;
        laMesagxo.setForeground( Color.red );

        setLayout( new GridLayout( 6, 1 ) );
        add(laMesagxo);
        add(laPoentoj);
        add(eblajPoentoj);
        add(butAldonu);
        add(butVisxu);
        add(butFinu);
    }
    public boolean action( Event ev, Object ob ) {
        if( ev.target instanceof Button ) {
            if( ev.target == butVisxu  ) patro.agVisxu();
            if( ev.target == butAldonu ) patro.agAldonu();
            if( ev.target == butFinu   ) patro.agFinu();
        }
        return true;
    }
    /**
     * (mal)aktivigu la butonon "Aldonu".
     * la butono "Aldonu" estas aktiva nur, sed la enigita vorto
     * estas suficxe longa.
     */
    public void aktiviguAldonu( boolean cxu ) {
        butAldonu.enable( cxu );
    }
    public void mesagxu( String t ) {
        laMesagxo.setText( t );
    }
    public void vortoj( int n ) {
        float procentoj = n*100.0F/nEblajPoentoj;
        if( n == 1 )
            laPoentoj.setText("1 vorto trovita (" + procentoj + " %)");
        else
            laPoentoj.setText(n + " vortoj trovitaj (" + procentoj + " %)");
    }
    public void eblajVortoj( int n ) {
        nEblajPoentoj = n;
        eblajPoentoj.setText(n == 0 ? "Mi ne trovis vorton."
                                    : "Almenaux " + n + " vortoj troveblaj");
    }
}

/**
 * klaso "Krado": la liter-krado de dimensio 4x4
 */
class Krado extends java.awt.Canvas {
    int cx, cy;
    int xMus, yMus;
    Font tiparo;
    PadaKadro patro = null;

    Krado( PadaKadro p ) {
        patro = p;
        tiparo= new Font("Courier", Font.BOLD, 20);
    }

    /**
     * Desegnu kvadrateton kun litero, en la formo de butono.
     * Bedauxrinde estas tre komplike desegni supersignon en
     * Java-butono, tial mi preferis mem realigi butonojn.
     * Ili ne funkcias precize kiel la veraj: se la muso
     * foriras el butono antaux laso de musbutono,
     * tio ne nuligas la alklakon!
     * @param g      la grafika kunteksto por desegni
     * @param x      la numero de la vertikalo (kolumno) por la butono
     * @param y      la numero de la horizontalo (linio) por la butono
     * @param prem   cxu la butono aspektu premita
     */
    public void desegnuButonon( Graphics g, int x, int y, boolean prem ) {
        cx = size().width;
        cy = size().height;
        int nx = patro.nx();
        int ny = patro.ny();
        g.setFont( tiparo );
        FontMetrics fm = getFontMetrics( tiparo );

        int x0 = cx *  x    / nx;
        int x1 = cx * (x+1) / nx - 1;
        int y0 = cy *  y    / ny;
        int y1 = cy * (y+1) / ny - 1;
        g.clearRect( x0, y0, x1-x0, y1-y0 );

        int px = (x0 + x1)/2 - fm.charWidth( patro.lit(x,y) ) / 2;
        int py = (y0 + y1)/2 + fm.getMaxDescent();
        if( prem ) {
            px ++;
            py ++;
        }

        char c[] = new char[1];
        c[0] = patro.lit(x,y);
        if( Character.isLowerCase( c[0] ) ) {
            if( c[0] == 'u' )
                g.drawArc( px, py - fm.getMaxAscent()*8/5,
                           fm.charWidth('U'), fm.charWidth('U'), 230, 86 );
            else
                g.drawString("^", px, py - fm.getMaxDescent()*2/3);
            c[0] = Character.toUpperCase( c[0] );
        }
        g.drawChars( c, 0, 1, px, py );

        // draw3DRect() aspektas malbone en Java 1.0, do faru mem:
        g.setColor( prem ? Color.black : Color.white );
        g.drawLine( x0, y0, x0, y1 );
        g.drawLine( x0, y0, x1, y0 );
        g.setColor( prem ? Color.white : Color.black );
        g.drawLine( x0, y1, x1, y1 );
        g.drawLine( x1, y0, x1, y1);
    }

    public void paint( Graphics g ) {
        cx = size().width;
        cy = size().height;
        int nx = patro.nx();
        int ny = patro.ny();
        g.setFont( tiparo );

        for( int x= 0; x < nx; x++ ) {
            for( int y= 0; y < ny; y++ ) {
                desegnuButonon( g, x, y, false );
            }
        }
    }

    public boolean mouseDown(Event evt,
                           int x,
                           int y)
    {
        xMus = x * patro.nx() / cx;
        yMus = y * patro.ny() / cy;
        if( xMus < 0 ) xMus = 0;
        if( yMus < 0 ) yMus = 0;
        if( xMus >= patro.nx() ) xMus = patro.nx()-1;

        desegnuButonon( getGraphics(), xMus, yMus, true );
        return true;
    }

    public boolean mouseUp(Event evt,
                           int x,
                           int y)
    {
        desegnuButonon( getGraphics(), xMus, yMus, false );
        patro.novaLitero( patro.lit(xMus, yMus) );
        return true;
    }
}

/**
 * "Vorto": la aktuale enigata vorto
 */
class Vorto extends java.awt.Canvas {
    int cx, cy;
    PadaKadro patro = null;
    String teksto;

    Vorto( PadaKadro p ) {
        patro = p;
        resize(100,30);
    }
    void teksto( String t ) {
        teksto = t;
        repaint();
    }
    public void paint( Graphics g ) {
        if( teksto != null ) {
            Font tiparo = new Font("Courier", Font.BOLD, 24);
            FontMetrics fm = getFontMetrics( tiparo );
            g.setFont( tiparo );

            char c[] = new char[1];
            int px = 3;
            int py = fm.getMaxAscent() * 4/3;
            for( int i= 0; i < teksto.length(); i++ ) {
                c[0] = teksto.charAt(i);
                if( Character.isLowerCase( c[0] ) ) {
                    if( c[0] == 'u' )
                        g.drawArc( px, py - fm.getMaxAscent()*8/5,
                           fm.charWidth('U'), fm.charWidth('U'), 230, 86 );
                    else
                        g.drawString("^", px, py - fm.getMaxDescent()*2/3);
                    c[0] = Character.toUpperCase( c[0] );
                }
                g.drawChars( c, 0, 1, px, py );
                px += fm.charWidth( c[0] );
            }
        }
        g.draw3DRect( 1, 1, size().width, 30, true );
    }
}


/**
 * "NovaVorto": dialogo por demandi cxu iu vorto estas vera vorto.
 * Bedauxrinde dialogoj funkcias malbone en Java 1.0.
 */
class NovaVorto extends Dialog {
    private Label eti;
    private Button butJes;
    private Button butNe;
    String laVorto;

    public NovaVorto( Frame fen, String vorto ) {
        super( fen, vorto, false );
        laVorto = vorto;

        setLayout( new FlowLayout() );
        eti = new Label( "Cxu \"" + vorto + "\"estas gxusta vorto?" );
        add( eti );

        butJes = new Button( "Jes" );
        butNe  = new Button( "Ne" );
        add( butJes );
        add( butNe );

//        pack();
        show();
        resize(200,120);
    }

    public boolean action( Event ev, Object o ) {
        if( ev.target instanceof Button ) {
            if( ev.target.equals(butJes) ) {
                Padoj.novaVorto( laVorto );
                System.out.println("ni havas novan vorton: " + laVorto);
            }
            this.hide();
            this.dispose();
        }
        return true;
    }
}

