
// Utils.java
// Andrew Davison, ad@coe.psu.ac.th, March 2023

/* The utilities are split into four groups:

     * versions of methods that handle exceptions so your
       code doesn't have to;

     * functions for drawing text to the screen

     * input functions

     * functions for using text images

   These methods are meant to be used with Lanterna's Screen,
   not the lower-level Terminal.

   Uses Lanterna 3
   https://github.com/mabe02/lanterna

   Usage (Windows):
     > compile.bat Utils.java

   Usage (Linux):
     > ./compile.sh Utils.java

*/


import java.io.*;
import java.nio.file.*;
import java.nio.charset.Charset;
import java.util.*;

import com.googlecode.lanterna.*;
import com.googlecode.lanterna.graphics.*;
import com.googlecode.lanterna.input.*;
import com.googlecode.lanterna.screen.*;
import com.googlecode.lanterna.terminal.*;
import com.googlecode.lanterna.gui2.*;



public class Utils 
{
  public static final int ESC = 27;  // ASCII escape



  // ----- versions of methods that handle exceptions ---------


  public static Terminal createTerminal()
  {  return createTerminal("UTF8");  }


  public static Terminal createTerminal(String charset)
  // make explicit the character set used;
  // possibilities include "UTF8" and "US-ASCII"
  {
    Terminal term = null;
    try {
      term = new DefaultTerminalFactory(System.out, System.in, 
                       Charset.forName(charset)).createTerminal();
    }
    catch (IOException e) {   
      System.out.println("Could not create a " + charset + " terminal");
      System.exit(1);
    }
    return term;
  } /* createTerminal */


  public static Screen createScreen()
  {
    Screen scr = null;
    try {
      //scr = new DefaultTerminalFactory().createScreen();
      scr = new DefaultTerminalFactory(System.out, System.in, 
                      Charset.forName("UTF8")).createScreen();
      scr.startScreen();
    }
    catch (IOException e) {   
      System.out.println("Could not create a Screen object");
      System.exit(1);
    }
    return scr;
  } /* createScreen */


  public static void refresh(Screen scr)
  {
    try {
      scr.refresh();
    }
    catch (IOException e) 
    {   System.err.println(e);  }
  } 


  public static void close(Screen scr)
  {
    try {
      scr.close();
    }
    catch (IOException e) 
    {   System.err.println(e);  }
  } 


  // -------------- place text on screen --------------------


  public static void putString(Screen scr, int x, int y, String msg)
  {  putString(scr, x, y, msg, null, null);  }


  public static void putString(Screen scr, int x, int y, 
                          String msg, TextColor.ANSI fgColor)
  {  putString(scr, x, y, msg, fgColor, null);  }


  public static void putString(Screen scr, int x, int y, String msg, 
                      TextColor.ANSI fgColor, TextColor.ANSI bgColor)
  {
    TextGraphics tg = scr.newTextGraphics();
    if (fgColor != null)
      tg.setForegroundColor(fgColor);
    if (bgColor != null)
      tg.setBackgroundColor(bgColor);
    tg.putString(x, y, msg);
    refresh(scr);    // note the refresh
  }  // end of putString()


  public static void clearLine(Screen scr, int x, int y, int len)
  {
    int numCols = scr.getTerminalSize().getColumns();
    int maxCol = ((x+len) > numCols) ? numCols : (x+len);

    TextCharacter tch = new TextCharacter(' ');
    for (int col=x; col < maxCol; col++)
      scr.setCharacter(col, y, tch);
    refresh(scr);    // note the refresh
  }  // end of clearLine()


  public static void clearLine(Screen scr, int x, int y)
  {
    int numCols = scr.getTerminalSize().getColumns();
    TextCharacter tch = new TextCharacter(' ');
    for (int col=x; col < numCols; col++)
      scr.setCharacter(col, y, tch);
    refresh(scr);
  }  // end of clearLine()



  public static void setCharacter(Screen scr, int x, int y, char ch)
  {
    TextCharacter tch = new TextCharacter(ch);
    scr.setCharacter(x, y, tch);      // **NO** refresh after write
  }


  public static void setCharacter(Screen scr, int x, int y, 
                                    char ch, TextColor.ANSI fgColor)
  {  
    TextCharacter tch = new TextCharacter(ch, fgColor, 
                                     TextColor.ANSI.DEFAULT);
    scr.setCharacter(x, y, tch);     // **NO** refresh after write
  }


  public static void setCharacter(Screen scr, int x, int y, char ch, 
                      TextColor.ANSI fgColor, TextColor.ANSI bgColor)
  {
    TextCharacter tch = new TextCharacter(ch, fgColor, bgColor);
    scr.setCharacter(x, y, tch);
  }


  public static void setCell(Screen scr, int x, int y,  
                      TextColor.ANSI color)
  // fill the cell with a solid color by printing a space
  {
    TextCharacter tch = new TextCharacter(' ', 
                                  TextColor.ANSI.DEFAULT, color);
    scr.setCharacter(x, y, tch);
  }



  public static void fill(Screen scr, TextColor.ANSI color)
  // fill the entire window with colored spaces
  {
    TextGraphics tg = scr.newTextGraphics();
    tg.setBackgroundColor(color);
    tg.fillRectangle(TerminalPosition.TOP_LEFT_CORNER,
                     tg.getSize(), ' ');
    refresh(scr);
  } // end of fill()




  public static void header(Screen scr, String msg)
  // print a centered message at the top of the window
  {
    TerminalSize termSz = scr.getTerminalSize();
    int numCols = termSz.getColumns();
    int xOffset = (numCols/2) - (msg.length()/2);
    putString(scr, xOffset, 1, msg, TextColor.ANSI.WHITE_BRIGHT,
                                      TextColor.ANSI.BLACK);
  }


  public static void statusLine(Screen scr, String msg)
  // print a message on the bottom row of the window
  {
    TerminalSize termSz = scr.getTerminalSize();
    int numRows = termSz.getRows();
    putString(scr, 1, numRows-1, msg, TextColor.ANSI.WHITE_BRIGHT,
                                      TextColor.ANSI.BLACK);
  }


  public static String list2Str(List<Character> charsList)
  // convert a list of chars to a string
  {
    StringBuilder sb = new StringBuilder();
    for (Character ch : charsList)
      sb.append(ch);
    return sb.toString();
  }


  public static TextColor.ANSI randomColor()
  {
    int numColors = TextColor.ANSI.values().length;
    int pos = (int)(Math.random() * numColors);
    return TextColor.ANSI.values()[pos];
  }


  public static TextColor.ANSI randomColor(TextColor.ANSI notColor)
  /* generate a random color, but excluding notColor as
     a possibility */
  {
    int numColors = TextColor.ANSI.values().length;
    while(true) {
      int pos = (int)(Math.random() * numColors);
      TextColor.ANSI color = TextColor.ANSI.values()[pos];
      if (color != notColor)
        return color;
    }
  }


  public static void messageBox(Screen scr, 
                                String msg, int x, int y)
  /* print a message inside a fancy box with its top-left
     corner located at (x,y) in the window
  */
  {
    // set box coordinates
    TerminalPosition topLeft = new TerminalPosition(x, y);
    TerminalSize size = new TerminalSize(msg.length() + 2, 3);
    TerminalPosition topRight =
         topLeft.withRelativeColumn(size.getColumns() - 1);

    // draw the box
    TextGraphics tg = scr.newTextGraphics();
    tg.fillRectangle(topLeft, size, ' ');

    // Draw upper and lower horizontal lines
    tg.drawLine( topLeft.withRelativeColumn(1),
                 topLeft.withRelativeColumn(size.getColumns() - 2),
                 Symbols.DOUBLE_LINE_HORIZONTAL);

    tg.drawLine( topLeft.withRelativeRow(2).withRelativeColumn(1),
                 topLeft.withRelativeRow(2).withRelativeColumn(
                                            size.getColumns() - 2),
                 Symbols.DOUBLE_LINE_HORIZONTAL);

    // draw the edges and vertical lines
    tg.setCharacter(topLeft, Symbols.DOUBLE_LINE_TOP_LEFT_CORNER);
    tg.setCharacter(topLeft.withRelativeRow(1),
                              Symbols.DOUBLE_LINE_VERTICAL);
    tg.setCharacter(topLeft.withRelativeRow(2),
                              Symbols.DOUBLE_LINE_BOTTOM_LEFT_CORNER);

    tg.setCharacter(topRight, Symbols.DOUBLE_LINE_TOP_RIGHT_CORNER);
    tg.setCharacter(topRight.withRelativeRow(1),
                              Symbols.DOUBLE_LINE_VERTICAL);
    tg.setCharacter(topRight.withRelativeRow(2),
                              Symbols.DOUBLE_LINE_BOTTOM_RIGHT_CORNER);

    // put the text inside the box
    tg.putString(topLeft.withRelative(1, 1), msg);
  }  // end of messageBox()



  // ----------------- reading input ------------------


  public static KeyStroke pollInput(Screen scr)
  {
    KeyStroke ks = null;
    try {
      ks = scr.pollInput();
    }
    catch (IOException e) 
    {   System.err.println(e);  }
    return ks;
  } /* pollInput */


  public static KeyStroke readInput(Screen scr)
  {
    KeyStroke ks = null;
    try {
      ks = scr.readInput();
      if (ks.getKeyType() == KeyType.EOF)
        return ks;

      // remove rest of input buffer;
      /* this prevents the buffer from filling wth key presses
         if the user holds a key down for too long */
      while (pollInput(scr) != null) {}
    }
    catch (IOException e) 
    {   System.err.println(e);  }
    return ks;
  } /* readInput */



  public static char readChar(Screen scr)
  // get a character, or ESCAPE
  {
    Character ch = readInput(scr).getCharacter();
    if (ch == null)
      return ESC; 
    else
      return ch.charValue();
  } /* readChar */


  public static int readInt(Screen scr)
  // get an integer, or -1
  {
    Character ch = readInput(scr).getCharacter();
    if (ch == null)
      return -1;
    else
      return Character.getNumericValue(ch.charValue());
  } /* readInt */


  public static boolean isChar(KeyStroke ks)
  {
    if (ks == null)
      return false;
    return (ks.getCharacter() != null);
  }


  public static boolean hasQuit(KeyStroke ks)
  {
    if (ks == null)
      return false;

    // did user press escape or close the window? 
    return ((ks.getKeyType() == KeyType.Escape) ||
            (ks.getKeyType() == KeyType.EOF) );
  }  // end of hasQuit()


  public static void waitQuit(Screen scr)
  // wait until the user presses <escape> (or the window is closed)
  {
    statusLine(scr, "Press <esc> to exit...");
    KeyStroke key = readInput(scr);
    while (!hasQuit(key))
      key = readInput(scr);
  }  // end of hasQuit()


  public static void pause(int ms)
  {
    try {
      Thread.sleep(ms);
    }
    catch(InterruptedException ex) { }
  }  // end of pause()



  // ------------------ text images ---------------------


  public static ImageComponent makeIMC(String[] imStr)
  // make an image component from a string array
  {
    ImageComponent imc = new ImageComponent();
    imc.setTextImage( makeTextImage(imStr));
    return imc;
  }


  public static ImageComponent makeIMC(String fnm)
  // load an image component from a file
  {
    ImageComponent imc = new ImageComponent();
    imc.setTextImage( loadTextImage(fnm));
    return imc;
  }


  public static TextImage loadTextImage(String fnm)
  {
    try {
      List<String> lines = new ArrayList<String>();
      lines = Files.readAllLines(Paths.get(fnm));
      String[] linesArr = lines.toArray(new String[0]);
      System.err.println("Loaded text image from " + fnm);
      return makeTextImage(linesArr);
    }
    catch(IOException e)
    {  System.err.println("Unable to load " + fnm);  }
 
    return null;
  }  // end of loadTextImage()



  public static TextImage makeTextImage(String[] imStr)
  {
    TerminalSize sz = new TerminalSize(imStr[0].length(),
                                       imStr.length);
    TextImage tim = new BasicTextImage(sz);
    for (int row = 0; row < imStr.length; row++) {
      String line = imStr[row];
      for (int col = 0; col < line.length(); col++) {
        char ch = line.charAt(col);
        TextCharacter textCh = new TextCharacter(ch);
        tim.setCharacterAt(col, row, textCh);
      }
    }
    return tim;
  }  // end of makeTextImage()



  public static void drawImage(Screen scr, int x, int y, TextImage im)
  {  drawImage(scr, x, y, im, null, null);  }


  public static void drawImage(Screen scr, int x, int y, 
                          TextImage im, TextColor.ANSI fgColor)
  {  drawImage(scr, x, y, im, fgColor, null);  }


  public static void drawImage(Screen scr, int x, int y, TextImage im, 
                      TextColor.ANSI fgColor, TextColor.ANSI bgColor)
  /* Note: I could not get this function to work, i.e.
     to draw a text image in different colors.
  */
  {
    TerminalPosition pos = new TerminalPosition(x, y);
    TextGraphics scrTG = scr.newTextGraphics(); 
    TextGraphics imTG = im.newTextGraphics();
    if (fgColor != null) {
      imTG.setForegroundColor(fgColor);
      scrTG.setForegroundColor(fgColor);
    }
    if (bgColor != null) {
      imTG.setBackgroundColor(bgColor);
      scrTG.setBackgroundColor(bgColor);
    }
    scrTG.drawImage(pos, im);
    refresh(scr);   // note: refresh carried out after the drawing
  }  // end of drawImage()

}  // end of Utils class
