package org.ais_sanmarino.scis.s1.refo.padoj2;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Font;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.LayoutManager;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.text.NumberFormat;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Locale;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

/**
 * 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>
 *
 * @author Reinhard Foessmeier, reinhard@foessmeier.de
 */
public class Padoj extends java.applet.Applet {
    private static java.applet.Applet miLaApleto = null;
    private static PadaKadro laKadro = null;
    private static final String literaro =
           "AAAAAAAABBBBCCCC\u0108\u0108DDEEEEEEEEEFFFGGG\u011c\u011c"
         + "H\u0124IIIIIIJJ\u0134\u0134KKKKK"
         + "LLLMMNNNNOOOOOOOOPPRRRRRRSSSSSSSS\u015cTTTTTUUUUU\u016cVVZZ";
    private 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");
long x = System.currentTimeMillis();
        laKadro = new PadaKadro( laFenestro, literaro, en );
System.out.println("Tempo: " + (System.currentTimeMillis() - x));
        laFenestro.setSize(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());
        }
System.out.println("malfermis...");

        if( laKadro != null ) {  // se jam okazis ludo,
            laKadro.visxu();     // detruu gxian kadron
            laKadro = null;
        }

        laKadro = new PadaKadro( this, literaro, en );
        try {
            en.close();
        } catch (IOException e) {
            System.out.println("eraro dum fermo de radikaro: " + e);
        }
    }

    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 ) {
                System.out.println("MFU: " + mfue);
            } catch( IOException ioe ) {   // nenio farebla
                System.out.println("leg: " + ioe);
            } catch( Exception e ) {
                System.out.println("esc: " + 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;
    private Krado laKrado = null;
    private JLabel laVorto = null;
    private Komandaro laButonoj = null;
    private DefaultListModel modeloTrovitajVortoj = new DefaultListModel();
    private DefaultListModel modeloNetrovitajVortoj = new DefaultListModel();
    private DefaultListModel modeloNovajVortoj = new DefaultListModel();
    private JList listoTrovitajVortoj = new JList(modeloTrovitajVortoj);
    private JList listoNetrovitajVortoj = new JList(modeloNetrovitajVortoj);
    private JList listoNovajVortoj = new JList(modeloNovajVortoj);

    private int cx, cy;
    private static final int NX = 4;
    private static final int NY = 4;
    final Color griza = new Color( 224, 224, 224 );
    private SortedMap eblajVortoj = new TreeMap();
    private int nTrovitajVortoj;
    private char[][] lit = new char[NX][NY];
    private boolean[][] uzita = new boolean[NX][NY];
    private String teksto;
    static final int MIN_LONGECO = 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 ) {

        listoTrovitajVortoj.setPrototypeCellValue("ABCDEFGH");
        listoNetrovitajVortoj.setPrototypeCellValue("ABCDEFGH");
        listoNovajVortoj.setPrototypeCellValue("ABCDEFGH");

        listoTrovitajVortoj.setBorder(
            BorderFactory.createRaisedBevelBorder());
        listoNetrovitajVortoj.setBorder(
            BorderFactory.createRaisedBevelBorder());
        listoNovajVortoj.setBorder(
            BorderFactory.createRaisedBevelBorder());
         final JScrollPane ujoTrovitajVortoj =
             new JScrollPane(listoTrovitajVortoj);
         final JScrollPane ujoNetrovitajVortoj =
             new JScrollPane(listoNetrovitajVortoj);
         final JScrollPane ujoNovajVortoj =
             new JScrollPane(listoNovajVortoj);

        laFenestro = fen;
        final BoxLayout horizontalaArangxo
            = new BoxLayout(laFenestro, BoxLayout.X_AXIS);
        final JPanel ludejo = new JPanel();
        final BoxLayout vertikalaArangxo
            = new BoxLayout(ludejo, BoxLayout.Y_AXIS);
        ludejo.setLayout( vertikalaArangxo );

        laFenestro.setLayout( horizontalaArangxo );
        cx = laFenestro.getSize().width;
        cy = laFenestro.getSize().height;
System.out.println("GRANDECO: " + cx + "x" + cy);
        if( cx < 160 ) cx = 160;
        if( cy < 160 ) cy = 160;

        final int ll = literaro.length();
        for( int x= 0; x < NX; x++ ) {
            for( int y= 0; y < NY; y++ ) {
                double ghaz = Math.random();
                int haz = (int) (ghaz * ll);
                lit[x][y] = literaro.charAt(haz);
            }
        }
        
        laKrado = new Krado( this );
        laKrado.setSize( cx/2, cy*2/5);
        final JPanel cxirkauxLaVorto = new JPanel(); // sen tio aspektas strange
        cxirkauxLaVorto.setLayout(new BorderLayout());

        laVorto = new JLabel("");
        laVorto.setFont(new Font("sansserif", Font.BOLD, 18));
        cxirkauxLaVorto.setAlignmentX(0.01F); // kun 0.0F ne funkcias
        cxirkauxLaVorto.add(laVorto);
        cxirkauxLaVorto.setBorder(BorderFactory.createEtchedBorder());

        laButonoj = new Komandaro( this );
        laButonoj.setSize( cx/2, cy*2/5 );
        JLabel etiTrovitaj   = new JLabel( "trovitaj" );
        JLabel etiNetrovitaj = new JLabel( "ne trovitaj" );
        JLabel etiNovaj      = new JLabel( "novaj");

        ludejo.add(laKrado);
        ludejo.add(cxirkauxLaVorto);
        ludejo.add(laButonoj);
        vertikalaArangxo.layoutContainer(ludejo);

        laFenestro.add(ludejo);
        laFenestro.add(ujoTrovitajVortoj);
        laFenestro.add(ujoNovajVortoj);
        laFenestro.add(ujoNetrovitajVortoj);

        laButonoj.aktiviguAldonu( false );

        nTrovitajVortoj = 0;
        eblajVortoj.clear();
        listoTrovitajVortoj.removeAll();
        if( en != null ) {
            BufferedReader den
                = new BufferedReader(new InputStreamReader(en));
            try {
                while( (teksto= den.readLine()) != null ) {
                    teksto = teksto.replace('c', '\u0108');
                    teksto = teksto.replace('g', '\u011c');
                    teksto = teksto.replace('h', '\u0124');
                    teksto = teksto.replace('j', '\u0134');
                    teksto = teksto.replace('s', '\u015c');
                    teksto = teksto.replace('u', '\u016c');
                    if( akceptebla() ) {
                        eblajVortoj.put(teksto, Boolean.FALSE );
                    }
                }
                den.close();
                den = null;
            } catch( IOException e ) {
                System.out.println("Escepto dum legado: " + e.getMessage());
            } finally {
            }
            laButonoj.eblajVortoj( eblajVortoj.size() );
        }
        teksto = "";
        laButonoj.validate();
        laFenestro.validate();
    }

    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;
    }

    /**
     *  agVisxu() visxas la enigitan vorton
     */
    public void agVisxu() {
        teksto = "";
        laVorto.setText("");
        laVorto.setMinimumSize(new Dimension(100, 40));
        laButonoj.aktiviguAldonu( false );
    }

    /**
      * agAldonu() provas akcepti la enigitan vorton:
      */
    public void agAldonu() {
        Boolean jam = (Boolean) eblajVortoj.get( teksto );
        if( jam == null ) { // ne en la listo!
            java.awt.Container ujo = laFenestro;
            while( ujo != null && ! (ujo instanceof Frame) ) {
                ujo = ujo.getParent();
            }
            Object[] elektoj = { "Jes", "Ne" };
            final int decido = JOptionPane.showOptionDialog(ujo,
                   "\u0108u \"" + teksto + "\" estas \u011dusta vorto?",
                   "Demando", JOptionPane.DEFAULT_OPTION,
                   JOptionPane.QUESTION_MESSAGE,
                   null, elektoj, elektoj[0]);
            if (0 == decido) { // jes
                Padoj.novaVorto( teksto );
                PadaKadro.aldonuAlListo( modeloNovajVortoj, teksto);
            }
        }
        else if( jam.booleanValue() ) {
            laButonoj.mesagxu( teksto + " jam estis trovita");
        }
        else {
            eblajVortoj.put(teksto, Boolean.TRUE );
            aldonuAlListo(modeloTrovitajVortoj, teksto);
            nTrovitajVortoj ++;
            laButonoj.vortoj( nTrovitajVortoj );
        }
        agVisxu();
    }

    /**
      * la ludanto volas fini la ludon
      */
    public void agFinu() {
        if( nTrovitajVortoj < eblajVortoj.size() ) {
            // montru la netrovitajn vortojn:
            final Iterator cxiuj = eblajVortoj.keySet().iterator();
            while( cxiuj.hasNext() ) {
                    String v = (String) cxiuj.next();
                    Boolean trovita = (Boolean) eblajVortoj.get( v );
                    if( ! trovita.booleanValue() ) {
                        modeloNetrovitajVortoj.addElement( v );
                    }
            }
            eblajVortoj.clear();
        }
        else {
            Padoj.finuLaProgramon();
        }
    }

    /**
     * aldonu vorton al listo
     */
    public static void aldonuAlListo( DefaultListModel l, String vorto ) {
        int i;
        for( i= 0; i < l.size(); i++) {
            if( ((String) l.get(i)).compareTo(vorto) > 0 )
                break;
        }
        l.add( i, vorto );
    }

    /**
     * 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.setText( teksto );
            laButonoj.aktiviguAldonu( teksto.length() >= MIN_LONGECO );
            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
            return true;
        }
        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 {
    private static final String etiAldonu = "Aldonu";
    private static final String etiVisxu  = "Vi\u015du";
    private static final String etiFinu   = "Finu";
    private int nEblajPoentoj;
    private PadaKadro patro = null;
    private JLabel laMesagxo = new JLabel();
    private JLabel laPoentoj = new JLabel();
    private JLabel eblajPoentoj = new JLabel();
    private JButton butAldonu = new JButton( etiAldonu );
    private JButton butVisxu  = new JButton( etiVisxu  );
    private JButton butFinu   = new JButton( 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);

        butAldonu.addActionListener( new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                patro.agAldonu();
            }
        });

        butVisxu.addActionListener( new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                patro.agVisxu();
            }
        });

        butFinu.addActionListener( new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                patro.agFinu();
            }
        });
    }

    /**
     * (mal)aktivigu la butonon "Aldonu".
     * la butono "Aldonu" estas aktiva nur, sed la enigita vorto
     * estas suficxe longa.
     */
    public void aktiviguAldonu( boolean cxu ) {
        butAldonu.setEnabled( cxu );
        butAldonu.setToolTipText(cxu
            ? "aldonu la vorton al la listo"
            : "vorto havu minimume " + PadaKadro.MIN_LONGECO + " signojn.");
    }
    public void mesagxu( String t ) {
        laMesagxo.setText( t );
    }
    public void vortoj( int n ) {
        final float procentoj = n*100.0F/nEblajPoentoj;
        NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
        nf.setMaximumFractionDigits(2);
        final String tprocentoj = nf.format(procentoj);
        if( n == 1 )
            laPoentoj.setText("1 vorto trovita (" + tprocentoj + " %)");
        else
            laPoentoj.setText(n + " vortoj trovitaj (" + tprocentoj + " %)");
    }
    public void eblajVortoj( int n ) {
        nEblajPoentoj = n;
        eblajPoentoj.setText(n == 0 ? "Mi ne trovis vorton."
                                    : "Almena\u016d " + n + " vortoj troveblaj");
    }
}

/**
 * klaso "Krado": la liter-krado de dimensio 4x4
 */
class Krado extends JComponent {
    private int cx;
    private int cy;
    private int xMus;
    private int yMus;
    private Font tiparo;
    private PadaKadro patro = null;

    Krado( PadaKadro p ) {
        patro = p;
        tiparo= new Font("sansserif", Font.BOLD, 18);
        setMinimumSize(new Dimension(160, 160));

        final int nx = patro.nx();
        final int ny = patro.ny();
        
        final Dimension minimumo = new Dimension(30, 26);
        final Insets rando = new Insets(2, 3, 2, 3);
        setLayout( new GridLayout( nx, ny ) );
        final char[] c = new char[1];
        for( int x= 0; x < nx; x++ ) {
            for( int y= 0; y < ny; y++ ) {
                c[0] = patro.lit(x,y);
		JButton butono = new JButton(new String(c));
                butono.setMinimumSize(minimumo);
                butono.setMargin(rando);
                butono.setFont(tiparo);
                add(butono);
                butono.addActionListener( new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        final Object fonto = e.getSource();
                        final String litero = ((JButton) fonto).getText();
                        patro.novaLitero(litero.charAt(0));
                    }
                });
            }
        }
    }
}


/**
 * "NovaVorto": dialogo por demandi, cxu iu vorto estas vera vorto.
 */
class NovaVorto extends Dialog implements ActionListener {
    private final JLabel eti;
    private final JButton butJes;
    private final JButton butNe;
    private final String laVorto;
    private final DefaultListModel modeloNovajVortoj;

    public NovaVorto( Frame fen, String vorto, DefaultListModel listmodelo ) {
        super( fen, vorto, false );
        laVorto = vorto;
        modeloNovajVortoj = listmodelo;

        setLayout( new FlowLayout() );
        eti = new JLabel( "\u0108u \"" + vorto + "\"estas \u011dusta vorto?" );
        add( eti );

        butJes = new JButton( "Jes" );
        butNe  = new JButton( "Ne" );
        add( butJes );
        add( butNe );

        butJes.addActionListener( this );
        butNe.addActionListener( this );

        show();
        setSize(200,120);
    }

    public void actionPerformed(ActionEvent e) {
        final Object fonto = e.getSource();
        if (fonto == butJes) {
            Padoj.novaVorto( laVorto );
            PadaKadro.aldonuAlListo( modeloNovajVortoj, laVorto);
            // System.out.println("ni havas novan vorton: " + laVorto);
        }
        this.hide();
        this.dispose();
    }
}

