public class Preferences extends PreferencesStorage{

    private static Preferences instance;

    private final String DEFAULT_EDITOR_FONT_FAMILY = "Monospace";
    private final int DEFAULT_EDITOR_FONT_SIZE = 11;
    private final int DEFAULT_EDITOR_RIGHT_MARGIN_POSITION = 80;
    private final int DEFAULT_EDITOR_TAB_WIDTH = 4;
    private final boolean DEFAULT_EDITOR_SHOWS_LINE_NUMBERS = true;
    private final boolean DEFAULT_EDITOR_SHOWS_RIGHT_MARGIN = true;
    private final boolean DEFAULT_EDITOR_HIGLIGHTS_CURRENT_LINE = true;
    private final String DEFAULT_EDITOR_SCHEME = "glossa";

    public static Preferences getInstance(){
        if(instance == null){
            instance = new Preferences();
        }
        return instance;
    }

    private Preferences(){
        super("lingua");
    }

    @Override
    public void setupDefaultValues(){
        setValue("editor-font-family", DEFAULT_EDITOR_FONT_FAMILY);
        setValue("editor-font-size", String.valueOf(DEFAULT_EDITOR_FONT_SIZE));
        setValue("editor-shows-line-numbers", String.valueOf(DEFAULT_EDITOR_SHOWS_LINE_NUMBERS));
        setValue("editor-highlights-current-line", String.valueOf(DEFAULT_EDITOR_HIGLIGHTS_CURRENT_LINE));
        setValue("editor-shows-right-margin", String.valueOf(DEFAULT_EDITOR_SHOWS_RIGHT_MARGIN));
        setValue("editor-right-margin-position", String.valueOf(DEFAULT_EDITOR_RIGHT_MARGIN_POSITION));
        setValue("editor-tab-width", String.valueOf(DEFAULT_EDITOR_TAB_WIDTH));
        setValue("editor-scheme", DEFAULT_EDITOR_SCHEME);
    }

    public void apply(){
        String fontDescription = getPangoCompatibleFontDescriptionString();
        InputFileWindow.getInstance().setFont(fontDescription);

        RuntimeWindow.getInstance().setFont(fontDescription);

        Editor ed = Editor.getInstance();
        ed.setFont(fontDescription);
        ed.setShowLineNumbers(getEditorShowsLineNumbers());
        ed.setShowRightMargin(getEditorShowsRightMargin());
        ed.setRightMarginPosition(getEditorRightMarginPosition());
        ed.setTabWidth(getEditorTabWidth());
        ed.setHighlightCurrentLine(getEditorHighlightsCurrentLine());
        ed.getBuffer().setScheme(getEditorScheme());
    }

    public String getPangoCompatibleFontDescriptionString(){
        return getEditorFontFamily() + " " + getEditorFontSize();
    }

    /**
     * @return the editorFontFamily
     */
    public String getEditorFontFamily() {
        String result = getValue("editor-font-family");
        return result==null?DEFAULT_EDITOR_FONT_FAMILY:result;
    }

    /**
     * @param editorFontFamily the editorFontFamily to set
     */
    public void setEditorFontFamily(String editorFontFamily) {
        setValue("editor-font-family", editorFontFamily);
    }
    
    /**
     * @return the editorScheme or null for default
     */
    public String getEditorScheme() {
        String result = getValue("editor-scheme");
        return result==null?DEFAULT_EDITOR_SCHEME:result;
    }

    /**
     * @param editorScheme the editorFontFamily to set
     */
    public void setEditorScheme(String editorScheme) {
        setValue("editor-scheme", "".equals(editorScheme)?null:editorScheme);
    }

    /**
     * @return the editorFontSize
     */
    public int getEditorFontSize() {
        String result =  getValue("editor-font-size");
        return result==null?DEFAULT_EDITOR_FONT_SIZE:Integer.valueOf(result);
    }

    /**
     * @param editorFontSize the editorFontSize to set
     */
    public void setEditorFontSize(int editorFontSize) {
        setValue("editor-font-size", String.valueOf(editorFontSize));
    }

    /**
     * @return the editorShowsLineNumbers
     */
    public boolean getEditorShowsLineNumbers() {
        String result =  getValue("editor-shows-line-numbers");
        return result==null?DEFAULT_EDITOR_SHOWS_LINE_NUMBERS:Boolean.parseBoolean(result);
    }

    /**
     * @param editorShowsLineNumbers the editorShowsLineNumbers to set
     */
    public void setEditorShowsLineNumbers(boolean editorShowsLineNumbers) {
        setValue("editor-shows-line-numbers", String.valueOf(editorShowsLineNumbers));
    }

    /**
     * @return the editorHighlightsCurrentLine
     */
    public boolean getEditorHighlightsCurrentLine() {
        String result =  getValue("editor-highlights-current-line");
        return result==null?DEFAULT_EDITOR_HIGLIGHTS_CURRENT_LINE:Boolean.parseBoolean(result);
    }

    /**
     * @param editorHighlightsCurrentLine the editorHighlightsCurrentLine to set
     */
    public void setEditorHighlightsCurrentLine(boolean editorHighlightsCurrentLine) {
        setValue("editor-highlights-current-line", String.valueOf(editorHighlightsCurrentLine));
    }

    /**
     * @return the editorShowsRightMargin
     */
    public boolean getEditorShowsRightMargin() {
        String result =  getValue("editor-shows-right-margin");
        return result==null?DEFAULT_EDITOR_SHOWS_RIGHT_MARGIN:Boolean.parseBoolean(result);
    }

    /**
     * @param editorShowsRightMargin the editorShowsRightMargin to set
     */
    public void setEditorShowsRightMargin(boolean editorShowsRightMargin) {
        setValue("editor-shows-right-margin", String.valueOf(editorShowsRightMargin));
    }

    /**
     * @return the editorRightMarginPosition
     */
    public int getEditorRightMarginPosition() {
        String result =  getValue("editor-right-margin-position");
        return result==null?DEFAULT_EDITOR_RIGHT_MARGIN_POSITION:Integer.valueOf(result);
    }

    /**
     * @param editorRightMarginPosition the editorRightMarginPosition to set
     */
    public void setEditorRightMarginPosition(int editorRightMarginPosition) {
        setValue("editor-right-margin-position", String.valueOf(editorRightMarginPosition));
    }

    /**
     * @return the editorTabWidth
     */
    public int getEditorTabWidth() {
        String result =  getValue("editor-tab-width");
        return result==null?DEFAULT_EDITOR_TAB_WIDTH:Integer.valueOf(result);
    }

    /**
     * @param editorTabWidth the editorTabWidth to set
     */
    public void setEditorTabWidth(int editorTabWidth) {
        setValue("editor-tab-width", String.valueOf(editorTabWidth));
    }

    public String getDefaultEditorScemeId(){
        return DEFAULT_EDITOR_SCHEME;
    }


} public class GlossaPrettyPrinter extends PrettyPrinter {

    @Override
    public String getPrettyText(String src) {
        StringBuilder result = new StringBuilder();

        int indentationLevel = 0;
        int numberOfPrecedingEmptyLines = 0;
        int numberOfEmptyLinesToAdd = 0;

        String[] lines = src.split("\\n|\\r|\\r\\n");
        String lowerCaseLine;
        
        boolean addNewLinesAfter = false;
        boolean firstCharIsAmp;
        String line;
        for (int i=0; i<lines.length; i++) {
            line = lines[i];
            
            line = line.trim();
            if (!"".equals(line)) {
                
                if((numberOfEmptyLinesToAdd!=0)&&(!nextNonEmptyLineAmpLine(lines, i-1))){
                    if(!isIgnoreEmptyLinesOn()){
                        numberOfPrecedingEmptyLines = numberOfEmptyLinesToAdd;
                        for(int j=0; j<numberOfEmptyLinesToAdd; j++){
                            result.append("\n");
                        }
                    }
                }
                numberOfEmptyLinesToAdd = 0;

                lowerCaseLine = line.toLowerCase();
                firstCharIsAmp = false;

                if (lowerCaseLine.startsWith("&")) {
                    line = line.substring(1).trim();
                    result.append("&");
                    lowerCaseLine = lowerCaseLine.substring(1).trim();
                    firstCharIsAmp = true;
                }
                
                addNewLinesAfter = firstCharIsAmp && addNewLinesAfter;

                if (lowerCaseLine.startsWith("αν")
                        || lowerCaseLine.startsWith("επίλεξε")
                        || lowerCaseLine.startsWith("επιλεξε")
                        || lowerCaseLine.startsWith("για")
                        || lowerCaseLine.startsWith("οσο")
                        || lowerCaseLine.startsWith("όσο")
                        || lowerCaseLine.startsWith("αρχη_επαν")
                        || lowerCaseLine.startsWith("αρχή_επαν")) {

                    result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForBlocks());
                        result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    }
                    indentationLevel++;
                    addNewLinesAfter = true;

                } else if (lowerCaseLine.startsWith("πρόγραμμα")
                        || lowerCaseLine.startsWith("προγραμμα")) {

                    indentationLevel = 1;
                    addNewLinesAfter = true;

                } else if (lowerCaseLine.startsWith("συνάρτηση")
                        || lowerCaseLine.startsWith("συναρτηση")
                        || lowerCaseLine.startsWith("διαδικασία")
                        || lowerCaseLine.startsWith("διαδικασια")) {
                    
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForSubPrograms());
                    }
                    
                    indentationLevel = 1;
                    addNewLinesAfter = true;

                } else if (lowerCaseLine.startsWith("σταθερέ")
                        || lowerCaseLine.startsWith("σταθερε")
                        || lowerCaseLine.startsWith("μεταβλητέ")
                        || lowerCaseLine.startsWith("μεταβλητε")) {

                    indentationLevel = 1;
                    result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForBlocks());
                        result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    }
                    
                    indentationLevel = 2;

                } else if (lowerCaseLine.startsWith("αλλιω")
                        || lowerCaseLine.startsWith("αλλιώ")
                        || lowerCaseLine.startsWith("περίπτωση")
                        || lowerCaseLine.startsWith("περιπτωση")) {

                    indentationLevel--;
                    result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForBlocks());
                        result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    }
                    indentationLevel++;
                    addNewLinesAfter = true;

                } else if (lowerCaseLine.startsWith("αρχή")
                        || lowerCaseLine.startsWith("αρχη")){
                    
                    indentationLevel = 0;
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForBlocks());
                    }
                    indentationLevel = 1;
                    addNewLinesAfter = true;
                    
                } else if (lowerCaseLine.startsWith("τέλος_επαν")
                        || lowerCaseLine.startsWith("τελος_επαν")
                        || lowerCaseLine.startsWith("τέλοσ_επαν")
                        || lowerCaseLine.startsWith("τελοσ_επαν")
                        || lowerCaseLine.startsWith("τέλος_αν")
                        || lowerCaseLine.startsWith("τελος_αν")
                        || lowerCaseLine.startsWith("τέλοσ_αν")
                        || lowerCaseLine.startsWith("τελοσ_αν")
                        || lowerCaseLine.startsWith("τέλος_επιλογ")
                        || lowerCaseLine.startsWith("τελος_επιλογ")
                        || lowerCaseLine.startsWith("τέλοσ_επιλογ")
                        || lowerCaseLine.startsWith("τελοσ_επιλογ")
                        || lowerCaseLine.startsWith("μέχρις_ότου")
                        || lowerCaseLine.startsWith("μέχρις_οτου")
                        || lowerCaseLine.startsWith("μεχρις_ότου")
                        || lowerCaseLine.startsWith("μεχρις_οτου")
                        || lowerCaseLine.startsWith("μέχρισ_ότου")
                        || lowerCaseLine.startsWith("μέχρισ_οτου")
                        || lowerCaseLine.startsWith("μεχρισ_ότου")
                        || lowerCaseLine.startsWith("μεχρισ_οτου")
                        || lowerCaseLine.startsWith("μέχρις ότου")
                        || lowerCaseLine.startsWith("μέχρις οτου")
                        || lowerCaseLine.startsWith("μεχρις ότου")
                        || lowerCaseLine.startsWith("μεχρις οτου")
                        || lowerCaseLine.startsWith("μέχρισ ότου")
                        || lowerCaseLine.startsWith("μέχρισ οτου")
                        || lowerCaseLine.startsWith("μεχρισ ότου")
                        || lowerCaseLine.startsWith("μεχρισ οτου")) {

                    indentationLevel--;
                    result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForBlocks());
                        result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                    }
                    addNewLinesAfter = true;

                } else if (lowerCaseLine.startsWith("τέλος_προγρ")
                        || lowerCaseLine.startsWith("τελος_προγρ")
                        || lowerCaseLine.startsWith("τέλοσ_προγρ")
                        || lowerCaseLine.startsWith("τελοσ_προγρ")
                        || lowerCaseLine.startsWith("τέλος_διαδικασ")
                        || lowerCaseLine.startsWith("τελος_διαδικασ")
                        || lowerCaseLine.startsWith("τέλοσ_διαδικασ")
                        || lowerCaseLine.startsWith("τελοσ_διαδικασ")
                        || lowerCaseLine.startsWith("τέλος_συν")
                        || lowerCaseLine.startsWith("τελος_συν")
                        || lowerCaseLine.startsWith("τέλοσ_συν")
                        || lowerCaseLine.startsWith("τελοσ_συν")) {

                    indentationLevel = 0;
                    if(numberOfPrecedingEmptyLines==0){
                        result.append(getNewLinesForBlocks());
                    }
                } else {
                    result.append(getWhitespaceForIndentationLevel(indentationLevel, firstCharIsAmp));
                }

                result.append(processLine(line));
                result.append("\n");
                if(addNewLinesAfter && (!nextNonEmptyLineAmpLine(lines, i))  ){
                    result.append(getNewLinesForBlocks());
                    numberOfPrecedingEmptyLines = getNumOfLinesBetweenBlocks();
                }else{
                    numberOfPrecedingEmptyLines = 0;
                }
            }else{
                //numberOfPrecedingEmptyLines++;
                //result.append("\n");
                numberOfEmptyLinesToAdd++;
            }
        }

        return result.toString();
    }
    
    private boolean nextNonEmptyLineAmpLine(String[] lines, int index){
        String line;
        int i=index+1;
        if(index<lines.length-1){
            while(i<lines.length){
                line = lines[i].trim();
                if(!"".equals(line)){
                    if(line.startsWith("&")){
                        return true;
                    }else{
                        return false;
                    }
                }
                i++;
            }
            return false;
        }
        return false;
        
    }
    
    private String processLine(String line){
        StringBuilder result = new StringBuilder();
        
        boolean inSingleQuotedString = false;
        boolean inDoubleQuotedString = false;
        boolean inComment = false;
        char c = 0;
        char lastAppended = 0;
        char lastChar;
        char[] chars = line.toCharArray();
        
        for(int i=0; (i<chars.length)&&(!inComment); i++){
            lastChar = c;
            c = chars[i];
            if(c=='!'){
                if(!(inSingleQuotedString||inDoubleQuotedString)){
                    inComment = true;
                    if( (lastAppended!=' ') && (lastAppended!='\t') && (i!=0)){
                        result.append(' ');
                    }
                    result.append(line.substring(i)); //the rest of the line is comment
                }
            }else if(c=='\''){
                if(inSingleQuotedString){
                    inSingleQuotedString = false;
                }else if(!inDoubleQuotedString){
                    inSingleQuotedString = true;
                    if( (lastAppended!=' ') && (lastAppended!='\t') ){
                        result.append(' ');
                    }
                }
            } else if(c=='"'){
                if(inDoubleQuotedString){
                    inDoubleQuotedString = false;
                }else if(!inSingleQuotedString){
                    inDoubleQuotedString = true;
                    if( (lastAppended!=' ') && (lastAppended!='\t') ){
                        result.append(' ');
                    }
                }
            }
            
            if(!inComment){
                if(!(inSingleQuotedString || inDoubleQuotedString)){
                    if((c!=' ')&&(c!='\t')){
                        if((lastChar==' ')||(lastChar=='\t')){
                            if( (  (c!='(') && (c!='[') && (c!=']') && (c!=',') && (c!=':') )
                                    &&
                                ( (lastAppended!=' ') && (lastAppended!='\t') && (lastAppended!='[') )
                              ){
                                if( (lastChar=='\t') && isReplaceTabsWithSpacesOn() ){
                                    result.append(getSpacesForTab());
                                }else{
                                    result.append(lastChar);
                                }
                            }
                        }else{
                            if( (c==')')  && (lastAppended!=' ') && (lastAppended!='\t')){
                                result.append(' ');
                            }
                        }
                        result.append(c);
                        lastAppended = c;
                        if( ((c=='(') || (c==']') ||(c==',') || (c==':')) && (lastAppended!=' ') && (lastAppended!='\t')){
                            result.append(' ');
                            lastAppended = ' ';
                        }
                    }
                } else { //always print in-string chars:
                    result.append(c);
                    lastAppended = c;
                }
            }
        }
        return result.toString();
    }
} public abstract class PrettyPrinter {

    private int numOfWhitespaceCharsPerIndentationLevel;
    private boolean useSpaces;
    
    boolean ignoreEmptyLines;
    
    private boolean replaceTabsWithSpaces;
    private int numOfSpacesPerTab;
    
    private char whitespaceChar;
    private int numOfLinesBetweenSubprograms;
    private int numOfLinesBetweenBlocks;
    private int numOfWhitespaceCharsBetweenAssignmentOperands;
    private int numOfWhitespaceCharsBeforeAndAfterConditions;
    private String spacesForTab;
    private String whitespaceUnit;
    private String newLinesForBlocks;
    private String newLinesForSubPrograms;

    public PrettyPrinter() {
        ignoreEmptyLines = true;
        numOfWhitespaceCharsPerIndentationLevel = 4;
        whitespaceChar = ' ';
        useSpaces = true;
        replaceTabsWithSpaces = true;
        numOfSpacesPerTab = 4;
        numOfLinesBetweenSubprograms = 4;
        numOfWhitespaceCharsBeforeAndAfterConditions = 1;
        numOfLinesBetweenBlocks = 1;
        setSpacesForTab();
        setWhitespaceUnit();
        setNewLinesForBlocks();
        setNewLinesForSubprograms();
    }

    public abstract String getPrettyText(String src);

    public String getWhitespaceForIndentationLevel(int level, boolean reduceFirstUnitByOneAndOneChar) {
        if (level <= 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        int i = 0;

        if (reduceFirstUnitByOneAndOneChar) {
            boolean tmpUseSpaces = useSpaces;
            numOfWhitespaceCharsPerIndentationLevel--;

            setUseSpaces(true);
            sb.append(whitespaceUnit);

            numOfWhitespaceCharsPerIndentationLevel++;
            setUseSpaces(tmpUseSpaces);
            i = 2;
        }

        for (; i < level; i++) {
            sb.append(whitespaceUnit);
        }

        return sb.toString();
    }

    public void setUseSpaces(boolean useSpaces) {
        this.useSpaces = useSpaces;
        if (useSpaces) {
            setWhitespace(' ');
        } else {
            setWhitespace('\t');
        }
        setWhitespaceUnit();
    }
    
    private void setSpacesForTab(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < numOfSpacesPerTab; i++) {
            sb.append(" ");
        }
        spacesForTab = sb.toString();
    }

    private void setWhitespaceUnit() {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < numOfWhitespaceCharsPerIndentationLevel; i++) {
            sb.append(whitespaceChar);
        }

        whitespaceUnit = sb.toString();
    }

    private void setNewLinesForBlocks() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < numOfLinesBetweenBlocks; i++) {
            sb.append("\n");
        }
        newLinesForBlocks = sb.toString();
    }

    private void setNewLinesForSubprograms() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < numOfLinesBetweenSubprograms; i++) {
            sb.append("\n");
        }
        newLinesForSubPrograms = sb.toString();
    }

    public String getNewLinesForBlocks() {
        return newLinesForBlocks;
    }

    public String getNewLinesForSubPrograms() {
        return newLinesForSubPrograms;
    }

    /**
     * @return the numOfWhitespaceCharsPerIndentationLevel
     */
    public int getNumOfWhitespaceCharsPerIndentationLevel() {
        return numOfWhitespaceCharsPerIndentationLevel;
    }

    /**
     * @param numOfWhitespaceCharsPerIndentationLevel the
     * numOfWhitespaceCharsPerIndentationLevel to set
     */
    public void setNumOfWhitespaceCharsPerIndentationLevel(int numOfWhitespaceCharsPerIndentationLevel) {
        this.numOfWhitespaceCharsPerIndentationLevel = numOfWhitespaceCharsPerIndentationLevel;
        setWhitespaceUnit();
    }

    /**
     * @return the useSpaces
     */
    public boolean isUseSpacesOn() {
        return useSpaces;
    }

    /**
     * @return the whitespace
     */
    public char getWhitespace() {
        return whitespaceChar;
    }

    public String getSpacesForTab() {
        return spacesForTab;
    }

    /**
     * @param whitespace the whitespace to set
     */
    private void setWhitespace(char whitespace) {
        this.whitespaceChar = whitespace;
    }

    /**
     * @return the numOfLinesBetweenSubprograms
     */
    public int getNumOfLinesBetweenSubprograms() {
        return numOfLinesBetweenSubprograms;
    }

    /**
     * @param numOfLinesBetweenSubprograms the numOfLinesBetweenSubprograms to
     * set
     */
    public void setNumOfLinesBetweenSubprograms(int numOfLinesBetweenSubprograms) {
        this.numOfLinesBetweenSubprograms = numOfLinesBetweenSubprograms;
        setNewLinesForSubprograms();
    }

    /**
     * @return the numOfWhitespaceCharsBetweenAssignmentOperands
     */
    public int getNumOfWhitespaceCharsBetweenAssignmentOperands() {
        return numOfWhitespaceCharsBetweenAssignmentOperands;
    }

    /**
     * @param numOfWhitespaceCharsBetweenAssignmentOperands the
     * numOfWhitespaceCharsBetweenAssignmentOperands to set
     */
    public void setNumOfWhitespaceCharsBetweenAssignmentOperands(int numOfWhitespaceCharsBetweenAssignmentOperands) {
        this.numOfWhitespaceCharsBetweenAssignmentOperands = numOfWhitespaceCharsBetweenAssignmentOperands;
    }

    /**
     * @return the numOfWhitespaceCharsBeforeAndAfterConditions
     */
    public int getNumOfWhitespaceCharsBeforeAndAfterConditions() {
        return numOfWhitespaceCharsBeforeAndAfterConditions;
    }

    /**
     * @param numOfWhitespaceCharsBeforeAndAfterConditions the
     * numOfWhitespaceCharsBeforeAndAfterConditions to set
     */
    public void setNumOfWhitespaceCharsBeforeAndAfterConditions(int numOfWhitespaceCharsBeforeAndAfterConditions) {
        this.numOfWhitespaceCharsBeforeAndAfterConditions = numOfWhitespaceCharsBeforeAndAfterConditions;
    }

    /**
     * @return the numOfLinesBetweenBlocks
     */
    public int getNumOfLinesBetweenBlocks() {
        return numOfLinesBetweenBlocks;
    }

    /**
     * @param numOfLinesBetweenBlocks the numOfLinesBetweenBlocks to set
     */
    public void setNumOfLinesBetweenBlocks(int numOfLinesBetweenBlocks) {
        this.numOfLinesBetweenBlocks = numOfLinesBetweenBlocks;
        setNewLinesForBlocks();
    }

    public void setNumOfSpacesPerTab(int numOfSpacesPerTab) {
        this.numOfSpacesPerTab = numOfSpacesPerTab;
        setSpacesForTab();
    }

    public void setReplaceTabsWithSpaces(boolean replaceTabsWithSpaces) {
        this.replaceTabsWithSpaces = replaceTabsWithSpaces;
    }

    public int getNumOfSpacesPerTab() {
        return numOfSpacesPerTab;
    }

    public boolean isReplaceTabsWithSpacesOn() {
        return replaceTabsWithSpaces;
    }

    public boolean isIgnoreEmptyLinesOn() {
        return ignoreEmptyLines;
    }

    public void setIgnoreEmptyLines(boolean collapseEmptyLines) {
        this.ignoreEmptyLines = collapseEmptyLines;
    }
    
} public class MainWindow extends Window implements InterpreterListener{
    private static MainWindow instance;
    private final Box mainContainer;
    private final BottomPane bottomPane;
    private final SidePane sidePane;
    private final MainWindowStatusbar statusBar;
    private final MenuBar mainMenubar;
    private final Toolbar mainToolbar;
    private final EditorContainer editorContainer;
    private final Paned verticalPaned;
    private final Paned horizontalPaned;
    private static AcceleratorGroup accelGroupInstance;



    private Interpreter interpreter;
    private boolean runInteractively;
    private int interpreterDelayBetweenSteps;
    private boolean running;


    public static MainWindow getInstance() {
        if (instance == null) {
            instance = new MainWindow();
        }
        return instance;
    }

    private MainWindow() {
        mainContainer = new Box(Orientation.VERTICAL, 0);

        statusBar = MainWindowStatusbar.getInstance();
        mainContainer.packEnd(statusBar, false, false, 0);

        mainMenubar = new MenuBar();
        mainContainer.packStart(mainMenubar, false, true, 0);

        mainToolbar = MainToolbar.getInstance();
        mainContainer.packStart(mainToolbar, false, true, 0);

        bottomPane = new BottomPane();
        sidePane = new SidePane();

        verticalPaned = new Paned(Orientation.VERTICAL);
        horizontalPaned = new Paned(Orientation.HORIZONTAL);
        
        editorContainer = new EditorContainer();
        
        horizontalPaned.pack1(editorContainer, true, true); 
        horizontalPaned.pack2(sidePane, false, true);
        horizontalPaned.setPosition(500);
        
        verticalPaned.pack1(horizontalPaned, true, true);
        verticalPaned.pack2(bottomPane, false, true);
        verticalPaned.setPosition(300);

        mainContainer.packStart(verticalPaned, true, true, 0);

        this.add(mainContainer);

        this.addAcceleratorGroup(getAcceleratorGroup());

        this.setTitle(StringResources.getInstance().getString("untitled") + " - " + StringResources.getInstance().getString("app_name"));

        this.resize(800, 600);
        setIcon(IconResources.getIcon());

        connect(new Window.DeleteEvent() {

            public boolean onDeleteEvent(Widget source, Event event) {
                exitApp();
                return true;
            }
        });


        this.runInteractively = false;
        this.running = false;
        this.interpreterDelayBetweenSteps = 0;

        Preferences.getInstance().apply();
    }
    
    public void exitApp() {
        boolean exit = true;
        if(stopInterpreterAndContinue()){
            Editor editor = Editor.getInstance();
            EditorBuffer buf = editor.getBuffer();
            if(buf.getModified()){
                if(DialogManager.handleModifiedBuffer(false, buf.getDocumentTitle(), editor)){
                    exit = true;
                }else{
                    exit = false;
                }
            }else{
                exit = true;
            }
        }else{
            exit = false;
        }

        if(InputFileWindow.getInstance().isModified()){
            exit = exit && DialogManager.handleModifiedBuffer(true, StringResources.getInstance().getString("untitled"), InputFileWindow.getInstance());
        }

        if(exit){
            Gtk.mainQuit();
        }
    }

    public void updateTitle() {
        EditorBuffer buf= Editor.getInstance().getBuffer();
        StringBuilder title = new StringBuilder(buf.getDocumentTitle());
        if(buf.getModified()==true){
            title.append("*");
        }
        title.append(" - ");
        title.append(StringResources.getInstance().getString("app_name"));
        this.setTitle(title.toString());
    }

    public static AcceleratorGroup getAcceleratorGroup() {
        if (accelGroupInstance == null) {
            accelGroupInstance = new AcceleratorGroup();
        }
        return accelGroupInstance;
    }


    // <editor-fold defaultstate="collapsed" desc="Code execution">

    public void run() {
        RunAction.getInstance().setSensitive(false);
        if (running) {
            interpreter.resume();
        } else {
            runCurrentFile();
        }
    }

    public void stop() {
        if (interpreter != null) {
            interpreter.stop();
        }
    }

    public boolean stopInterpreterAndContinue(){
        boolean proceed = true;
        if(running && (interpreter!=null) ){
            if(DialogManager.verifyStopInterpreter()){
                stop();
                proceed = true;
            }else{
                proceed = false;
            }
        }
        return proceed;
    }

    public void parsingAndSemanticAnalysisFinished(Interpreter sender, boolean success) {
    }

    public void runtimeError() {
    }

    public void executionStarted(Interpreter sender) {
        running = true;
        Editor.getInstance().setEditable(false);
        RunAction.getInstance().setSensitive(false);
        StopAction.getInstance().setSensitive(true);
        MainToolbar.getInstance().setDelayControlsSensitive(false);
        ToggleStepByStepAction.getInstance().setSensitive(false);
        ToggleUseInputFileAction.getInstance().setSensitive(false);
        RuntimeWindow.getInstance().setFocusOnInputEntry();
    }

    public void executionStopped(Interpreter sender) {
        running = false;
        Editor.getInstance().getBuffer().clearHighlight("exec_highlight");
        Editor.getInstance().setEditable(true);
        RunAction.getInstance().setSensitive(true);
        MainToolbar.getInstance().setDelayControlsSensitive(true);
        ToggleStepByStepAction.getInstance().setSensitive(true);
        ToggleUseInputFileAction.getInstance().setSensitive(true);
        StopAction.getInstance().setSensitive(false);
    }

    public void readStatementExecuted(Interpreter sender, Integer line) {
        if (runInteractively || (interpreterDelayBetweenSteps > 0)) {
            Editor ed = Editor.getInstance();
            EditorBuffer buf = ed.getBuffer();
            buf.highlightLine(line.intValue() - 1, "exec_highlight");
            TextIter start = buf.getIterStart();
            start.forwardLines(line.intValue()-1);
            ed.scrollTo(start);
        }
    }

    public void executionPaused(Interpreter sender, Integer line, Boolean wasPrintStatement) {

        if (runInteractively || (interpreterDelayBetweenSteps > 0)) {
            Editor ed = Editor.getInstance();
            EditorBuffer buf = ed.getBuffer();
            buf.highlightLine(line.intValue() - 1, "exec_highlight");
            TextIter start = buf.getIterStart();
            start.forwardLines(line.intValue()-1);
            ed.scrollTo(start);
        }

        if (!runInteractively) {
            if (interpreterDelayBetweenSteps > 0) {
                final Interpreter inter = sender;
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        try {
                            Thread.sleep(interpreterDelayBetweenSteps);
                        } catch (InterruptedException e) {
                        }
                        inter.resume();
                    }
                });
                t.start();
            } else {
                sender.resume();
            }
        } else {
            RunAction.getInstance().setSensitive(true);
        }
    }

    public void stackPopped() {
    }

    public void stackPushed(SymbolTable newSymbolTable) {
    }

    

    public void runCurrentFile() {
        String text = Editor.getInstance().getBuffer().getText();
        if (!text.endsWith("\n")) {
            text += "\n";
        }
        InterpreterMessagesListView interpreterMessagesListView = bottomPane.getMessagesListView();
        RuntimeWindow runtimeWindow = RuntimeWindow.getInstance();

        Editor.getInstance().getBuffer().clearHighlight("exec_highlight");
        Editor.getInstance().getBuffer().clearHighlight("error");
        Editor.getInstance().getBuffer().clearHighlight("warning");


        if (this.interpreter != null) {
            interpreter.removeListener(this);
            interpreterMessagesListView.setInterpreter(null);
        }

        this.runInteractively = ToggleStepByStepAction.getInstance().getActive();
        this.interpreterDelayBetweenSteps = MainToolbar.getInstance().getDelayBetweenSteps();

        runtimeWindow.clear();
        interpreterMessagesListView.clear();
        bottomPane.setCurrentPage(0);
        InputStream in;
        boolean useInputFile = ToggleUseInputFileAction.getInstance().getActive();
        if (useInputFile) {
            in = new ByteArrayInputStream(InputFileWindow.getInstance().getText().getBytes());
        } else {
            in = runtimeWindow.getInputStream();
        }
        interpreter = new Interpreter(text, System.out, System.err, runtimeWindow.getOut(), runtimeWindow.getErr(), in);
        interpreter.addListener(this);
        interpreterMessagesListView.setInterpreter(interpreter);
        boolean success = interpreter.parseAndAnalyzeSemantics(true);
        if (success) {
            bottomPane.setCurrentPage(1);
            interpreter.exec(useInputFile);
        } else {
            RunAction.getInstance().setSensitive(true);
        }
    }
    // </editor-fold>

} public class GlossaDiermhneythsFileFilter extends FileFilterWithExtension{

    public GlossaDiermhneythsFileFilter() {
        setName(StringResources.getInstance().getString("glossa_diermhneyths_files"));
        addMimeType("text/glossa");
        addPattern("*.γλώσσα");
    }
    
    public String getExtension(){
        return(".γλώσσα");
    }

} public class DialogManager {

    public static boolean verifyStopInterpreter() {
        InterpreterStillRunningDialog dlg = new InterpreterStillRunningDialog();
        ResponseType response = dlg.run();
        dlg.hide();
        if (response == ResponseType.YES) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean handleModifiedBuffer(boolean isInputFileBuffer, String filename, BufferSaver dc) {
        FileModifiedMessageDialog dlg = new FileModifiedMessageDialog(isInputFileBuffer, filename);
        ResponseType response = dlg.run();
        dlg.hide();
        if (response == ResponseType.YES) { // The user selected to save the changes.
            return dc.saveFile(false);
        } else if (response == ResponseType.NO) {
            return true; // The user selected not to save the changes.
        }else{
            return false; // The user canceled;
        }
    }

    public static String showSaveDialog(SaveDialog saveDialog, File initialFile, String initialFileName) {
        saveDialog.setFilename(initialFileName, initialFile == null ? null : initialFile.getParent());
        ResponseType response = saveDialog.run();
        saveDialog.hide();
        String fname = saveDialog.getFilename();
        if ((response == ResponseType.OK) &&(fname!=null)){
            FileFilterWithExtension f = saveDialog.getFilter();
            String extension = f.getExtension();
            if(!fname.endsWith(extension)){
                fname += extension;
            }
            File target = new File(fname);
            if (target.exists()) {
                if (confirmOverwriteFile(target)) {
                    return target.getAbsolutePath();
                } else {
                    return null;
                }
            } else {
                return target.getAbsolutePath();
            }
        }
        return null;
    }

    public static String showExportToHTMLDialog(File initialFile, String initialFileName) {
        ExportToHTMLDialog saveDialog = ExportToHTMLDialog.getInstance();
        if(initialFileName.endsWith(".gls")){
            initialFileName = initialFileName.substring(0, initialFileName.length()-4);
            initialFileName = initialFileName.concat(".html");
        }
        saveDialog.setFilename(initialFileName, initialFile == null ? null : initialFile.getParent());
        ResponseType response = saveDialog.run();
        saveDialog.hide();
        String fname = saveDialog.getFilename();
        if ((response == ResponseType.OK) &&(fname!=null)){
            File target = new File(fname);
            if (target.exists()) {
                if (DialogManager.confirmOverwriteFile(target)) {
                    return target.getAbsolutePath();
                } else {
                    return null;
                }
            } else {
                return target.getAbsolutePath();
            }
        }
        return null;
    }

    public static boolean confirmOverwriteFile(File f) {
        FileExistsMessageDialog dlg = new FileExistsMessageDialog(f.getAbsolutePath());
        ResponseType response = dlg.run();
        dlg.hide();
        if (response == ResponseType.YES) {
            return true;
        } else {
            return false;
        }
    }

    public static String showOpenDialog(FileChooserDialog openDialog) {
        ResponseType response = openDialog.run();
        openDialog.hide();
        if (response == ResponseType.OK) {
            return openDialog.getFilename();
        }
        return null;
    }

} public interface BufferSaver {
    public boolean saveFile(boolean saveAs);
} public class OpenCodeDialog extends FileChooserDialog{

    private static OpenCodeDialog instance = null;

    public static OpenCodeDialog getInstance(){
        if(instance == null){
            instance = new OpenCodeDialog();
        }
        return instance;
    }

    private OpenCodeDialog() {
        super(StringResources.getInstance().getString("open_file"), MainWindow.getInstance(), FileChooserAction.OPEN);
        this.addFilter(new GlossaFileFilter());
        this.addFilter(new GlossaDiermhneythsFileFilter());
        this.addFilter(new AllFilesFileFilter());
        this.setFilter(new GlossaFileFilter());
        this.setModal(true);
    }

} public class SaveCodeDialog extends SaveDialog{
    
    private static SaveCodeDialog instance = null;

    public static SaveCodeDialog getInstance(){
        if(instance == null){
            instance = new SaveCodeDialog();
        }
        return instance;
    }

    private SaveCodeDialog() {
        FileFilterWithExtension filter = new GlossaFileFilter();
        addFilter(filter);
        setFilter(filter);
        filter = new GlossaDiermhneythsFileFilter();
        addFilter(filter);
    }

    @Override
    public String getFilename() {
        String result = super.getFilename();
        if(result!=null){
           FileFilter f = getFilter();
           if(new GlossaFileFilter().equals(f) &&!result.endsWith(".gls")){
               result = result.concat(".gls");
           }else if(new GlossaDiermhneythsFileFilter().equals(f) &&!result.endsWith(".γλώσσα")){
               result = result.concat(".γλώσσα");
           }
        }
        return result;
    }

} public class EditorPopupMenu extends Menu{

    public EditorPopupMenu() {
        append(UndoAction.getInstance().createMenuItem());
        append(RedoAction.getInstance().createMenuItem());

        append(new SeparatorMenuItem());

        append(CutAction.getInstance().createMenuItem());
        append(CopyAction.getInstance().createMenuItem());
        append(PasteAction.getInstance().createMenuItem());
        append(DeleteAction.getInstance().createMenuItem());

        append(new SeparatorMenuItem());

        append(SelectAllAction.getInstance().createMenuItem());
    }

} public class CharsetDetector {

    /**
     * Tries to detect the charset of a file from an array of charsets
     *
     * @param f The target file
     *
     * @param charsets A String array containing the names of the charsets to be
     *                 tested.
     *
     * @return A Charset object representing the detected charset or null if no
     *         matching charset was detected.
     */
    public Charset detectCharset(File f, String[] charsets) {

        Charset charset = null;

        for (String charsetName : charsets) {
            charset = detectCharset(f, Charset.forName(charsetName));
            if (charset != null) {
                break;
            }
        }

        return charset;
    }

    private Charset detectCharset(File f, Charset charset) {
        try {
            BufferedInputStream input = new BufferedInputStream(new FileInputStream(f));

            CharsetDecoder decoder = charset.newDecoder();
            decoder.reset();

            byte[] buffer = new byte[512];
            boolean identified = false;
            while ((input.read(buffer) != -1) && (!identified)) {
                identified = identify(buffer, decoder);
            }

            input.close();

            if (identified) {
                return charset;
            } else {
                return null;
            }

        } catch (Exception e) {
            return null;
        }
    }

    private boolean identify(byte[] bytes, CharsetDecoder decoder) {
        try {
            decoder.decode(ByteBuffer.wrap(bytes));
        } catch (CharacterCodingException e) {
            return false;
        }
        return true;
    }
} public class MiscUtils {

    /**
     * Opens a file with the default program using the xdg-open command
     *
     * @param f The file to be opened
     * 
     */
    public static void xdgOpenFile(File f) {
        String CROSS_DESKTOP_OPEN_FILE_COMMAND = "/usr/bin/xdg-open";
        try {
            File parentDir = f.getParentFile();
            String fname = f.getName();
            String cmd = "/bin/sh " + CROSS_DESKTOP_OPEN_FILE_COMMAND + " " + fname;
            Runtime rt = Runtime.getRuntime();
            Process p = rt.exec(cmd, null, parentDir);
            p.waitFor();
        } catch (Exception e) {
        }
    }
    
    public static boolean isSpaceOrTab(int codePoint){
        if(Character.isSpaceChar(codePoint)|| ('\t'==codePoint)){
            return true;
        }
        return false;
    }
    
    public static String trimLeadingWhitespace(String s){
        return s.replaceAll("^\\s+", "");
    }
    
    public static String editorTabWidthToTabs(){
        StringBuilder sb = new StringBuilder();
        for(int i=0; i < Preferences.getInstance().getEditorTabWidth(); i++){
            sb.append(" ");
        }
        return sb.toString();
    }
}
import glossa.html_generator.GlossaHTMLGenerator;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import lingua.preferences.Preferences;
import lingua.pretty_printer.GlossaPrettyPrinter;
import lingua.pretty_printer.PrettyPrinter;
import lingua.ui.gtk.main_window.MainWindow;
import lingua.ui.gtk.main_window.dialogs.filters.GlossaDiermhneythsFileFilter;
import lingua.ui.gtk.main_window.dialogs.io.DialogManager;
import lingua.ui.gtk.main_window.dialogs.io.BufferSaver;
import lingua.ui.gtk.main_window.dialogs.io.OpenCodeDialog;
import lingua.ui.gtk.main_window.dialogs.io.SaveCodeDialog;
import lingua.ui.gtk.main_window.menus.EditorPopupMenu;
import lingua.utils.CharsetDetector;
import lingua.utils.MiscUtils;
import org.gnome.gdk.*;
import org.gnome.gtk.Menu;
import org.gnome.gtk.TextBuffer;
import org.gnome.gtk.Widget;
import org.gnome.pango.FontDescription;
import org.gnome.sourceview.SourceView;
/*
 *  The MIT License
 * 
 *  Copyright 2012 Georgios Migdos <cyberpython@gmail.com>.
 * 
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 * 
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
package lingua.ui.gtk.main_window.widgets;


/**
 *
 * @author Georgios Migdos <cyberpython@gmail.com>
 */
public class Editor extends SourceView implements BufferSaver{

    private static Editor instance = null;
    private final Menu popupMenu;
    private PrettyPrinter prettyPrinter;

    public static Editor getInstance() {
        if (instance == null) {
            instance = new Editor();
        }
        return instance;
    }

    private Editor() {
        super(new EditorBuffer());
        popupMenu = new EditorPopupMenu();
        prettyPrinter = new GlossaPrettyPrinter();