/*
 * WYWOD W GRAMATYCE
 *
 * KOMPILACJA:
 *   javac Wywod.java
 * WYKONANIE:
 *   java Wywod nazwaPliku
 * argument  nazwaPliku  ma zawierac gramatyke; terminale i
 * nieterminale sa jednoznakowe, nieterminal po lewej stronie
 * produkcji musi byc litera (duza lub mala).  Przykladowa tresc
 * pliku:
 *
 *   +-----------+
 *   | S ->      |
 *   | S -> aSbb |
 *   +-----------+
 *
 * program zaczyna od nieterminalu poczatkowego (czyli lewej strony
 * produkcji numer 0) i wykonuje po jednym zastapieniu, za kazdym
 * razem pytajac, ktory nieterminal zastapic i wg ktorej produkcji
 */


import java.util.*;
import java.io.*;


class Zle extends Exception {
  // sygnalizacja bledu:
  public Zle(String komunikat) {
    System.out.println(
      "\n  " + Wywod.CZERWONE +
      komunikat +
      Wywod.ZWYKLE + "\n"
    );
  }
}


class Produkcja {
  private char lewaStr;
  private String prawaStr;

  // konstruktor produkcji:
  public Produkcja(char lS, String pS) {
    lewaStr = lS; prawaStr = pS;
  }

  // selektory:
  public char lewaStr() { return lewaStr; }
  public String prawaStr() { return prawaStr; }

  // wydruk:
  public String toString() {
    return  "" + lewaStr + " -> " + prawaStr;
  }
}


class Gramatyka {
  private ArrayList<Produkcja> gram = new ArrayList<Produkcja>();

  // tworzenie gramatyki przez dodawanie nowych produkcji:
  public void nowaProd(Produkcja pr) { gram.add(pr); }

  // selektor -- nieterminal poczatkowy gramatyki:
  public char poczatek() { return gram.get(0).lewaStr(); }

  // wydruk:
  public String toString() {
    String str = "";
    int licznik=0;
    for (Produkcja pr : gram) {
      str += "  " + licznik + ":  " + pr.toString() + "\n";
      licznik++;
    }
    return str;
  }

  // spis nieterminali w napisie:
  ArrayList<Character> nTerms(String napis) {
    ArrayList<Character> nT = new ArrayList<Character>();
    for (Produkcja pr : gram)
      if (napis.indexOf(pr.lewaStr()) >= 0)  nT.add(pr.lewaStr());
    return nT;
  }

  // zastosowanie gramatyki do napisu:
  public String zastosuj(String napis, int prod, int poz) throws Zle {
    /* stosuje do napisu  napis
     * produkcje o numerze  prod
     * do nieterminalu stojacego na pozycji  poz
     * zwraca napis wynikajacy z tego zastosowania
     */
    if (poz < 0)
      throw new Zle("Ujemna pozycja");
    if (0 > prod || prod >= gram.size())
      throw new Zle("Nie ma takiej produkcji");
    if (poz >= napis.length())
      throw new Zle("Za krotkie slowo");
    if (!nTerms(napis).contains(napis.charAt(poz)))
      throw new Zle(napis.charAt(poz) + " nie jest nieterminalem");
    if (napis.charAt(poz) != gram.get(prod).lewaStr())
      throw new Zle(
        "Produkcja  " +
        gram.get(prod).toString() +
        "  nie daje sie zastosowac na tej pozycji"
      );
    String napisNowy = "";
    for (int i=0; i<poz; i++)
      napisNowy += napis.charAt(i);
    for (int i=0; i<gram.get(prod).prawaStr().length(); i++)
      napisNowy += gram.get(prod).prawaStr().charAt(i);
    for (int i=poz+1; i<napis.length(); i++)
      napisNowy += napis.charAt(i);
    return napisNowy;
  }
}


class Wywod {

  public static final String
    ZWYKLE = "\033[0m",
    ZOLTE_TLO = "\033[30;103m",
    CZERWONE = "\033[31m";

  /*
   gram ::= prod* 'EOF'
   prod ::= symb '->' symb* 'EOLN'
  */
  public static Gramatyka wczyt(String nazwaPliku)
      throws Zle, FileNotFoundException {
    // wczytuje gramatyke z pliku o podanej nazwie
    Gramatyka gram = new Gramatyka();
    Scanner czyt = new Scanner(new File(nazwaPliku));
    int licznik = 0;
    while (czyt.hasNextLine()) {
      String wiersz = czyt.nextLine().strip();
      if (wiersz.length() < 3)
        throw new Zle("Produkcja  " + licznik + "  za krotka");
      char ls = wiersz.charAt(0);
      if (!('A'<=ls && ls<='Z' || 'a'<=ls && ls<='z'))
        throw new Zle(
          "W produkcji  " + licznik + "  brak lewej strony"
        );
      String ps = wiersz.substring(1).strip();
      if (!ps.substring(0,2).equals("->"))
        throw new Zle("W produkcji  " + licznik + "  brak '->'");
      ps = ps.substring(2).strip();
      gram.nowaProd(new Produkcja(ls, ps));
      licznik++;
    }
    return gram;
  }

  public static void main(String[] args)
      throws Zle, FileNotFoundException {
    Gramatyka gramat = wczyt(args[0]);
    System.out.print("\nGRAMATYKA:\n" + gramat);
    Scanner czyt = new Scanner(System.in);
    String slowo = "" + gramat.poczatek();
    while (gramat.nTerms(slowo).size() > 0) {
      System.out.print("\nSLOWO: ");
      System.out.print(ZOLTE_TLO + slowo + ZWYKLE);
      System.out.print("\n       ");
      for (int i=0; i<slowo.length(); i++)
        System.out.print(CZERWONE + i%10 + ZWYKLE);
      System.out.print("\nZASTAPIC ZNAK NR: ");
      int nrZnaku = czyt.nextInt();
      System.out.print("PRODUKCJA NR: ");
      int nrProd = czyt.nextInt();
      slowo = gramat.zastosuj(slowo,nrProd, nrZnaku);
    }
    System.out.print("\nSLOWO WYPROWADZONE: ");
    System.out.println(
      "\033[30;103m" +
      slowo +
      "\033[0m\n"
    );
  }
}
