

/**
 * Title:
 * Description:
 * Copyright:    Copyright (c) 2002
 * Company:
 * @author
 * @version 1.0
 */
import java.util.*;
import java.io.*;
import java.net.*;

public class DataReader extends Thread{

    Kilpailu kilp;              // Kilpailu-olio, jolle tiedot lähetetään
    String id;                  // appletilta saatu tunnus, jonka avulla tietoja haetaan
    OLiveResults olr;           // isäntä-appletti, tarvitaan publicIP:n hakemiseen
    int demowait;               // ominaisuus, joka kertoo kuinka tiheään teitoja vastaanotetaan

    int port;			    // mihin sokettityhteys avataan (määritelty myöhemmin staattisena)
    private String host;            // mihin sokettityhteys avataan (määritelty myöhemmin staattisena)
    private String publicIP=null;   // oma IP, palvelin haluaa tietää
    private String myhost;          // oma host, palvelin haluaa tietää
    private boolean socketopen=false;  // onko soketti auki
    private Socket socket;          // käytettävä soketti, kaikki metodit käyttävät samaa (paitsi getPublicIP, joka toimii URL-HTTP -pohjalta)
    private BufferedReader in2;     // soketista otettava BufferedReader
    private PrintStream out;        // soketista otettava PrintStream
    private String myemail;         // oma e-mail, palvelin haluaa tietää
    private String mygsm;           // oma gsm, palvelin haluaa tietää
    private int viime = 0;          // kuinka monta riviä tulossanomia on luettu
    private int lines = 0;          // montako riviä luettu ko. yrityksellä
                                    //(jos tehdään silmukka joka lukee uudelleen tiedot alusta, lähetetään niitä vasta kun viime on saavutettu)
    private String uusin = "0";     // suurin vastaanotettu aikaleima, tietoa ei lähetetä, jos aikaleima on pienempi kuin uusin
                                    // uusillakin riveillä voi tulla samoja tietoja uudestaan jolloin aikaleima on pienempi)
                                    // (jos sama tieto tulee uudella aikaleimalla, tämä kontrolliodaan Kilpailu-oliossa)
    int status = 0;                     // kuinka monta riviä tämänhetkinen kanta sisältää
    private String statusaika = "0";    // tämänhetkisen kannan aikaleima, päivitetään näyttö vasta, kun tulee suurempi aikaleima tai rivejä on luettu yli status kpl
    private boolean paivitetty=false;   // kertoo, onko näyttöä jo päivitetty, jos on niin kaikki tiedot lähtee kisaaData -metodille parametrilla ipaivitys = true
    int pollaus = 60;                   // oletusarvo pollausaikavälille


    // otetaan viitteet tarvittaviin olioihin
    public DataReader(Kilpailu ikilp, String iid, String imyemail, String imygsm, OLiveResults iolr, int idemowait) {
        kilp = ikilp;
        id  = iid;
        myemail = imyemail;
        mygsm= imygsm;
        olr  =iolr;
        demowait = idemowait;
    }

    // kaikkien sokettiyhteyksien alussa suoritettavat rutiinit, jos ei
    // onnistu, niin heittää poikkeuksen, joka käsitellään ao. metodissa
    private void kattely(String output) throws Exception
    {
    	if (!socketopen) {

                //kilp.monitor("avataan yhteyttä...");
		socket = new Socket("www.suunnistus.info", 9004);
     		in2     = new BufferedReader( new InputStreamReader(socket.getInputStream()));
     		out    = new PrintStream(socket.getOutputStream());
		socketopen=true;
             }

            String str;
            out.println(id + " " + myhost + " " + publicIP + " " +myemail + " " + mygsm );

            if(!(str = in2.readLine()).startsWith("OK"))
            {
                System.out.println(str);
                throw new ProtocolException();
            }
            out.println(output);
             if(!(str = in2.readLine()).startsWith("OK"))
                throw new ProtocolException();

            if ((str = in2.readLine()).startsWith("ERR"))
                throw new ProtocolException();


    }

    public void suljeSoketti() {
        try {if (in2 != null) {in2.close();}} catch(Exception e) {}
        try {if (out != null) {out.close();}} catch(Exception e) {}
        try {if (socket != null) {socket.close();}} catch(Exception e) {}
        socketopen = false;
    }

    //varsinainen DataReader-olion toimintarunko
    public void run()
    {

        //jos ei saada publicIPta, ei soketitkaan aukea
        if(!getPublicIP())
        {
            kilp.monitor("yhteyden avaaminen ei onnistu (1)");
            return;
        }

        myhost=olr.getCodeBase().getHost();
        if(myhost.equals("")) myhost="JB4";
        //yritetään lukea lähtölistat ja Status (kuinka iso kanta), jos ei onnistus, niin ei haittaa vaan jatketaan
        kilp.monitor("yritetään ladata lähtölistoja");
        lueLahtolistat();

        //käytetty debuggaukseen
//kilp.naytaSarjaLista();
//paivitetty=true;

        lueStatus(10);

        if(status<1)
        {
            kilp.naytaSarjaLista();
            paivitetty = true;
            kilp.monitor("odotetaan uusia tietoja");
        }


	String str="";



  //tätä silmukkaa toistetaan säikeen elinajan, pois pääsee vain interruptedExceptinilla
  // tai 5 kertaa peräkkäin epäonnistuneella kättelyllä

  // eli jos yhteys katkeaa lukemisen aikana, tulee IOException, joka otetaan lopussa kiinni
  // ja aloitetaan kättely uudestaan
  eka: while(true)
  {
   lines = 0;
  //yritetään 5 kertaa kättelyä
  for(int i=0;i<5;i++)
  {
    try {
            kilp.monitor("avataan yhteyttä tuloksille");
            kattely("DB SHOWALL OPEN");
            break;
        }
    catch (InterruptedException ex) {
        suljeSoketti();
        return;
    }
    catch (Exception ex) {
            suljeSoketti();
            System.out.println("epäonnistunut kättely:"+i);
            ex.printStackTrace();
            // 5 sekunnnin tauko kättely-yritysten välillä
            for(int j =5;j>0;j--)
            {
              kilp.monitor("ei saada yhteyttä... uusi yritys "+j+"..." );
              try{sleep(1000);}catch(Exception ex1) {ex1.printStackTrace();suljeSoketti();return;}
            }
        }
    }
        //jos 5 yritystä ei tuottaneet tulosta
        if(!socketopen)
        {
          kilp.monitor("yhteyden muodostaminen epäonnistui... aloita valikosta uudelleen");
          return;
        }


        kilp.monitor("luetaan tähänastisia tietoja");
        if(paivitetty) kilp.monitor("odotetaan uusia tietoja");
                try {
                    toka: while (!this.isInterrupted()) {
                                    str = in2.readLine();
                                    if(str.startsWith("ATT"))
                                    {
                                      try {pollaus = Integer.parseInt(str.substring(4));}
                                      catch (Exception ex) {ex.printStackTrace();}
                                      suljeSoketti();
                                      siirryPollausMoodiin();
                                      return;
                                    }

                                    lines++;

                                    if(lines>viime){
                                     if(str!=null){
                                       if(!str.startsWith("#") && str.length()>20)
                                          if(analysoiData(str,(paivitetty || lines>status)) && !paivitetty)
                                            {kilp.naytaSarjaLista(); paivitetty = true; kilp.monitor("odotetaan uusia tietoja");}



                                     if (str.startsWith("OK") ) break;  // loppu ???

                                     viime = lines;
                                     }
                                                  try
                                                    {
                                            //double a = Math.random()*1000*demowait;
                                            //int b = (int)a;
                                                        int b=100;
                                                        if(!paivitetty) b=0;

                                                        yield();
                                                        Thread.sleep(b);
                                                    }
                                                    catch(InterruptedException _ex)
                                                    {
                                                        suljeSoketti();
                                                        socketopen = false;
                                                        break eka;
                                                    }

                                     }
                                    }

          suljeSoketti();
          kilp.monitor("yhteyden avaaminen ei onnistu (3)");
          System.out.println(str);

    }
    catch (Exception e) {
          suljeSoketti();
          kilp.monitor("yhteys katkennut");
          e.printStackTrace();

    }
    }
    suljeSoketti();
}


    public boolean getPublicIP() {
    	URL base = olr.getDocumentBase();
	//String path = base.getProtocol()+"://"+base.getHost()+"/cgi-bin/textdb/myip";
	String path = "http://www.suunnistus.info/cgi-bin/textdb/myip";
        BufferedReader in = null;
        try {
		URL url = new URL(path);
		in = new BufferedReader(
			new InputStreamReader( url.openStream() ));
		String line;
		while ((line = in.readLine()) != null) {
			publicIP=line;
			}
		if(in!=null) in.close();
                return true;
	    }
	catch(MalformedURLException e) {
                return false;
                }
	catch(IOException ee) {
               try {if(in!=null) in.close();} catch (Exception e){}
               return false;
                }
        finally {
          //suljetaan virta
          try {
            if (in!= null) in.close();
            } catch(IOException ioe) {}
        }

	}


    // palauttaa tiedon pitääkö näyttö päivitää
    // param istatus, kertoo pitääkö kutsuvan metodin mielestä näyttö päivittää
    //    = true jos lines > status tai on jo päivittety
    // tämä metodi voi palauttaa istauksesta huolimatta true, jos aikaleima ylittää statusajan


    private boolean analysoiData(String data, boolean istatus)
    {
        data = korvaa(data,";;","; ;");
        try{
               StringTokenizer st1 = new StringTokenizer(data,";");
               st1.nextToken();
               String aikaleima = st1.nextToken();
               //testataan, onko aikaleima vanhempi kuin uusin
               //samaa asiaa testataan rivimäärilläkin, siis tuplavarmistus

               if(aikaleima.compareTo(uusin)<0)
                return istatus && !paivitetty;
                // tässä päivitetään jos lines>status muttei ole vielä päivitetty
                // jos on jo päivitetty, ei kannata päivittää, kun tietoakaan ei lisätä

               uusin = aikaleima;
               String sarja = st1.nextToken();
               String matka = st1.nextToken();
               int nro = Integer.parseInt(st1.nextToken());
               String nimi = st1.nextToken() + " " + st1.nextToken();
               String seura = st1.nextToken();
               String aika= st1.nextToken();
               int aikasek = Integer.parseInt(st1.nextToken());
               kilp.lisaaData(aikaleima,sarja,matka,nro,nimi,seura,aika,aikasek,(istatus || aikaleima.compareTo(statusaika)>0));
               return(istatus || aikaleima.compareTo(statusaika)>0);

           }

           catch(Exception e) {
            e.printStackTrace();
            System.out.println("ongelmarivi:\n"+data);
            return istatus;
           }

    }

    private static String korvaa(String aSearch, String aFind, String aReplace)
    {
    String result = aSearch;
    if (result != null && result.length() > 0)
        {
        int a = 0;
        int b = 0;
        while (true)
            {
            a = result.indexOf(aFind, b);
            if (a != -1)

                {
                result = result.substring(0, a) + aReplace + result.substring(a + aFind.length());
                b = a + aReplace.length();
            }
            else
            break;
        }
    }
    return result;
}
    private void lueStatus(int varmuusvara) {
	String str;
        try
        {
            kattely("DB STATUS");
        }
        catch (Exception ex)
        {
            suljeSoketti();
            ex.printStackTrace();
            return;
         }

         try
         {
            str = in2.readLine();
            StringTokenizer st1 = new StringTokenizer(str,";");
            if(st1.countTokens()==8)
            {
              statusaika ="";
              for(int i=0;i<6;i++)
                  statusaika += st1.nextToken();

              st1.nextToken();
              // luetaan status, joka kertoo montako riviä kannassa on juuri nyt, näytön päivitys teh-
              // dään vasta, kun status kpl rivejä on luettu
              // -10 varmuuden vuoksi
              try{status=Integer.parseInt(st1.nextToken())-varmuusvara;}catch(Exception ex){}
            }

          suljeSoketti();

        }
        catch (Exception e)
        {
          suljeSoketti();
          //kilp.monitor("yhteys katkennut");
          e.printStackTrace();
        }



    }

 private void siirryPollausMoodiin() {
    String str;
     kilp.monitor("siirrytään pollausmoodiin "+pollaus);
    //luetaan nykyiset statustiedot
    int aikStatus = status;
    String aikStatusaika = statusaika;
    boolean onnistuiko = false;

    if(!paivitetty)
    {
      kilp.naytaSarjaLista();
      paivitetty = true;
      kilp.monitor("odotetaan uusia tietoja");
    }

    //toistetaan säikeen loppuun asti
    eka: while(!this.isInterrupted())
    {
           for(int i = pollaus;i>0;i--)
           {
            kilp.monitor("pollausmoodi...tietojen lukuun "+i);
                try
                {
                    yield();
                    Thread.sleep(1000);
                }
                catch(InterruptedException _ex)
                {
                    suljeSoketti();
                    socketopen = false;
                    break eka;
                }
            }
                aikStatus = (status<0?0:status);
                aikStatusaika = statusaika;
                lueStatus(0);
                kilp.monitor("luetaan tietoja...");
                //jos on muutoksia
                if(status>aikStatus || statusaika.compareTo(aikStatusaika)>0 || (!onnistuiko && status>lines))
                {
                     //System.out.println("st"+status+" as"+aikStatus+" l"+lines);
                        try {
                                kilp.monitor("avataan yhteyttä tuloksille");
                                kattely("DB "+ (onnistuiko?aikStatus+"":(lines<aikStatus?lines+"":aikStatus+"")) + " 0");
                        //System.out.println("DB "+ (onnistuiko?aikStatus+"":(lines<aikStatus?lines+"":aikStatus+"")) + " 0");
                                onnistuiko = false;
                            }
                         catch (Exception ex) {
                                suljeSoketti();
                                ex.printStackTrace();
                                onnistuiko = false;
                        }
                        //jos kättely onnistui
                        if(socketopen)
                        {
                            // tehdään joko säikeen keskeytykseen tai OK-riviin (tai poikkeukseen) asti
                            toka: while (!this.isInterrupted()) {
                                    try {
                                        str = in2.readLine();
                                        //System.out.println(str);
                                        lines++;
                                        if (str.startsWith("OK") )
                                        {
                                             onnistuiko = true;
                                             break toka;  // loppu
                                        }
                                        if(str!=null)
                                           if(!str.startsWith("#") && str.length()>20)
                                              analysoiData(str,true);

                                     }
                                     catch (Exception ex) {
                                        suljeSoketti();
                                        socketopen = false;
                                        break toka;
                                     }
                            }
                            suljeSoketti();
                        }


       }

       }
    suljeSoketti();
    return;



 }


 private void lueLahtolistat() {

	String str;
        Vector sarjat = new Vector();


        try
        {
            kattely("START LISTCLASS");
        }
        catch (Exception ex)
        {
            suljeSoketti();
            ex.printStackTrace();
            return;
         }


        try {

                     while((str = in2.readLine())!=null && !str.startsWith("OK"))
                     {
                        if(str.equals("ONELIST"))
                        {
                            try {if (in2 != null) {in2.close(); out.close();}} catch(IOException ioe) {}
                            socketopen = false;
                            haeONELIST();
                            return;
                        }
                        else
                        {
                            StringTokenizer st1 = new StringTokenizer(str,";");
                            if(!str.startsWith("#") && st1.hasMoreTokens())
                            sarjat.addElement(st1.nextToken());


                         }
                      }

                   suljeSoketti();

            }
            catch (Exception e)
            {
                  suljeSoketti();
                  kilp.monitor("yhteys katkennut");
                  e.printStackTrace();
            }

    for(int i=0;i<sarjat.size();i++)
    {
      haeSarjanlista((String)sarjat.elementAt(i));
    }

}

private void haeONELIST() {
	String str;
        Vector sarjat = new Vector();



        try
        {
            kattely("START LIST");
        }
        catch (Exception ex)
        {
            suljeSoketti();
            ex.printStackTrace();
            return;
         }

         try
         {
                kilp.monitor("ladataan lähtölistoja");
                String rivi;
                String sarja ="";
                String matka ="";
                String data ="";
                while((rivi=in2.readLine()) != null)
                {
                    if(rivi.startsWith("SARJA;"))
                    {
                        if(!sarja.equals("") && !data.equals(""))
                             kilp.teeSarja(sarja,matka,data);


                        if(rivi.length()>6)
                        {
                          sarja = rivi.substring(6).trim();
                          while(sarja.endsWith(";")) sarja = sarja.substring(0,sarja.length()-1);
                           if (sarja.indexOf(';')>0)
                              {
                                matka = sarja.substring(sarja.indexOf(';')+1);
                                sarja = sarja.substring(0,sarja.indexOf(';'));
                              }
                            else matka = "";
                          }
                       data = "";
                     }
                    else if (rivi.length()>10) data += rivi +"\n";

                }

            if(!sarja.equals("") && !data.equals("")) {
                          kilp.teeSarja(sarja,matka,data);
			  kilp.monitor(sarja);
			  }

            suljeSoketti();

        }
        catch (Exception e) {
          suljeSoketti();
          kilp.monitor("virhe lähtölistojen luvussa");
          e.printStackTrace();
        }
}

private void haeSarjanlista(String isarja) {

	String str;
        Vector sarjat = new Vector();


        try
        {
            kattely("START "+isarja);
        }
        catch (Exception ex)
        {
            suljeSoketti();
            ex.printStackTrace();
            return;
         }

         try {
                kilp.monitor("ladataan lähtölistoja");
                String sarja = isarja;
                String matka ="";
                String rivi = in2.readLine();

                        if(rivi.startsWith("#") && rivi.indexOf(';')>0);
                        {
                          sarja = rivi.substring(rivi.indexOf(';')+1).trim();
                          while(sarja.endsWith(";")) sarja = sarja.substring(0,sarja.length()-1);
                           if (sarja.indexOf(';')>0)
                              {
                                matka = sarja.substring(sarja.indexOf(';')+1);
                                sarja = sarja.substring(0,sarja.indexOf(';'));
                              }
                            else matka = "";

                          }
                 String data = "";
                 while((rivi=in2.readLine()) != null)
                    if (rivi.length()>10) data += rivi +"\n";

                 if(!sarja.equals("") && !data.equals("")) {
                    kilp.teeSarja(sarja,matka,data);
		    kilp.monitor(sarja);
		    }

               suljeSoketti();
    }
    catch (Exception e) {
          suljeSoketti();
          kilp.monitor("virhe lähtölistojen luvussa");
          e.printStackTrace();
    }

    }

}




