//----------------------------------------------------------------
// Example 1: TextController and TextWindow classes
// intertwined
//----------------------------------------------------------------
    
// TextController.java:

    window.setAdjustmentListeners(
            new VerticalAdjustmentListener(),
            new HorizontalAdjustmentListener()
    );

// TextWindow.java:

    public void setAdjustmentListeners(
            AdjustmentListener verticalAdjustmentListener,
            AdjustmentListener horizontalAdjustmentListener) {
        horizontalScrollBar.addAdjustmentListener(horizontalAdjustmentListener);
        verticalScrollBar.addAdjustmentListener(verticalAdjustmentListener);
    }
    
// TextController.java:
    
    // Adjustment listener that handles vertical scrollbar changes
    private class VerticalAdjustmentListener implements AdjustmentListener {
        @Override
        public void adjustmentValueChanged(AdjustmentEvent e) {
            if (activeWindow == null) {
                return;
            }
            TextPosition nextScrollPosition = new TextPosition(activeWindow
                    .getScrollPosition());
            nextScrollPosition.setLineNumber(e.getValue());
            activeWindow.setScrollPosition(nextScrollPosition, false);
            setVisibleText();
        }
    }
    
    private void setVisibleText() {
        for (TextWindow window : windows) {
            int numLines = window.getNumLinesVisible();
            int numColumns = window.getNumColumnsVisible();

            // Determine bounds of visible text
            TextPosition scrollPosition = new TextPosition(
                    window.getScrollPosition()
            );
            ...
            // Update window text based on bounds
            window.setTextContent(
                    buffer.getText(
                        new TextPosition(scrollPosition.getLineNumber(), 0),
                        new TextPosition(endLineNumber, endLineLength)
                    ),
                    buffer.getLongestLineLength(),
                    buffer.getNumLines()
            );
        }
    }

//----------------------------------------------------------------
// Example 2: Undo action code separated from data
//----------------------------------------------------------------

// InsertCommand.java:
    
public class InsertCommand<E> implements Command
{
    private final int x;
    private final int y;
    private final E  deletion;
    public InsertCommand(int x, int y, E insertion) {...}
    public int getX() { return x; }
    public int getY() { return y; }
    public E getInsertion() { return insertion; }
}

// TextEditorModel.java:

public void execute(Command cmd) {
    ...
    if (cmd instanceof InsertCommand) {
        // Make the change in the line where we must insert the character
        // and replace the old with the updated line (in the list 'lines') 
        char charToInsert = ((InsertCommand<Character>)cmd).getInsertion();
        line = line.substring(0, indexWithinLine)
             + charToInsert + line.substring(indexWithinLine);
        lines.set(lineIndex, line);
        
        updateLineLengthHistorgram(initLineLength, initLineLength + 1);
        replacementLines.add(line);
    }
    else if (cmd instanceof DeleteCommand) {
        ...
    }
    ...
}

public void undo() {
    ...
    if (cmd instanceof InsertCommand) {
        // Remove the character that was inserted
        int charIndex = cmd.getX(); // Index of char we should remove
        String line = lines.get(lineIndex);
        int lineLength = line.length(); // Length after cmd was executed 
        if (lineLength == charIndex + 1) {
            // We had inserted the character at the end of the line
            line = line.substring(0, charIndex);
        }
        else {
            line = line.substring(0, charIndex)
                 + line.substring(charIndex + 1);
        }
        lines.set(lineIndex, line);
        
        // We are essentially deleting one character
        updateLineLengthHistorgram(lineLength, lineLength - 1);
        replacementLines.add(line);
    }
    else if (cmd instanceof DeleteCommand) {
        ...
    }
    ...
}


//----------------------------------------------------------------
// Example 3: pass-through methods
//----------------------------------------------------------------

//TextDocument.java (passing down from TextEditor to RWTextArea):

public Character getLastTypedCharacter() {
    return this.textArea.getLastTypedCharacter();
}

public int getCursorOffset() {
    return this.textArea.getCursorOffset();
}

public void insertString(String textToInsert, int offset) {
    this.textArea.insertText(textToInsert, offset);
}

//TextDocument.java (passing down from TextEditor to Swing/AWT):

private static KeyStroke cutKeyStroke() {
    return KeyStroke.getKeyStroke('X', InputEvent.CTRL_DOWN_MASK);
}

private static KeyStroke copyKeyStroke() {
    return KeyStroke.getKeyStroke('C', InputEvent.CTRL_DOWN_MASK);
}

//TextDocument.java (passing up from RWTextArea to TextEditor):
   
@Override
public void willInsertString(String stringToInsert, int offset) {
    if (this.listener != null && this.listener.get() != null) {
        this.listener.get().willInsertString(this, stringToInsert, offset);
    }            
}

@Override
public void willDeleteString(String stringToDelete, int offset) {
    if (this.listener != null && this.listener.get() != null) {
        this.listener.get().willDeleteString(this, stringToDelete, offset);
    }                    
}


//----------------------------------------------------------------
// Example 4: code with many dependencies, hard to tell whether it
// works or not
//----------------------------------------------------------------

window.addKeyBinding(
        KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, noKeyModifiers),
        "delete",
        (ActionEvent e) -> {
            TextPosition cursorPosition = window.getCursorPosition();
            int lineNumber = cursorPosition.getLineNumber();
            int numLines = buffer.getNumLines();
            int columnNumber = cursorPosition.getColumnNumber();
            int lineLength = buffer.getLineLength(lineNumber);

            if (window.getTextSelected()) {
                deleteSelectedText();
            } else if (lineNumber != numLines - 1 ||
                        columnNumber != lineLength) {
                // Cursor is not at end of text
                String charDeleted = buffer.getText(cursorPosition);

                // Update undo/redo history
                if (typedDirection != Direction.BACKWARD) {
                    TextPosition endPosition = buffer
                            .getColumnOffsetPosition(cursorPosition, 1);
                    saveHistory(endPosition);
                    currentModification.setLength(0);
                    typedDirection = Direction.BACKWARD;
                }
                currentModification.append(charDeleted);
                redoActions.clear();

                buffer.deleteText(cursorPosition);
            }
            setVisibleText();
        }
);

// keeps track of the direction of the current edit
// when this value is Direction.FORWARD, it means text is being typed
// when this value is Direction.BACKWARD, it means text is being deleted
private Direction typedDirection;

// a StringBuffer that stores the current modification
// this could be text that has been inserted, or text has been deleted
private StringBuffer currentModification;

/**
 * Helper method that saves the curernt modification into the undo history
 * @param cursorPosition the position of the cursor prior to saving the
 *                       current modification
 */
private void saveHistory(TextPosition cursorPosition) {
    ...
}

// Code from my solution:

public void keyPressed(KeyEvent event)
    ...
    if (code == KeyEvent.VK_DELETE) {
        if (deleteSelection()) {
            return;
        }
        Position p = panel.getInsertPosition();
        Position p2 = context.text.advancePosition(p, 1);
        context.text.delete(p, p2);
        return;
    }
}


//----------------------------------------------------------------
//Example 5: documentation duplicates code
//----------------------------------------------------------------

/*
 * The text cursor.
 */
private TextCursor textCursor;

/*
 * Helps with positioning and finding the text index of the cursor.
 */
private TextPositioningHelper positioningHelper;


//----------------------------------------------------------------
//Example 6: documentation to critique
//----------------------------------------------------------------

/**
 * Inserts the specified text at the position specified by startPosition
 *
 * @param text          The text to insert (can contain new line charactars)
 * @param startPosition the position within the buffer at which to insert
 */
public void insertText(String text, TextPosition startPosition) {...}


/**
 * {@inheritDoc}
 * 
 * @param cmd The command to execute which is one of the following and must
 *            be not {@code null}:
 * <ol>
 * <li>{@link InsertCommand}{@code <Character>}: Executed to insert a
 *     single character in the text.</li>
 * <li>{@link DeleteCommand}{@code <Character>}: Executed to delete a
 *     single character from the text.</li>
 * <li>{@link ReplaceCommand}{@code List<String>}: Executed to replace
 *     a list of lines in the text with another list of lines. The list of
 *     lines to replace can be {@code null} to indicate that no lines are
 *     replaced (i.e., inserting the lines in the {@code replacement})<br>
 *     The replacement can be {@code null} to indicate that no lines replace
 *     ones in the {@code contentToReplace}).</li>
 * <li>{@link TrackCommand}: Executing this command has no affect to the
 *     state of the model (i.e., the actual text). It is used only so that
 *     the user can keep track/observe an entity representable in the x-y
 *     space.</li>
 * </ol>
 */
public void execute(Command cmd) {...}
    


if (detectAndHandleCtrlCommands(e)) {
    // The user pressed one of: Ctrl-c, Ctrl-v, Ctrl-z, Ctrl-y,
    // Ctrl-s and Ctrl-o
    return;
}

/**
 * Detects if a given keystroke is one of the following in which case it
 * performs the corresponding action:
 * <ul>
 * <li>Control-c: copies the selected text (if any) to clipboard</li>
 * <li>Control-v: pastes text (if any) from clipboard</li>
 * <li>Control-z: undo the last group of changes (if any)</li>
 * <li>Control-y: redo the modifications in the reverse order they were
 *                undone (if any)</li>
 * <li>Control-s: save the file with the editor text</li>
 * <li>Control-o: opens a new text editor window</li>
 * </ul>
 * 
 * @param e The event that indicates that a keystroke occurred
 * 
 * @return {@code true} if the pressed key is either {@code Control-c},
 *         {@code Control-v}, {@code Control-z}, {@code Control-y},
 *         {@code Control-s} or {@code Control-o}. In any other case,
 *         it returns {@code false}
 */
private boolean detectAndHandleCtrlCommands(KeyEvent e) {...}


//----------------------------------------------------------------
//Example 7: documentation to rewrite
//----------------------------------------------------------------

/**
 * Returns a TextPosition obtained by applying the supplied offset to the
 * supplied anchorPosition. If anchorPosition is out of bounds then it is
 * automatically set to the valid TextPosition closest to the bound that it
 * exceeds
 * @param anchorPosition position relative to which the offset is applied
 * @param offset         a positive or negative number indicating the
 *                       direction the offset is applied. Positive
 *                       indicates to the right, and negative indicates
 *                       to the left. An offset of 0 results in
 *                       a result of the anchorPosition itself
 * @return A TextPosition obtained by applying the offset to the
 * anchorPosition
 */
public TextPosition getColumnOffsetPosition(TextPosition anchorPosition,
        int offset) {...}


/**
 * Given a String representing a body of text, this method
 * separates the string into an array of Strings such that
 * each element represents a chunk of text separated by
 * new-line charactars. Empty lines within the supplied
 * text are represented by empty strings within the resulting
 * array
 *
 * @param text String representing a body of text to separate
 * @return array of strings that represent each line
 */
private static String[] parseLines(String text) {...}


//----------------------------------------------------------------
//Example 8: sloppy exception handling during file writes
//----------------------------------------------------------------

public void persistFileContents() {
    String fileContentAsString = "";
    for (String line: this.fileContents) {
        fileContentAsString += line + '\n';
    }
    try {
        File file = new File(this.fp);
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(fileContentAsString);
        fileWriter.flush();
        fileWriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void save() {
    try {
        BufferedWriter writer = new BufferedWriter(new FileWriter(filename));        
        for (int i = 0; i < lines.size(); i++) {
            writer.write(lines.get(i));
        }
        writer.close();
    } catch (IOException ex) {
        System.err.println("Unable to write to file: " + filename + ".");
        System.exit(1);
    }
}

private ArrayList<String> getFileContents(String fp) {
    ...
    try {
        // FileReader reads text files in the default encoding.
        FileReader fileReader =
                new FileReader(fp);

        ...

        // Always close files.
        bufferedReader.close();
    }
    catch(FileNotFoundException ex) {
        System.out.println(
                "Unable to open file '" +
                        fp + "'");
    }
    catch(IOException ex) {
        System.out.println(
                "Error reading file '"
                        + fp + "'");
    }
}


//----------------------------------------------------------------
//Example 9: OK not to report error
//----------------------------------------------------------------


public static synchronized void log(Level level, String message) {
    if (!LOGGING_ON || level.intValue() < THRESHOLD.intValue()) {
        return;
    }
    
    String formattedRecord = format(level, message);
    
    // Append the log file. If the file does not exists, create it first.
    try {
        Files.write(LOG_PATH,
                    formattedRecord.getBytes(),
                    StandardOpenOption.CREATE,
                    StandardOpenOption.APPEND);
    }
    catch (IOException ioe) {
        System.err.println("Error writing to the log file '"
                         + LOG_FILE_PATHNAME + "'. Details: " + NEW_LINE
                         + ioe.getMessage());
    }
}


//----------------------------------------------------------------
//Example 10: how will caller deal with error?
//----------------------------------------------------------------

/**
 * Returns a copy with the list of lines whose index is greater or equal to
 * the {@code startIndex} and less than or equal to {@code endIndex}.
 * 
 * @param startIndex The lower bound (inclusive) for the line index 
 * @param endIndex The upper bound (inclusive) for the line index 
 * 
 * @return a copy with the list of lines whose index is in the given range
 * 
 * @throws IndexOutOfBoundsException if the {@code startIndex} is negative
 *                                   or greater then the {@code endIndex} or
 *                                   the {@code endIndex} is greater than
 *                                   the maximum line index
 */
public final List<String> getLinesInRange(int startIndex, int endIndex) {
    if (startIndex < 0 || startIndex > endIndex) {
        throw new IndexOutOfBoundsException(
              "The 'startIndex' is negative or greater than the 'endIndex");
    }
    if (endIndex > lines.size() - 1) {
        throw new IndexOutOfBoundsException(
             "The 'endIndex' is greater than " + (lines.size() - 1));
    }
    
    List<String> linesInRange = new LinkedList<String>();
    for (int i = startIndex; i <= endIndex; i++) {
        linesInRange.add(lines.get(i));
    }
    
    return linesInRange;
}


//----------------------------------------------------------------
//Example 11: class definitions embedded in method
//----------------------------------------------------------------

private void initializeWindowEventListeners(TextWindow window) {
    final int noKeyModifiers = 0;

    // WINDOW CLOSING
    window.setWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
            ...
        }
    });

    // FOCUS
    window.setFocusListener(new FocusAdapter() {
        @Override
        public void focusGained(FocusEvent e) {
            ...;
        }

        @Override
        public void focusLost(FocusEvent e) {
            ...
        }
    });

    // RESIZE
    window.setResizeListener(new ComponentAdapter() {
        @Override
        public void componentResized(ComponentEvent e) {
            ...
        }
    });

    // ... 16 more listeners after this ...
}