package com.orientechnologies.common.console;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.orientechnologies.common.log.OLogManager;

public class TTYConsoleReader implements OConsoleReader {

  private static final String HISTORY_FILE_NAME   = ".orientdb_history";

  private static int          MAX_HISTORY_ENTRIES = 50;

  public static int           END_CHAR            = 70;

  public static int           BEGIN_CHAR          = 72;

  public static int           DEL_CHAR            = 126;

  public static int           DOWN_CHAR           = 66;

  public static int           UP_CHAR             = 65;

  public static int           RIGHT_CHAR          = 67;

  public static int           LEFT_CHAR           = 68;

  public static int           HORIZONTAL_TAB_CHAR = 9;

  public static int           VERTICAL_TAB_CHAR   = 11;

  public static int           BACKSPACE_CHAR      = 127;

  public static int           NEW_LINE_CHAR       = 10;

  public static int           UNIT_SEPARATOR_CHAR = 31;

  protected int               currentPos          = 0;

  protected List<String>      history             = new ArrayList<String>();

  protected String            historyBuffer;

  protected Reader            inStream;

  protected PrintStream       outStream;

  public TTYConsoleReader() {
    File file = getHistoryFile(true);
    BufferedReader reader;
    try {
      reader = new BufferedReader(new FileReader(file));
      String historyEntry = reader.readLine();
      while (historyEntry != null) {
        history.add(historyEntry);
        historyEntry = reader.readLine();
      }
      if (System.getProperty("file.encoding") != null) {
        inStream = new InputStreamReader(System.in, System.getProperty("file.encoding"));
        outStream = new PrintStream(System.out, false, System.getProperty("file.encoding"));
      } else {
        inStream = new InputStreamReader(System.in);
        outStream = System.out;
      }
    } catch (FileNotFoundException fnfe) {
      OLogManager.instance().error(this, "History file not found", fnfe, "");
    } catch (IOException ioe) {
      OLogManager.instance().error(this, "Error reading history file.", ioe, "");
    }
  }

  protected OConsoleApplication console;

  public String readLine() {
    String consoleInput = "";
    try {
      StringBuffer buffer = new StringBuffer();
      currentPos = 0;
      historyBuffer = null;
      int historyNum = history.size();
      boolean hintedHistory = false;
      while (true) {
        boolean escape = false;
        boolean ctrl = false;
        int next = inStream.read();
        if (next == 27) {
          escape = true;
          inStream.read();
          next = inStream.read();
        }
        if (escape) {
          if (next == 49) {
            inStream.read();
            next = inStream.read();
          }
          if (next == 53) {
            ctrl = true;
            next = inStream.read();
          }
          if (ctrl) {
            if (next == RIGHT_CHAR) {
              currentPos = buffer.indexOf(" ", currentPos) + 1;
              if (currentPos == 0)
                currentPos = buffer.length();
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            } else if (next == LEFT_CHAR) {
              if (currentPos > 1 && currentPos < buffer.length() && buffer.charAt(currentPos - 1) == ' ') {
                currentPos = buffer.lastIndexOf(" ", (currentPos - 2)) + 1;
              } else {
                currentPos = buffer.lastIndexOf(" ", currentPos) + 1;
              }
              if (currentPos < 0)
                currentPos = 0;
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            } else {
            }
          } else {
            if (next == UP_CHAR && !history.isEmpty()) {
              if (history.size() > 0) { // UP
                StringBuffer cleaner = new StringBuffer();
                for (int i = 0; i < buffer.length(); i++) {
                  cleaner.append(" ");
                }
                rewriteConsole(cleaner, true);
                if (!hintedHistory && (historyNum == history.size() || !buffer.toString().equals(history.get(historyNum)))) {
                  if (buffer.length() > 0) {
                    hintedHistory = true;
                    historyBuffer = buffer.toString();
                  } else {
                    historyBuffer = null;
                  }
                }
                historyNum = getHintedHistoryIndexUp(historyNum);
                if (historyNum > -1) {
                  buffer = new StringBuffer(history.get(historyNum));
                } else {
                  buffer = new StringBuffer(historyBuffer);
                }
                currentPos = buffer.length();
                rewriteConsole(buffer, false);
                // writeHistory(historyNum);
              }
            } else if (next == DOWN_CHAR && !history.isEmpty()) { // DOWN
              if (history.size() > 0) {
                StringBuffer cleaner = new StringBuffer();
                for (int i = 0; i < buffer.length(); i++) {
                  cleaner.append(" ");
                }
                rewriteConsole(cleaner, true);

                historyNum = getHintedHistoryIndexDown(historyNum);
                if (historyNum == history.size()) {
                  if (historyBuffer != null) {
                    buffer = new StringBuffer(historyBuffer);
                  } else {
                    buffer = new StringBuffer("");
                  }
                } else {
                  buffer = new StringBuffer(history.get(historyNum));
                }
                currentPos = buffer.length();
                rewriteConsole(buffer, false);
                // writeHistory(historyNum);
              }
            } else if (next == RIGHT_CHAR) {
              if (currentPos < buffer.length()) {
                currentPos++;
                StringBuffer cleaner = new StringBuffer();
                for (int i = 0; i < buffer.length(); i++) {
                  cleaner.append(" ");
                }
                rewriteConsole(cleaner, true);
                rewriteConsole(buffer, false);
              }
            } else if (next == LEFT_CHAR) {
              if (currentPos > 0) {
                currentPos--;
                StringBuffer cleaner = new StringBuffer();
                for (int i = 0; i < buffer.length(); i++) {
                  cleaner.append(" ");
                }
                rewriteConsole(cleaner, true);
                rewriteConsole(buffer, false);
              }
            } else if (next == END_CHAR) {
              currentPos = buffer.length();
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            } else if (next == BEGIN_CHAR) {
              currentPos = 0;
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            } else {
            }
          }
        } else {
          if (next == NEW_LINE_CHAR) {
            outStream.println();
            break;
          } else if (next == BACKSPACE_CHAR) {
            if (buffer.length() > 0 && currentPos > 0) {
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              buffer.deleteCharAt(currentPos - 1);
              currentPos--;
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            }
          } else if (next == DEL_CHAR) {
            if (buffer.length() > 0 && currentPos >= 0 && currentPos < buffer.length()) {
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              buffer.deleteCharAt(currentPos);
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            }
          } else if (next == HORIZONTAL_TAB_CHAR) {
            StringBuffer cleaner = new StringBuffer();
            for (int i = 0; i < buffer.length(); i++) {
              cleaner.append(" ");
            }
            buffer = writeHint(buffer);
            rewriteConsole(cleaner, true);
            rewriteConsole(buffer, false);
            currentPos = buffer.length();
          } else {
            if ((next > UNIT_SEPARATOR_CHAR && next < BACKSPACE_CHAR) || next > BACKSPACE_CHAR) {
              StringBuffer cleaner = new StringBuffer();
              for (int i = 0; i < buffer.length(); i++) {
                cleaner.append(" ");
              }
              if (currentPos == buffer.length()) {
                buffer.append((char) next);
              } else {
                buffer.insert(currentPos, (char) next);
              }
              currentPos++;
              rewriteConsole(cleaner, true);
              rewriteConsole(buffer, false);
            } else {
              outStream.println();
              outStream.print(buffer);
            }
          }
          historyNum = history.size();
          hintedHistory = false;
        }
      }
      consoleInput = buffer.toString();
      history.remove(consoleInput);
      history.add(consoleInput);
      historyNum = history.size();
      writeHistory(historyNum);
    } catch (IOException e) {
      return null;
    }
    if (consoleInput.equals("clear")) {
      outStream.flush();
      for (int i = 0; i < 150; i++) {
        outStream.println();
      }
      outStream.print("\r");
      outStream.print(console.getPrompt());
      return readLine();
    } else {
      return consoleInput;
    }
  }

  private void writeHistory(int historyNum) throws IOException {
    if (historyNum <= MAX_HISTORY_ENTRIES) {
      File historyFile = getHistoryFile(false);
      BufferedWriter writer = new BufferedWriter(new FileWriter(historyFile));
      try {
        for (String historyEntry : history) {
          writer.write(historyEntry);
          writer.newLine();
        }
      } finally {
        writer.flush();
        writer.close();
      }
    } else {
      File historyFile = getHistoryFile(false);
      BufferedWriter writer = new BufferedWriter(new FileWriter(historyFile));
      try {
        for (String historyEntry : history.subList(historyNum - MAX_HISTORY_ENTRIES - 1, historyNum - 1)) {
          writer.write(historyEntry);
          writer.newLine();
        }
      } finally {
        writer.flush();
        writer.close();
      }
    }
  }

  private StringBuffer writeHint(StringBuffer buffer) {
    List<String> suggestions = new ArrayList<String>();
    for (Method method : console.getConsoleMethods().keySet()) {
      String command = OConsoleApplication.getClearName(method.getName());
      if (command.startsWith(buffer.toString())) {
        suggestions.add(command);
      }
    }
    if (suggestions.size() > 1) {
      StringBuffer hintBuffer = new StringBuffer();
      String[] bufferComponents = buffer.toString().split(" ");
      String[] suggestionComponents;
      Set<String> bufferPart = new HashSet<String>();
      String suggestionPart = null;
      boolean appendSpace = true;
      for (String suggestion : suggestions) {
        suggestionComponents = suggestion.split(" ");
        hintBuffer.append("* " + suggestion + " ");
        hintBuffer.append("\n");
        suggestionPart = "";
        if (bufferComponents.length == 0 || buffer.length() == 0) {
          suggestionPart = null;
        } else if (bufferComponents.length == 1) {
          bufferPart.add(suggestionComponents[0]);
          if (bufferPart.size() > 1) {
            suggestionPart = bufferComponents[0];
            appendSpace = false;
          } else {
            suggestionPart = suggestionComponents[0];
          }
        } else {
          bufferPart.add(suggestionComponents[bufferComponents.length - 1]);
          if (bufferPart.size() > 1) {
            for (int i = 0; i < bufferComponents.length; i++) {
              suggestionPart += bufferComponents[i];
              if (i < (bufferComponents.length - 1)) {
                suggestionPart += " ";
              }
              appendSpace = false;
            }
          } else {
            for (int i = 0; i < suggestionComponents.length; i++) {
              suggestionPart += suggestionComponents[i] + " ";
            }
          }
        }
      }
      if (suggestionPart != null) {
        buffer = new StringBuffer();
        buffer.append(suggestionPart);
        if (appendSpace) {
          buffer.append(" ");
        }
      }
      hintBuffer.append("-----------------------------\n");
      rewriteHintConsole(hintBuffer);
    } else if (suggestions.size() > 0) {
      buffer = new StringBuffer();
      buffer.append(suggestions.get(0));
      buffer.append(" ");
    }
    return buffer;
  }

  public void setConsole(OConsoleApplication iConsole) {
    console = iConsole;
  }

  public OConsoleApplication getConsole() {
    return console;
  }

  private void rewriteConsole(StringBuffer buffer, boolean cleaner) {
    outStream.print("\r");
    outStream.print(console.getPrompt());
    if (currentPos < buffer.length() && buffer.length() > 0 && !cleaner) {
      outStream.print("\033[0m" + buffer.substring(0, currentPos) + "\033[0;30;47m" + buffer.substring(currentPos, currentPos + 1)
          + "\033[0m" + buffer.substring(currentPos + 1) + "\033[0m");
    } else {
      outStream.print(buffer);
    }
  }

  private void rewriteHintConsole(StringBuffer buffer) {
    outStream.print("\r");
    outStream.print(buffer);
  }

  private int getHintedHistoryIndexUp(int historyNum) {
    if (historyBuffer != null && !historyBuffer.equals("")) {
      for (int i = (historyNum - 1); i >= 0; i--) {
        if (history.get(i).startsWith(historyBuffer)) {
          return i;
        }
      }
      return -1;
    }
    return historyNum > 0 ? (historyNum - 1) : 0;
  }

  private int getHintedHistoryIndexDown(int historyNum) throws IOException {
    if (historyBuffer != null && !historyBuffer.equals("")) {
      for (int i = historyNum + 1; i < history.size(); i++) {
        if (history.get(i).startsWith(historyBuffer)) {
          return i;
        }
      }
      return history.size();
    }
    return historyNum < history.size() ? (historyNum + 1) : history.size();
  }

  private File getHistoryFile(boolean read) {
    File file = new File(HISTORY_FILE_NAME);
    if (!file.exists()) {
      try {
        file.createNewFile();
      } catch (IOException ioe) {
        OLogManager.instance().error(this, "Error creating history file.", ioe, "");
      }
    } else if (!read) {
      file.delete();
      try {
        file.createNewFile();
      } catch (IOException ioe) {
        OLogManager.instance().error(this, "Error creating history file.", ioe, "");
      }
    }
    return file;
  }

}
