import Game;

@localized('en', 'Graphics2D')
@localized('es', 'Graficos2D')
namespace Graphics2D {

    enum _DrawCommands {
        RECTANGLE = 1,
        ELLIPSE = 2,
        LINE = 3,
        TRIANGLE = 4,
        QUAD = 5,
        IMAGE = 6,

        TEXT = 7, // Not used directly in Graphics2D. Injected by GraphicsText
    }

    static class Draw {

        static field _queueOld = new Core.Object();
        static field _queue = new Core.Object();
        static field _text_queueOld = null;
        static field _text_queue = null;

        static constructor() {
            // essentially no-ops, but runs the queue initializers.
            $lib_graphics2d_renderQueueAction(Graphics2D.Draw._queueOld, Graphics2D.Draw._text_queueOld, 0);
            $lib_graphics2d_renderQueueAction(Graphics2D.Draw._queue, Graphics2D.Draw._text_queue, 0);

            Game.GameWindow._frameEndHandlers.add(Graphics2D.Draw._renderer);
            Game.GameWindow._frameBeginHandlers.add(Graphics2D.Draw._rendererReset);
        }

        static function _renderer() {
            // Go through and cap the color arguments to a proper range.
            // Doing this here is two-fold:
            // - only one function call. User-facing draw calls are unencumbered with extra logic.
            // - This is done in Pastel code. Platform-specific renders will get canonicalized data.
            $lib_graphics2d_renderQueueValidateArgs(Graphics2D.Draw._queue);

            // send the render queue to the platform renderer
            $lib_graphics2d_renderQueueAction(Graphics2D.Draw._queue, Graphics2D.Draw._text_queue, 1);

            // Swap the buffers. The old buffer stays in memory, in tact, while new events get drawn to [text]queue.
            t = Graphics2D.Draw._queue;
            Graphics2D.Draw._queue = Graphics2D.Draw._queueOld;
            Graphics2D.Draw._queueOld = t;

            t = Graphics2D.Draw._text_queue;
            Graphics2D.Draw._text_queue = Graphics2D.Draw._text_queueOld;
            Graphics2D.Draw._text_queueOld = t;
        }

        static function _rendererReset() {
            // clear the render queue
            Draw._text_queue = null;
            return $lib_graphics2d_renderQueueAction(Graphics2D.Draw._queue, Graphics2D.Draw._text_queue, 2);
        }

        static function ellipse(left, top, width, height, r, g, b, a = 255) {
            return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                _DrawCommands.ELLIPSE,
                left, top, width, height, r, g, b, a);
        }

        static function circle(centerX, centerY, radius, r, g, b, a = 255) {
            // This can't be inlined as it is currently written as it uses values indirectly and multiple times.
            // TODO: Add a CIRCLE draw command to get around this problem.
            return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                _DrawCommands.ELLIPSE,
                centerX - radius, centerY - radius, radius * 2, radius * 2, r, g, b, a);
        }

        static function line(startX, startY, endX, endY, strokeWidth, r, g, b, a = 255) {
            if (strokeWidth < 1) return;

            // casts all locals that are floats into ints.
            // This is needed because ints are needed for the next two if statements and
            // intQueueWrite16 runs too late.
            $$$('convertFloatArgsToInts');

            // If the line is horizontal or vertical, use the rectangle drawing code instead.
            // This guarantees clean crisp 1-pixel lines across platforms which is actually
            // probably the most common use case.

            if (startY == endY) { // horizontal
                return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                    _DrawCommands.RECTANGLE,
                    $$$('min', startX, endX), // left
                    startY - strokeWidth / 2, // top
                    $$$('abs', endX - startX), // width
                    strokeWidth, // height
                    r, g, b, a);
            }

            if (startX == endX) { // vertical
                return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                    _DrawCommands.RECTANGLE,
                    startX - strokeWidth / 2, // left
                    $$$('min', startY, endY), // top
                    strokeWidth, // width
                    $$$('abs', endY - startY), // height
                    r, g, b, a);
            }

            /*
                GL-based platforms use the quadrilateral method to draw lines.
                For other platforms, the capping behavior of lines tends to vary
                so only use the native line drawing method when the line is skinny
                and different caps aren't noticeable. (1 or 2 pixels wide)

                For everything else, calculate the coordinates of the quadrilateral
                that represents this line and use the platform's polygon drawing method
                instead.
            */
            if (strokeWidth < 3 && !$lib_graphics2d_isOpenGlBased()) {
                return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                    _DrawCommands.LINE,
                    startX, startY, endX, endY, strokeWidth, r, g, b, a);
            }

            $$$('intQueueWrite16', Graphics2D.Draw._queue,
                _DrawCommands.QUAD,
                startX, startY, endX, endY, strokeWidth, 0, 0, 0, r, g, b, a);
            // The above command is just a placeholder. Do the rest of the
            // transformation in translated code...
            return $lib_graphics2d_lineToQuad(Graphics2D.Draw._queue);
        }

        static function rectangle(left, top, width, height, r, g, b, a = 255) {
            return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                _DrawCommands.RECTANGLE,
                left, top, width, height, r, g, b, a);
        }

        static function triangle(ax, ay, bx, by, cx, cy, r, g, b, a = 255) {
            return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                _DrawCommands.TRIANGLE,
                ax, ay, bx, by, cx, cy, r, g, b, a);
        }

        static function quad(ax, ay, bx, by, cx, cy, dx, dy, r, g, b, a = 255) {
            // TODO: if this makes a bowtie, and this is an OpenGL based platform,
            // decompose this into two triangle draws.
            return $$$('intQueueWrite16', Graphics2D.Draw._queue,
                _DrawCommands.QUAD,
                ax, ay, bx, by, cx, cy, dx, dy, r, g, b, a);
        }

        static function fill(r, g, b) {
            t = Game.GameWindow.instance;
            $lib_graphics2d_renderQueueAction(Graphics2D.Draw._queue, Graphics2D.Draw._text_queue, 2);
            return Graphics2D.Draw.rectangle(0, 0, t._w, t._h, r, g, b);
        }
    }
}
