/**********************************************************************
 *
 * This file is part of HBCI4Java.
 * Copyright (c) Olaf Willuhn
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **********************************************************************/

package org.kapott.hbci.manager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Uebernimmt das Umwandeln eines Flicker-Codes in die blinkende Darstellung.
 * Da wir hier in HBCI4Java ja keinen GUI-spezifischen Code (Swing, SWT, etc.)
 * haben, uebernimmt die Klasse lediglich das Erzeugen der schwarzen und weissen
 * Balken sowie das Timing. Sie ruft dann im Wiedergabe-Takt die Funktion paint()
 * auf, die ueberschrieben werden muss, um dort dann das eigentliche Zeichnen
 * der 5 Balken durchzufuehren.
 * Die paint-Funktion wird so ca. 10-20 mal pro Sekunde aufgerufen, sollte die
 * Ausgabe auf dem Bildschirm daher flott machen ;)
 */
public class FlickerRenderer
{
  /**
   * Default-Taktfequenz in Hz.
   * Soll laut tan_hhd_uc_v14.pdf, Kapitel C.1 zwischen 2 und 20 Hz liegen.
   */
  public final static int FREQUENCY_DEFAULT = 10;
  
  /**
   * Minimale Taktfrequenz.
   */
  public final static int FREQUENCY_MIN     = 2;
  
  /**
   * Maximale Taktfrequenz.
   * Laut Spec. sind die Geraete bis 20 Hz zugelassen, viele koennen aber schneller.
   */
  public final static int FREQUENCY_MAX = 40;
  
  private int halfbyteid       = 0;
  private int clock            = 0;
  private List<int[]> bitarray = null;
  
  private Thread thread = null;
  private int iterations = 0;
  private int freq       = FREQUENCY_DEFAULT;
  
  /**
   * ct.
   * @param code der zu rendernde Flicker-Code.
   * Er wird von HBCI4Java ueber den Callback NEED_PT_TA uebergeben.
   * 
   * Etwa so:
   * 
   * case HBCICallback.NEED_PT_TAN:
   *   String flicker = retData.toString();
   *   if (flicker != null && flicker.length() > 0)
   *   {
   *     MyFlickerRenderer = new FlickerRenderer(flicker) {
   *       // hier paint() ueberschreiben
   *     };
   *   }
   */
  public FlickerRenderer(String code)
  {
    // Sync-Identifier vorn dran haengen.
    code = "0FFF" + code;

    // Das Bitfeld mit der BCD-Codierung.
    // Koennte man auch in einer For-Schleife und etwas Bit-Schieberei machen.
    // Aber das ist ist besser lesbar ;)
    Map<String,int[]> bcdmap = new HashMap<String,int[]>();
    bcdmap.put("0",new int[]{0, 0, 0, 0, 0});
    bcdmap.put("1",new int[]{0, 1, 0, 0, 0});
    bcdmap.put("2",new int[]{0, 0, 1, 0, 0});
    bcdmap.put("3",new int[]{0, 1, 1, 0, 0});
    bcdmap.put("4",new int[]{0, 0, 0, 1, 0});
    bcdmap.put("5",new int[]{0, 1, 0, 1, 0});
    bcdmap.put("6",new int[]{0, 0, 1, 1, 0});
    bcdmap.put("7",new int[]{0, 1, 1, 1, 0});
    bcdmap.put("8",new int[]{0, 0, 0, 0, 1});
    bcdmap.put("9",new int[]{0, 1, 0, 0, 1});
    bcdmap.put("A",new int[]{0, 0, 1, 0, 1});
    bcdmap.put("B",new int[]{0, 1, 1, 0, 1});
    bcdmap.put("C",new int[]{0, 0, 0, 1, 1});
    bcdmap.put("D",new int[]{0, 1, 0, 1, 1});
    bcdmap.put("E",new int[]{0, 0, 1, 1, 1});
    bcdmap.put("F",new int[]{0, 1, 1, 1, 1});

    // Wir mappen den Code basierend auf dem Bit-Array.
    // Linkes und rechtes Zeichen jedes Bytes wird getauscht.
    this.bitarray = new ArrayList<int[]>();
    for (int i = 0; i < code.length(); i += 2) {
      bitarray.add(bcdmap.get(Character.toString(code.charAt(i+1))));
      bitarray.add(bcdmap.get(Character.toString(code.charAt(i))));
    }
  }
  
  /**
   * Legt die Taktfrequenz in Hz fest.
   * @param hz die zu verwendende Taktfrequenz.
   * Es werden nur Werte zwischen {@link FlickerRenderer#FREQUENCY_MIN} und
   * {@link FlickerRenderer#FREQUENCY_MAX} akzeptiert.
   */
  public void setFrequency(int hz)
  {
    if (hz < FREQUENCY_MIN || hz > FREQUENCY_MAX)
      return;
    this.freq = hz;
  }
  
  /**
   * Startet das Rendering des Flicker-Codes.
   * Die Funktion startet einen neuen Thread, kehrt also sofort zurueck.
   * 
   * Da diese Funktion einen neuen Thread startet und somit sofort
   * zurueckkehrt, kann es ggf. noetig sein, eine Warteschleife zu
   * implementieren. Hierzu kann einfach die Funktion "waitFor()" aufgerufen
   * werden. Sie pausiert solange, bis "stop()" augerufen wurde.
   * 
   * Beispiel:
   * 
   * FlickerRenderer renderer = new FlickerRenderer(meinCode) {
   *   public void paint(boolean b1,boolean b2,boolean b3,boolean b4,boolean b5)
   *   {
   *     // hier eigenen Code zum Rendern einbauen
   *   }
   *   
   *   public void done(int iterations)
   *   {
   *     // Nach 20 Uebertragungen hoeren wir auf.
   *     if (iterations > 20)
   *       stop();
   *   }
   * };
   * 
   * renderer.start();
   * renderer.waitFor();
   * System.out.println("Code uebertragen");
   * 
   */
  public final synchronized void start()
  {
    // ggf. laufenden Thread beenden
    stop();
    
    this.thread = new Thread("Flicker Update-Thread")
    {
      public void run()
      {
        // Wir fangen beim ersten Halbbyte an.
        halfbyteid = 0;
        
        // Die Clock, die immer hin und her kippt. Wir beginnen bei 1.
        // Sonst wuerde das allererste Zeichen nur einmal uebertragen
        // werden, was bewirkt, dass der Code erst einmal komplett
        // durchlaufen muesste, bevor wir einen kompletten gesendet haetten
        clock      = 1;

        try
        {
          // Die Endlos-Schleife mit der Uebertragung
          while (true)
          {
            int[] bits = bitarray.get(halfbyteid);
            
            bits[0] = clock;
            paint(bits[0] == 1,bits[1] == 1,bits[2] == 1,bits[3] == 1,bits[4] == 1);
            
            clock--;
            if (clock < 0)
            {
              clock = 1;
              
              // Jedes Zeichen muss doppelt uebertragen werden. Einmal mit clock 0
              // und einmal mit clock 1.
              halfbyteid++;
              if (halfbyteid >= bitarray.size())
              {
                halfbyteid = 0;

                // Wir sind einmal mit dem Code komplett durch
                iterations++;
                done(iterations);
              }
            }
            
            // Warten
            // Wir errechnen die Wartezeit in jedem Durchlauf.
            // Dann kann die Frequenz auch waehrend des Blinkens geaendert werden.
            long sleep = 1000L / freq;
            sleep(sleep);
          }
        }
        catch (InterruptedException e)
        {
          // Ende der Anzeige
        }
      }
    };
    thread.start();
  }
  
  /**
   * Stoppt das Rendern.
   */
  public final void stop()
  {
    if (this.thread != null)
    {
      try
      {
        if (this.thread != null)
        {
          this.thread.interrupt();
          synchronized (this.thread)
          {
            this.thread.notifyAll();
          }
        }
      }
      finally
      {
        this.thread = null;
      }
    }
  }
  
  /**
   * Wird immer dann aufgerufen, wenn die 5 Balken der Flicker-Grafik aktualisiert
   * werden sollen. Die 5 Boolean-Parameter legen die aktuell anzuzeigende Codierung
   * fest.
   * 
   * Die Default-Implementierung ist leer.
   * 
   * Diese Funktion muss auf jeden Fall ueberschrieben werden. Sonst kriegt man
   * keinen Flicker-Code.
   * 
   * @param b1 Balken 1. true=weiss, false=schwarz
   * @param b2 Balken 2. true=weiss, false=schwarz
   * @param b3 Balken 3. true=weiss, false=schwarz
   * @param b4 Balken 4. true=weiss, false=schwarz
   * @param b5 Balken 5. true=weiss, false=schwarz
   */
  public void paint(boolean b1,boolean b2,boolean b3,boolean b4,boolean b5)
  {
  }
  
  /**
   * Wird immer dann aufgerufen, nachdem der Flicker-Code einmal komplett
   * an den TAN-Generator uebertragen wurde.
   * Die Funktion wird zum ersten Mal NACH der ersten Uebertragung aufgerufen
   * und anschliessend nach jeder weiteren.
   * 
   * Die Default-Implementierung ist leer.
   * 
   * Fuer gewoehnlich wird die Funktion zur Darstellung nicht benoetigt.
   * Sie kann aber zu Debugging-Zwecken verwendet werden oder zum automatischen
   * Abbruch nach einer definierten Anzahl von Uebertragungen.
   * Sie muss also nicht ueberschrieben werden.
   * 
   * Die Funktion wird direkt im Flicker-Thread aufgerufen. Sie sollte daher
   * auf keinen Fall irgendwas aufwaendiges machen, da das zum Ausbremsen
   * der Flicker-Uebertragung fuehren wuerde.
   * 
   * @param iterations Anzahl der bisherigen Uebertragungen (beginnend bei 1 -
   * da die Funktion ja erst nach der ersten Uebertragung aufgerufen wird)
   */
  public void done(int iterations)
  {
  }
  
  /**
   * Kann verwendet werden, um den Aufrufer-Thread solange zu pausieren,
   * bis "stop()" aufgerufen wurde. Damit kann warten, bis die Uebertragung
   * abgeschlossen ist.
   */
  public final synchronized void waitFor()
  {
    if (this.thread == null)
      return;
    
    synchronized (this.thread)
    {
      if (this.thread == null)
        return;
      
      try
      {
        this.thread.wait();
      }
      catch (InterruptedException e)
      {
        // Wir sind raus.
      }
    }
  }
}



/**********************************************************************
 * $Log: FlickerRenderer.java,v $
 * Revision 1.6  2011/06/07 13:55:08  willuhn
 * @N 28-hbci4java-flicker-speed2.patch
 *
 * Revision 1.5  2011-06-06 15:32:51  willuhn
 * @N 26-hbci4java-flicker-speed.patch
 *
 * Revision 1.3  2011-06-06 15:25:12  willuhn
 * @N 26-hbci4java-flicker-speed.patch
 *
 * Revision 1.2  2011-05-27 15:46:13  willuhn
 * @N 23-hbci4java-chiptan-opt2.patch - Kleinere Nacharbeiten
 *
 * Revision 1.1  2011-05-27 10:28:38  willuhn
 * @N 22-hbci4java-chiptan-opt.patch
 *
 **********************************************************************/