
// ImagesLoader.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Konfigurační soubory a soubory s obrázky jsou umístěny v adresáři Images/.
   (konstanta IMAGE_DIR).

   Formát konfiguračního souboru:

    o <jméno souboru>                     // jediný obrázek file

    n <jméno souboru*.ext> <číslo>       // série očíslovaných souborů s obrázyk, jejichž
                                         // jména používají čísla 0 - <číslo>-1

    s <jméno souboru> <číslo>           // pásmový soubor obsahující jediný řádek obrázků, jejichž
                                        // počet udává parametr <číslo>

    g <název> <jméno souboru> [ <jméno souboru> ]*   // skupina souborů různách jmen;
                                // jsou přístupné přes <název> a pozici nebo přes prefix
                                // <jméno souboru>

    nebo prázdné řádeky 
    nebo komentáře.

    K očíslovaným souborům s obrázky (n) lze přistupovat pomocí prefixu <jméno souboru> a <číslo>.

    Obrázky z pásového souboru obržíme přes prefix <jméno souboru> a jejich pozici v pásu
    (u něhož předpokládáme, že má jeden řádek).

    K obrázkům ve skupině souborů lze přistupovat přes <název> v sekci 'g' a 
    prefixem <jméno souboru> příslušné danému souboru nebo jeho pozicí ve skupině.

    Obrázky jsou uloženy jako objekty typu BufferedImage, takže s nimi 
    bude JVM pokud možno pracovat jako s 'řízenými' ('managed') obrázky.
*/


import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;     // pro třídu ImageIcon


public class ImagesLoader
{
  private final static String IMAGE_DIR = "Images/";

  private HashMap imagesMap; 
    /* klíčem je jméno souboru, objektem (hodnotou) pole instancí typu BufferedImage */
  private HashMap gNamesMap;
    /* klíčem je řetězec <jméno> sekce 'g', objektem je seznam typu ArrayList jmen souborů
       dané skupiny. Pomocí tohoto objektu lze přistupovat ke skupinovému obrázku přes
       název skupiny a jméno souboru. */

  private GraphicsConfiguration gc;


  public ImagesLoader(String fnm)
  // začni načtením obrázků specifikovaných v souboru fnm
  { initLoader();
    loadImagesFile(fnm);
  }  // konec metody: ImagesLoader()

  public ImagesLoader()
  {  initLoader();  } 


  private void initLoader()
  {
    imagesMap = new HashMap();
    gNamesMap = new HashMap();

    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
  }  // konec metody: initLoader()


  private void loadImagesFile(String fnm)
  /* Formát:
        o <fnm>                     // jediný obrázek
        n <fnm*.ext> <number>       // číslovaná sekvence obrázků
        s <fnm> <number>            // obrázkové pásmo
        g <name> <fnm> [ <fnm> ]*   // skupina obrázků 

       nebo prázdný řádeks
       nebo komentář.
  */
  { 
    String imsFNm = IMAGE_DIR + fnm;
    System.out.println("Čtený soubor: " + imsFNm);
    try {
      InputStream in = this.getClass().getResourceAsStream(imsFNm);
      BufferedReader br = new BufferedReader( new InputStreamReader(in));
      // BufferedReader br = new BufferedReader( new FileReader(imsFNm));
      String line;
      char ch;
      while((line = br.readLine()) != null) {
        if (line.length() == 0)  // prázdný řádek
          continue;
        if (line.startsWith("//"))   // komentář
          continue;
        ch = Character.toLowerCase( line.charAt(0) );
        if (ch == 'o')  // jediný obrázek
          getFileNameImage(line);
        else if (ch == 'n')  // číslovaná sekvence obrázků
          getNumberedImages(line);
        else if (ch == 's')  // obrázkové pásmo
          getStripImages(line);
        else if (ch == 'g')  // skupina obrázků
          getGroupImages(line);
        else
          System.out.println("Řádek nerozpoznán: " + line);
      }
      br.close();
    } 
    catch (IOException e) 
    { System.out.println("Chyba při čtení souboru: " + imsFNm);
      System.exit(1);
    }
  }  // konec metody: loadImagesFile()


  // --------- načti jediný obrázek -------------------------------

  private void getFileNameImage(String line)
  /* formát:
        o <fnm>
  */
  { StringTokenizer tokens = new StringTokenizer(line);

    if (tokens.countTokens() != 2)
      System.out.println("Nesprávný počet argumentů pro " + line);
    else {
      tokens.nextToken();    // přeskoč označení příkazu
      System.out.print("Řádek typu o: ");
      loadSingleImage( tokens.nextToken() );
    }
  }  // konec metody: getFileNameImage()


  public boolean loadSingleImage(String fnm)
  // metodu lze volat přímo
  {
    String name = getPrefix(fnm);

    if (imagesMap.containsKey(name)) {
      System.out.println( "Chyba: " + name + " se již používá");
      return false;
    }

    BufferedImage bi = loadImage(fnm);
    if (bi != null) {
      ArrayList imsList = new ArrayList();
      imsList.add(bi);
      imagesMap.put(name, imsList);
      System.out.println("  Uloženo " + name + "/" + fnm);
      return true;
    }
    else
      return false;
  }  // konec metody: loadSingleImage()


  private String getPrefix(String fnm)
  // extrahuj jméno souboru před znakem '.'
  {
    int posn;
    if ((posn = fnm.lastIndexOf(".")) == -1) {
      System.out.println("V zadaném souboru prefix neexistuje: " + fnm);
      return fnm;
    }
    else
      return fnm.substring(0, posn);
  } // konec metody: getPrefix()



  // --------- načti číslované obrázky -------------------------------

  private void getNumberedImages(String line)
  /* formát:
        n <fnm*.ext> <number>
  */
  {
    StringTokenizer tokens = new StringTokenizer(line);

    if (tokens.countTokens() != 3)
      System.out.println("Nesprávný počet argumentů pro " + line);
    else {
      tokens.nextToken();    // přeskoč označení příkazu
      System.out.print("n Line: ");

      String fnm = tokens.nextToken();
      int number = -1;
      try {
        number = Integer.parseInt( tokens.nextToken() );
      }
      catch(Exception e)
      { System.out.println("Číslo je nesprávné na řádku " + line);  }

      loadNumImages(fnm, number);
    }
  }  // konec metody: getNumberedImages()



  public int loadNumImages(String fnm, int number)
  /*  Metodu lze zavolat přímo.
      fnm odpovídá parametru na řádku (konfiguračního souboru):
           n <fnm*.ext> <number>
  */
  {
    String prefix = null;
    String postfix = null;
    int starPosn = fnm.lastIndexOf("*");   // najdi znak '*'
    if (starPosn == -1) {
      System.out.println("No '*' in filename: " + fnm);
      prefix = getPrefix(fnm);
    }
    else {   // považuj fnm za prefix + "*" + postfix
      prefix = fnm.substring(0, starPosn);
      postfix = fnm.substring(starPosn+1);
    }

    if (imagesMap.containsKey(prefix)) {
      System.out.println( "Chyba: " + prefix + " se již používá");
      return 0;
    }

    return loadNumImages(prefix, postfix, number);
  }  // konec loadNumImages()



  private int loadNumImages(String prefix, String postfix, int number)
  /* Načte sérii obrázků ze souborů, jejichž jména mají tvar
            prefix + <i> + postfix
     kde i se pohybuje v rozmezí 0 až number-1
  */
  { 
    String imFnm;
    BufferedImage bi;
    ArrayList imsList = new ArrayList();
    int loadCount = 0;

    if (number <= 0) {
      System.out.println("Chyba: Number <= 0: " + number);
      imFnm = prefix + postfix;
      if ((bi = loadImage(imFnm)) != null) {
        loadCount++;
        imsList.add(bi);
        System.out.println("  Uloženo " + prefix + "/" + imFnm);
      }
    }
    else {   // načte prefix + <i> + postfix, kde i = 0 až <number-1>
      System.out.print("  Přidávám " + prefix + "/" + 
                           prefix + "*" + postfix + "... ");
      for(int i=0; i < number; i++) {
        imFnm = prefix + i + postfix;
        if ((bi = loadImage(imFnm)) != null) {
          loadCount++;
          imsList.add(bi);
          System.out.print(i + " ");
        }
      }
      System.out.println();
    }

    if (loadCount == 0)
      System.out.println("Nebyly načteny žádné obrázky pro prefix " + prefix);
    else 
      imagesMap.put(prefix, imsList);

    return loadCount;
  }  // konec metody: loadNumImages()



  // --------- načti obrázkové pásmo -------------------------------

  private void getStripImages(String line)
  /* formát:
        s <fnm> <number>
  */
  {
    StringTokenizer tokens = new StringTokenizer(line);

    if (tokens.countTokens() != 3)
      System.out.println("Nesprávný počet argumentů pro " + line);
    else {
      tokens.nextToken();    // přeskoč označení příkazu
      System.out.print("s Line: ");

      String fnm = tokens.nextToken();
      int number = -1;
      try {
        number = Integer.parseInt( tokens.nextToken() );
      }
      catch(Exception e)
      { System.out.println("Číslo je nesprávné pro řádek " + line);  }

      loadStripImages(fnm, number);
    }
  }  // konec metody: getStripImages()



  public int loadStripImages(String fnm, int number)
  /* Metodu lze volat přímo. Slouží pro načtení souboru s pásmem,
     které obsahuje <number> obrázků.
  */
  {
    String name = getPrefix(fnm);
    if (imagesMap.containsKey(name)) {
      System.out.println( "Chyba: " + name + " se již používá");
      return 0;
    }
    // load the images into an array
    BufferedImage[] strip = loadStripImageArray(fnm, number);
    if (strip == null)
      return 0;

    ArrayList imsList = new ArrayList();
    int loadCount = 0;
    System.out.print("  Přidávám " + name + "/" + fnm + "... ");
    for (int i=0; i < strip.length; i++) {
      loadCount++;
      imsList.add(strip[i]);
      System.out.print(i + " ");
    }
    System.out.println();

    if (loadCount == 0)
      System.out.println("Nebyly načteny žádné obrázky pro " + name);
    else 
      imagesMap.put(name, imsList);

    return loadCount;
  }  // konec loadStripImages()



  // ------ skupina souborů s obrázky ---------


  private void getGroupImages(String line)
  /* formát:
        g <name> <fnm>  [ <fnm> ]*
  */
  { StringTokenizer tokens = new StringTokenizer(line);

    if (tokens.countTokens() < 3)
      System.out.println("Nesprávný počet argumentů pro " + line);
    else {
      tokens.nextToken();    // přeskoč označení příkazu
      System.out.print("g Line: ");

      String name = tokens.nextToken();

      ArrayList fnms = new ArrayList();
      fnms.add( tokens.nextToken() );  // přečti jména všech souborů
      while (tokens.hasMoreTokens())
        fnms.add( tokens.nextToken() );

      loadGroupImages(name, fnms);
    }
  }  // konec metody: getGroupImages()



  public int loadGroupImages(String name, ArrayList fnms)
  /* Metodu lze zavolat přímo. Slouží pro načtení skupiny obrázků, 
     jejichž jména souborů jsou uloženy v seznamu typu ArrayList jménen fnms.
     Budou uloženy pod svým názvem uvedeným konfiguračním souboru na řádku 'g'.
  */
  {
    if (imagesMap.containsKey(name)) {
      System.out.println( "Chyba: " + name + " se již používá");
      return 0;
    }

    if (fnms.size() == 0) {
      System.out.println("Seznam jmen souborů je prázdný");
      return 0;
    }

    BufferedImage bi;
    ArrayList nms = new ArrayList();
    ArrayList imsList = new ArrayList();
    String nm, fnm;
    int loadCount = 0;

    System.out.println("  Přidávám " + name + "...");
    System.out.print("  ");
    for (int i=0; i < fnms.size(); i++) {    // načti soubory
      fnm = (String) fnms.get(i);
      nm = getPrefix(fnm);
      if ((bi = loadImage(fnm)) != null) {
        loadCount++;
        imsList.add(bi);
        nms.add( nm );
        System.out.print(nm + "/" + fnm + " ");
      }
    }
    System.out.println();

    if (loadCount == 0)
      System.out.println("Nebyly načteny žádné soubory pro " + name);
    else {
      imagesMap.put(name, imsList);
      gNamesMap.put(name, nms);
    }

    return loadCount;
  }  // konec metody: loadGroupImages()


  public int loadGroupImages(String name, String[] fnms)
  // do pole se zadávají jména příslušných souborů
  {  
    ArrayList al = new ArrayList( Arrays.asList(fnms) );
    return loadGroupImages(name, al);  
  }



  // ------------------ metody pro přístup k atributům -------------------

  public BufferedImage getImage(String name)
  /* Poskytuje obrázek spojený se jménem <name>. Existuje-li pod tímto
     návem více obrázků, vrátí první z nich.
  */
  {
    ArrayList imsList = (ArrayList) imagesMap.get(name);
    if (imsList == null) {
      System.out.println("Pod daným názvem nejsou žádné obrázky: " + name);  
      return null;
    }

    // System.out.println("Vracím obrázek uložený pod názvem " + name);  
    return (BufferedImage) imsList.get(0);
  }  // konec metody: getImage() s parametrem název



  public BufferedImage getImage(String name, int posn)
  /* Poskytuje obrázek spojený se jménem <name> na pozici <posn>
     v seznamu obrázků. Je-li <posn> < 0 pak vrací první obrázek 
     ze seznamu. Je-li větší než velikost seznamu, pak
     použije hodnotu spočítanou jako <posn> modulo velikost seznamu.
  */
  {
    ArrayList imsList = (ArrayList) imagesMap.get(name);
    if (imsList == null) {
      System.out.println("Pod daným názvem nejsou žádné obrázky: " + name);  
      return null;
    }

    int size = imsList.size();
    if (posn < 0) {
      // System.out.println("Obrázek " + name + " se na pozici " + posn +
      //                      "nenachází; vracím obrázek na pozici 0"); 
      return (BufferedImage) imsList.get(0);   // vrať první obrázek
    }
    else if (posn >= size) {
      // System.out.println("Obrázek " + name + " se na dané pozici nenachází: " + posn); 
      int newPosn = posn % size;   // modulo
      // System.out.println("Vracím obrázek na pozici " + newPosn); 
      return (BufferedImage) imsList.get(newPosn);
    }

    // System.out.println("Vracím obrázek " + name + " na pozici " + posn);  
    return (BufferedImage) imsList.get(posn);
  }  // konec metody: getImage() s parametrem pozice;



  public BufferedImage getImage(String name, String fnmPrefix)
  /* Poskytuje obrázek spojený s názvem <name> ve skupině a prefixem
     <fnmPrefix> jména souboru.
  */
  {
    ArrayList imsList = (ArrayList) imagesMap.get(name);
    if (imsList == null) {
      System.out.println("Pod daným názvem nejsou žádné obrázky: " + name);  
      return null;
    }

    int posn = getGroupPosition(name, fnmPrefix);
    if (posn < 0) {
      // System.out.println("Vracím obrázek na pozici 0"); 
      return (BufferedImage) imsList.get(0);   // vrať první obrázek
    }

    // System.out.println("Vracím obrázek " + name + 
    //                        " s párovým jménem " + fnmPrefix);  
    return (BufferedImage) imsList.get(posn);
  }  // konec metody: getImage() s parametrem prefix jména souboru



  private int getGroupPosition(String name, String fnmPrefix)
  /* Prohledá hašovací tabulku na název <name> hledaje prefix <fnmPrefix>.
     Vrátí pozici skupiny v seznamu nebo -1.
  */
  {
    ArrayList groupNames = (ArrayList) gNamesMap.get(name);
    if (groupNames == null) {
      System.out.println("Pro dané jméno neexistuje skupina: " + name);  
      return -1;
    }

    String nm;
    for (int i=0; i < groupNames.size(); i++) {
      nm = (String) groupNames.get(i);
      if (nm.equals(fnmPrefix))
        return i;          // pozice prefixu <fnmPrefix> v seznamu jmen
    }

    System.out.println("Žádné jméno skupiny " + fnmPrefix + 
                  " pro daný název název neexistuje: " + name);  
    return -1;
  }  // konec metody: getGroupPosition()



  public ArrayList getImages(String name)
  // vrátí všechny objekty typu BufferedImage daného jména
  {
    ArrayList imsList = (ArrayList) imagesMap.get(name);
    if (imsList == null) {
      System.out.println("No image(s) stored under " + name);  
      return null;
    }

    System.out.println("Returning all images stored under " + name);  
    return imsList;
  }  // konec metody: getImages();


  public boolean isLoaded(String name)
  // je <name> klíčem hašovací tabulky imagesMap?
  {
    ArrayList imsList = (ArrayList) imagesMap.get(name);
    if (imsList == null)
      return false;
    return true;
  }  // konec metody: isLoaded()


  public int numImages(String name)
  // kolik obrázků je uloženo po názvem <name>?
  {
    ArrayList imsList = (ArrayList) imagesMap.get(name);
    if (imsList == null) {
      System.out.println("Pod daným názvem se nenachází žádný obrázek" + name);  
      return 0;
    }
    return imsList.size();
  } // konec metody: numImages()


  // ------------------- Načtení vlastního obrázku ------------------

  /* Máme tu tři verze metody loadImage(). Pro načítání pužívají tyto metody:
           ImageIO   // preferovaný přístup
           ImageIcon
           Image
     U posledních dvou přístupů předpokládáme, že kopie objektu typu 
     BufferedImage vyžaduje alfa-kanál.
  */

   public BufferedImage loadImage(String fnm) 
   /* Načte obrázek ze souboru <fnm> a vrátí jej jako objekt typu BufferedImage,
      který je kompatibilní s používaným grafickým zařízením.
      Používá třídu ImageIO.
   */
   {
     try {
       BufferedImage im =  ImageIO.read( 
                      getClass().getResource(IMAGE_DIR + fnm) );
       // Obrázek vrácený z ImageIO v rámci J2SE <= 1.4.2 _není_
       // řízeným obrázkem, ovšem po zkopírování již _je_!

       int transparency = im.getColorModel().getTransparency();
       BufferedImage copy =  gc.createCompatibleImage(
                                im.getWidth(), im.getHeight(),
		                        transparency );
       // vytvoř grafický kontext
       Graphics2D g2d = copy.createGraphics();
       // g2d.setComposite(AlphaComposite.Src);

       // reportTransparency(IMAGE_DIR + fnm, transparency);

       // zpokíruj obrázek
       g2d.drawImage(im,0,0,null);
       g2d.dispose();
       return copy;
     } 
     catch(IOException e) {
       System.out.println("Chyba při načítání obrázku " +
                     IMAGE_DIR + "/" + fnm + ":\n" + e); 
       return null;
     }
  } // konec metody: loadImage() využívající třídu ImageIO


  private void reportTransparency(String fnm, int transparency)
  {
    System.out.print(fnm + " průhlednost: ");
    switch(transparency) {
      case Transparency.OPAQUE:
        System.out.println("neprůhledné");
        break;
      case Transparency.BITMASK:
        System.out.println("bitová maska");
        break;
      case Transparency.TRANSLUCENT:
        System.out.println("průsvitná");
        break;
      default:
        System.out.println("neznámá");
        break;
    } // konec: switch
  }  // konec metody: reportTransparency()



  private BufferedImage loadImage2(String fnm)
   /* Načte obrázek ze souboru <fnm> a vrátí jej jako objekt typu BufferedImage.
      Používá třídu ImageIcon.
   */
  { ImageIcon imIcon = new ImageIcon(
                      getClass().getResource(IMAGE_DIR + fnm) );
    if (imIcon == null)
      return null;

    int width = imIcon.getIconWidth();
    int height = imIcon.getIconHeight();
    Image im = imIcon.getImage();

    return makeBIM(im, width, height);
  }  // konec metody: loadImage() využívající třídu ImageIcon


  private BufferedImage makeBIM(Image im, int width, int height)
  // Vytvoří kopii typu BufferImage pro objekt im, 
  // předmokládáme existenci alfa kanálu
  {
    BufferedImage copy = new BufferedImage(width, height, 
                                        BufferedImage.TYPE_INT_ARGB);
    // vytvoř grafický kontext
     Graphics2D g2d = copy.createGraphics();
    // g2d.setComposite(AlphaComposite.Src);

    // zpokíruj obrázek
    g2d.drawImage(im,0,0,null);
    g2d.dispose();
    return copy;
  }  // konec metody: makeBIM()



  public BufferedImage loadImage3(String fnm) 
  /* Načte obrázek ze souboru <fnm> a vrátí jej jako objekt typu BufferedImage.
     Používá třídu Image.
  */
  { Image im = readImage(fnm);
    if (im == null)
      return null;

    int width = im.getWidth( null );
    int height = im.getHeight( null );

    return makeBIM(im, width, height);
  } // konec metody: loadImage() využívající Image


  private Image readImage(String fnm)
  // načte obrázek a počká, dokud není celý skutečně nenačte (nestáhne)
  {
    Image image = Toolkit.getDefaultToolkit().getImage(
                     getClass().getResource(IMAGE_DIR + fnm) );
    MediaTracker imageTracker = new MediaTracker( new JPanel() );

    imageTracker.addImage(image, 0);
    try {
      imageTracker.waitForID(0);
    }
    catch (InterruptedException e) {
      return null;
    }
    if (imageTracker.isErrorID(0))
      return null;
    return image;
  } // konec metody: readImage()



  public BufferedImage[] loadStripImageArray(String fnm, int number) 
  /* Extrahuje individuální obrázky ze souboru <fnm> s pásmem obrázků.
     Předpokládáme, že obrázky jsou uloženy v jediném řádku, a že jejich
     počet je <number>. Obrázky jsou vráceny jako pole objektů typu BufferedImage.
  */
  {
    if (number <= 0) {
      System.out.println("parametr number <= 0; vracím null");
      return null;
    }

    BufferedImage stripIm;
    if ((stripIm = loadImage(fnm)) == null) {
      System.out.println("Vracím null");
      return null;
    }

    int imWidth = (int) stripIm.getWidth() / number;
    int height = stripIm.getHeight();
    int transparency = stripIm.getColorModel().getTransparency();

    BufferedImage[] strip = new BufferedImage[number];
    Graphics2D stripGC;

    // každý objekt typu BufferedImage ze souboru s pásmem 
    // uložíme do pole strip[]
    for (int i=0; i < number; i++) {
      strip[i] =  gc.createCompatibleImage(imWidth, height, transparency);
       
      // vytvoř grafický kontext
      stripGC = strip[i].createGraphics();
      // stripGC.setComposite(AlphaComposite.Src);

      // zpokíruj obrázek
      stripGC.drawImage(stripIm, 
                  0,0, imWidth,height,
                  i*imWidth,0, (i*imWidth)+imWidth,height,
                  null);
      stripGC.dispose();
    } 
    return strip;
  } // konec metody: loadStripImageArray()


}  // konec třídy: ImagesLoader
