class TerminalTextGraphics extends AbstractTextGraphics {

    private final Terminal terminal;
    private final TerminalSize terminalSize;

    private final Map<TerminalPosition, TextCharacter> writeHistory;

    private AtomicInteger manageCallStackSize;
    private TextCharacter lastCharacter;
    private TerminalPosition lastPosition;

    TerminalTextGraphics(Terminal terminal) throws IOException {
        this.terminal = terminal;
        this.terminalSize = terminal.getTerminalSize();
        this.manageCallStackSize = new AtomicInteger(0);
        this.writeHistory = new HashMap<>();
        this.lastCharacter = null;
        this.lastPosition = null;
    }

    @Override
    public TextGraphics setCharacter(int columnIndex, int rowIndex, TextCharacter textCharacter) {
        return setCharacter(new TerminalPosition(columnIndex, rowIndex), textCharacter);
    }

    @Override
    public synchronized TextGraphics setCharacter(TerminalPosition position, TextCharacter textCharacter) {
        try {
            if(manageCallStackSize.get() > 0) {
                if(lastCharacter == null || !lastCharacter.equals(textCharacter)) {
                    applyGraphicState(textCharacter);
                    lastCharacter = textCharacter;
                }
                if(lastPosition == null || !lastPosition.equals(position)) {
                    terminal.setCursorPosition(position.getColumn(), position.getRow());
                    lastPosition = position;
                }
            }
            else {
                terminal.setCursorPosition(position.getColumn(), position.getRow());
                applyGraphicState(textCharacter);
            }
            terminal.putString(textCharacter.getCharacterString());
            if(manageCallStackSize.get() > 0) {
                lastPosition = position.withRelativeColumn(1);
            }
            writeHistory.put(position, textCharacter);
        }
        catch(IOException e) {
            throw new RuntimeException(e);
        }
        return this;
    }

    @Override
    public TextCharacter getCharacter(int column, int row) {
        return getCharacter(new TerminalPosition(column, row));
    }

    @Override
    public synchronized TextCharacter getCharacter(TerminalPosition position) {
        return writeHistory.get(position);
    }

    private void applyGraphicState(TextCharacter textCharacter) throws IOException {
        terminal.resetColorAndSGR();
        terminal.setForegroundColor(textCharacter.getForegroundColor());
        terminal.setBackgroundColor(textCharacter.getBackgroundColor());
        for(SGR sgr: textCharacter.getModifiers()) {
            terminal.enableSGR(sgr);
        }
    }

    @Override
    public TerminalSize getSize() {
        return terminalSize;
    }

    @Override
    public synchronized TextGraphics drawLine(TerminalPosition fromPoint, TerminalPosition toPoint, char character) {
        try {
            enterAtomic();
            super.drawLine(fromPoint, toPoint, character);
            return this;
        }
        finally {
            leaveAtomic();
        }
    }

    @Override
    public synchronized TextGraphics drawTriangle(TerminalPosition p1, TerminalPosition p2, TerminalPosition p3, char character) {
        try {
            enterAtomic();
            super.drawTriangle(p1, p2, p3, character);
            return this;
        }
        finally {
            leaveAtomic();
        }
    }

    @Override
    public synchronized TextGraphics fillTriangle(TerminalPosition p1, TerminalPosition p2, TerminalPosition p3, char character) {<FILL_FUNCTION_BODY>}

    @Override
    public synchronized TextGraphics fillRectangle(TerminalPosition topLeft, TerminalSize size, char character) {
        try {
            enterAtomic();
            super.fillRectangle(topLeft, size, character);
            return this;
        }
        finally {
            leaveAtomic();
        }
    }

    @Override
    public synchronized TextGraphics drawRectangle(TerminalPosition topLeft, TerminalSize size, char character) {
        try {
            enterAtomic();
            super.drawRectangle(topLeft, size, character);
            return this;
        }
        finally {
            leaveAtomic();
        }
    }

    @Override
    public synchronized TextGraphics putString(int column, int row, String string) {
        try {
            enterAtomic();
            return super.putString(column, row, string);
        }
        finally {
            leaveAtomic();
        }
    }

    /**
     * It's tricky with this implementation because we can't rely on any state in between two calls to setCharacter
     * since the caller might modify the terminal's state outside of this writer. However, many calls inside
     * TextGraphics will indeed make multiple calls in setCharacter where we know that the state won't change (actually,
     * we can't be 100% sure since the caller might create a separate thread and maliciously write directly to the
     * terminal while call one of the draw/fill/put methods in here). We could just set the state before writing every
     * single character but that would be inefficient. Rather, we keep a counter of if we are inside an 'atomic'
     * (meaning we know multiple calls to setCharacter will have the same state). Some drawing methods call other
     * drawing methods internally for their implementation so that's why this is implemented with an integer value
     * instead of a boolean; when the counter reaches zero we remove the memory of what state the terminal is in.
     */
    private void enterAtomic() {
        manageCallStackSize.incrementAndGet();
    }

    private void leaveAtomic() {
        if(manageCallStackSize.decrementAndGet() == 0) {
            lastPosition = null;
            lastCharacter = null;
        }
    }
}

        try {
            enterAtomic();
            super.fillTriangle(p1, p2, p3, character);
            return this;
        }
        finally {
            leaveAtomic();
        }
    
<methods>public com.googlecode.lanterna.graphics.TextGraphics clearModifiers() ,public transient com.googlecode.lanterna.graphics.TextGraphics disableModifiers(com.googlecode.lanterna.SGR[]) ,public com.googlecode.lanterna.graphics.TextGraphics drawImage(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.graphics.TextImage) ,public com.googlecode.lanterna.graphics.TextGraphics drawImage(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.graphics.TextImage, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalSize) ,public com.googlecode.lanterna.graphics.TextGraphics drawLine(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, char) ,public com.googlecode.lanterna.graphics.TextGraphics drawLine(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TextCharacter) ,public com.googlecode.lanterna.graphics.TextGraphics drawLine(int, int, int, int, char) ,public com.googlecode.lanterna.graphics.TextGraphics drawLine(int, int, int, int, com.googlecode.lanterna.TextCharacter) ,public com.googlecode.lanterna.graphics.TextGraphics drawRectangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalSize, char) ,public com.googlecode.lanterna.graphics.TextGraphics drawRectangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalSize, com.googlecode.lanterna.TextCharacter) ,public com.googlecode.lanterna.graphics.TextGraphics drawTriangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, char) ,public com.googlecode.lanterna.graphics.TextGraphics drawTriangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TextCharacter) ,public transient com.googlecode.lanterna.graphics.TextGraphics enableModifiers(com.googlecode.lanterna.SGR[]) ,public com.googlecode.lanterna.graphics.TextGraphics fill(char) ,public com.googlecode.lanterna.graphics.TextGraphics fillRectangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalSize, char) ,public com.googlecode.lanterna.graphics.TextGraphics fillRectangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalSize, com.googlecode.lanterna.TextCharacter) ,public com.googlecode.lanterna.graphics.TextGraphics fillTriangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, char) ,public com.googlecode.lanterna.graphics.TextGraphics fillTriangle(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TextCharacter) ,public EnumSet<com.googlecode.lanterna.SGR> getActiveModifiers() ,public com.googlecode.lanterna.TextColor getBackgroundColor() ,public com.googlecode.lanterna.TextCharacter getCharacter(com.googlecode.lanterna.TerminalPosition) ,public com.googlecode.lanterna.TextColor getForegroundColor() ,public com.googlecode.lanterna.screen.TabBehaviour getTabBehaviour() ,public com.googlecode.lanterna.graphics.TextGraphics newTextGraphics(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TerminalSize) throws java.lang.IllegalArgumentException,public synchronized com.googlecode.lanterna.graphics.TextGraphics putCSIStyledString(int, int, java.lang.String) ,public com.googlecode.lanterna.graphics.TextGraphics putCSIStyledString(com.googlecode.lanterna.TerminalPosition, java.lang.String) ,public com.googlecode.lanterna.graphics.TextGraphics putString(int, int, java.lang.String) ,public com.googlecode.lanterna.graphics.TextGraphics putString(com.googlecode.lanterna.TerminalPosition, java.lang.String) ,public transient com.googlecode.lanterna.graphics.TextGraphics putString(int, int, java.lang.String, com.googlecode.lanterna.SGR, com.googlecode.lanterna.SGR[]) ,public com.googlecode.lanterna.graphics.TextGraphics putString(int, int, java.lang.String, Collection<com.googlecode.lanterna.SGR>) ,public transient com.googlecode.lanterna.graphics.TextGraphics putString(com.googlecode.lanterna.TerminalPosition, java.lang.String, com.googlecode.lanterna.SGR, com.googlecode.lanterna.SGR[]) ,public com.googlecode.lanterna.graphics.TextGraphics setBackgroundColor(com.googlecode.lanterna.TextColor) ,public com.googlecode.lanterna.graphics.TextGraphics setCharacter(int, int, char) ,public com.googlecode.lanterna.graphics.TextGraphics setCharacter(com.googlecode.lanterna.TerminalPosition, com.googlecode.lanterna.TextCharacter) ,public com.googlecode.lanterna.graphics.TextGraphics setCharacter(com.googlecode.lanterna.TerminalPosition, char) ,public com.googlecode.lanterna.graphics.TextGraphics setForegroundColor(com.googlecode.lanterna.TextColor) ,public synchronized com.googlecode.lanterna.graphics.TextGraphics setModifiers(EnumSet<com.googlecode.lanterna.SGR>) ,public com.googlecode.lanterna.graphics.TextGraphics setStyleFrom(StyleSet<?>) ,public com.googlecode.lanterna.graphics.TextGraphics setTabBehaviour(com.googlecode.lanterna.screen.TabBehaviour) <variables>protected final non-sealed EnumSet<com.googlecode.lanterna.SGR> activeModifiers,protected com.googlecode.lanterna.TextColor backgroundColor,protected com.googlecode.lanterna.TextColor foregroundColor,private final non-sealed com.googlecode.lanterna.graphics.ShapeRenderer shapeRenderer,protected com.googlecode.lanterna.screen.TabBehaviour tabBehaviour