/*
    Редактор шрифтов UFN
    Эта программа создана специально для программы Малик Эмулятор.

    Copyright © 2019, 2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package ru.malik.elaborarer.ufneditor;

import com.nokia.mid.ui.*;
import javax.microedition.lcdui.*;
import malik.emulator.fileformats.font.*;
import malik.emulator.fileformats.font.UnicodeRasterFont.*;

public final class GlyphEditor extends FontCustomItem
{
    public static final int MODE_DRAW = 0;
    public static final int MODE_MOVE = 1;
    public static final int MODE_SIZE = 2;
    public static final int MINIMUM_ZOOM = 4;
    public static final int MAXIMUM_ZOOM = 16;
    private static final int LABEL_NONE         = 0;
    private static final int LABEL_GLYPH_WIDTH  = 1;
    private static final int LABEL_FONT_HEIGTH  = 2;
    private static final int LABEL_FONT_CAPITAL = 3;
    private static final int LABEL_FONT_SMALL   = 4;
    private static final int LABEL_FONT_BASE    = 5;
    private static final int COLOR_GRID    = 0x66404040;
    private static final int COLOR_AXIS    = 0x9f000000;
    private static final int COLOR_FRAME   = 0x66004000;
    private static final int COLOR_PIXEL   = 0xbf000000;
    private static final int COLOR_SMALL   = 0x66008000;
    private static final int COLOR_CAPITAL = 0x66006000;
    private static final int COLOR_BORDER  = 0xcc00bf00;

    private static final Image LABELS;

    static {
        Image labels;
        try
        {
            labels = Image.createImage("/labels.png");
        }
        catch(Exception e)
        {
            labels = Image.createImage(2, 5);
        }
        LABELS = labels;
    }

    private static boolean isPointerIn(int left, int top, int width, int height, int x, int y) {
        return x >= left && x < left + width && y >= top && y < top + height;
    }

    private boolean opaque;
    private boolean focused;
    private int coords;
    private int label;
    private int zoom;
    private int mode;
    private Glyph glyph;

    public GlyphEditor(UnicodeRasterFont font) {
        super(font);
        this.zoom = 8;
    }

    public void notifyGlyphChanged() {
        repaint();
    }

    public void zoomIn() {
        int zoom;
        if((zoom = this.zoom) < MAXIMUM_ZOOM) this.zoom = zoom + 1;
        invalidate();
    }

    public void zoomOut() {
        int zoom;
        if((zoom = this.zoom) > MINIMUM_ZOOM) this.zoom = zoom - 1;
        invalidate();
    }

    public void setZoom(int zoom) {
        if(zoom < MINIMUM_ZOOM) zoom = MINIMUM_ZOOM;
        if(zoom > MAXIMUM_ZOOM) zoom = MAXIMUM_ZOOM;
        this.zoom = zoom;
        invalidate();
    }

    public void setMode(int mode) {
        if(mode < MODE_DRAW || mode > MODE_SIZE)
        {
            throw new IllegalArgumentException("GlyphEditor.setMode: аргумент mode имеет недопустимое значение.");
        }
        this.mode = mode;
        repaint();
    }

    public void setGlyph(Glyph glyph) {
        if(glyph == null)
        {
            throw new NullPointerException("GlyphEditor.setGlyph: аргумент glyph равен нулевой ссылке.");
        }
        if(glyph.parentFont() != font)
        {
            throw new IllegalArgumentException("GlyphEditor.setGlyph: аргумент glyph не принадлежит заданному шрифту.");
        }
        this.glyph = glyph;
        repaint();
    }

    public int getZoom() {
        return zoom;
    }

    public int getMode() {
        return mode;
    }

    public Glyph getGlyph() {
        return glyph;
    }

    protected void paint(Graphics renderA, int contentWidth, int contentHeight) {
        int step = zoom;
        int clipL = renderA.getClipX();
        int clipT = renderA.getClipY();
        int clipR = clipL + renderA.getClipWidth() - 1;
        int clipB = clipT + renderA.getClipHeight() - 1;
        int glyphL = getGlyphX(clipL);
        int glyphT = getGlyphY(clipT);
        int glyphR = getGlyphX(clipR);
        int glyphB = getGlyphY(clipB);
        Glyph glyph = this.glyph;
        DirectGraphics renderB = DirectUtils.getDirectGraphics(renderA);
        UnicodeRasterFont font = this.font;
        renderB.setARGBColor(COLOR_GRID);
        for(int ex = getEditorX(glyphL) + step - 1, gx = glyphL; ex <= clipR && gx <= glyphR; ex += step, gx++) if(gx != -1) renderA.drawLine(ex, clipT, ex, clipB);
        for(int ey = getEditorY(glyphT) + step - 1, gy = glyphT; ey <= clipB && gy >= glyphB; ey += step, gy--) if(gy != 0) renderA.drawLine(clipL, ey, clipR, ey);
        // координатные оси
        {
            int ex = getEditorX(-1) + step - 1;
            int ey = getEditorY(0) + step - 1;
            renderB.setARGBColor(COLOR_AXIS);
            if(ex >= clipL && ex <= clipR) renderA.drawLine(ex, clipT, ex, clipB);
            if(ey >= clipT && ey <= clipB) renderA.drawLine(clipL, ey, clipR, ey);
        }
        if(glyph != null)
        {
            int fb = font.getBaselineHeight();
            int fh = font.getHeight();
            int gw = glyph.getWidth();
            int gl;
            int gr;
            int gb = -fb - 1;
            int gt = fh + gb;
            int gbl;
            int gbr;
            int gbb;
            int gbt;
            int ex1 = getEditorX(0);
            int ex2 = getEditorX(gw) - 1;
            int ey1 = getEditorY(gt) - 1;
            int ey2 = getEditorY(gb) - 1;
            renderB.setARGBColor(COLOR_FRAME);
            renderA.drawLine(ex1, ey1, ex2, ey1);
            renderA.drawLine(ex1, ey2, ex2, ey2);
            renderA.drawLine(ex2, ey1 + 1, ex2, ey2 - 1);
            ey1 = getEditorY(font.getCapitalLettersHeight() - 1) - 1;
            ey2 = getEditorY(font.getSmallLettersHeight() - 1) - 1;
            renderB.setARGBColor(COLOR_CAPITAL);
            renderA.drawLine(ex1, ey1, --ex2, ey1);
            renderB.setARGBColor(COLOR_SMALL);
            renderA.drawLine(ex1, ey2, ex2, ey2);
            gl = gbl = glyph.getRawLeft();
            gb = gbb = glyph.getRawBottom();
            gr = gbr = glyph.getRawWidth() + gl - 1;
            gt = gbt = glyph.getRawHeight() + gb - 1;
            if(gbl < glyphL) gbl = glyphL;
            if(gbb < glyphB) gbb = glyphB;
            if(gbr > glyphR) gbr = glyphR;
            if(gbt > glyphT) gbt = glyphT;
            renderB.setARGBColor(COLOR_PIXEL);
            for(int ex0 = getEditorX(gbl), ey = getEditorY(gbt), gy = gbt; gy >= gbb; ey += step, gy--) for(int ex = ex0, gx = gbl; gx <= gbr; ex += step, gx++) if(glyph.getPixel(gx, gy))
            {
                renderA.fillRect(ex, ey, step, step);
            }
            switch(mode)
            {
            case MODE_MOVE:
                if(gr >= gl && gt >= gb)
                {
                    ex1 = getEditorX(gl) - 1;
                    ey1 = getEditorY(gt) - 1;
                    ex2 = getEditorX(gr + 1) - 1;
                    ey2 = getEditorY(gb - 1) - 1;
                    renderB.setARGBColor(COLOR_BORDER);
                    renderA.drawLine(ex1 + 1, --ey1, ex2 - 1, ey1);
                    renderA.drawLine(ex1, ++ey1, ex2, ey1);
                    renderA.drawLine(ex1 - 1, ++ey1, ex2 + 1, ey1);
                    renderA.drawLine(ex1 - 1, --ey2, ex2 + 1, ey2);
                    renderA.drawLine(ex1, ++ey2, ex2, ey2);
                    renderA.drawLine(ex1 + 1, ++ey2, ex2 - 1, ey2);
                    ey1++;
                    ey2 -= 3;
                    renderA.drawLine(--ex1, ey1, ex1, ey2);
                    renderA.drawLine(++ex1, ey1, ex1, ey2);
                    renderA.drawLine(++ex1, ey1, ex1, ey2);
                    renderA.drawLine(--ex2, ey1, ex2, ey2);
                    renderA.drawLine(++ex2, ey1, ex2, ey2);
                    renderA.drawLine(++ex2, ey1, ex2, ey2);
                }
                break;
            case MODE_SIZE:
                int iy;
                int lw;
                int lh;
                int ofs;
                int li = label;
                Image labels = LABELS;
                lw = labels.getWidth() / 2;
                lh = labels.getHeight() / 5;
                ofs = -lh / 2 + step;
                ex1 -= lw + 1;
                renderA.drawRegion(labels, li == 1 ? lw : 0, iy = 0, lw, lh, 0, ex2 + 2, getEditorY(0) + ofs, 0);
                renderA.drawRegion(labels, li == 2 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(fh - fb) + ofs, 0);
                renderA.drawRegion(labels, li == 3 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(font.getCapitalLettersHeight()) + ofs, 0);
                renderA.drawRegion(labels, li == 4 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(font.getSmallLettersHeight()) + ofs, 0);
                renderA.drawRegion(labels, li == 5 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(-fb) + ofs, 0);
                break;
            }
        }
    }

    protected void keyReleased(int key) {
        int zoom;
        switch(key)
        {
        case '1':
            if(mode != MODE_DRAW)
            {
                mode = MODE_DRAW;
                super.notifyStateChanged();
                repaint();
            }
            break;
        case '2':
            if(mode != MODE_MOVE)
            {
                mode = MODE_MOVE;
                super.notifyStateChanged();
                repaint();
            }
            break;
        case '3':
            if(mode != MODE_SIZE)
            {
                mode = MODE_SIZE;
                super.notifyStateChanged();
                repaint();
            }
            break;
        case '*':
            if((zoom = this.zoom) > MINIMUM_ZOOM)
            {
                this.zoom = zoom - 1;
                super.notifyStateChanged();
                invalidate();
            }
            break;
        case '#':
            if((zoom = this.zoom) < MAXIMUM_ZOOM)
            {
                this.zoom = zoom + 1;
                super.notifyStateChanged();
                invalidate();
            }
            break;
        }
    }

    protected void pointerPressed(int x, int y) {
        Glyph glyph;
        if((glyph = this.glyph) != null)
        {
            int gx = getGlyphX(x);
            int gy = getGlyphY(y);
            switch(mode)
            {
            case MODE_DRAW:
                glyph.putPixel(gx, gy, opaque = !glyph.getPixel(gx, gy));
                repaint();
                break;
            case MODE_SIZE:
                if((label = getLabelIndexAt(x, y)) == LABEL_NONE) glyph.putPixel(gx, gy, opaque = !glyph.getPixel(gx, gy));
                repaint();
                /* fall through */
            case MODE_MOVE:
                coords = gx & 0xffff | gy << 16;
                break;
            }
        }
    }

    protected void pointerDragged(int x, int y) {
        Glyph glyph;
        if((glyph = this.glyph) != null)
        {
            int gx = getGlyphX(x);
            int gy = getGlyphY(y);
            int oldcoords;
            int newcoords;
            switch(mode)
            {
            case MODE_DRAW:
                glyph.putPixel(gx, gy, opaque);
                repaint();
                break;
            case MODE_MOVE:
                if((oldcoords = coords) != (newcoords = coords = gx & 0xffff | gy << 16))
                {
                    glyph.move((short) newcoords - (short) oldcoords, (newcoords >> 16) - (oldcoords >> 16));
                    repaint();
                }
                break;
            case MODE_SIZE:
                switch(label)
                {
                case LABEL_GLYPH_WIDTH:
                    if((oldcoords = (short) coords) != (newcoords = (short) (coords = gx & 0xffff | gy << 16)))
                    {
                        int width = glyph.getWidth() + (newcoords - oldcoords);
                        if(width > UnicodeRasterFont.MAX_COORDINATE) width = UnicodeRasterFont.MAX_COORDINATE;
                        if(width < 0) width = 0;
                        glyph.setWidth(width);
                        repaint();
                    }
                    break;
                case LABEL_FONT_HEIGTH:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int fb;
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getHeight() + (newcoords - oldcoords);
                        if(height > (tmp = (fb = font.getBaselineHeight()) + (UnicodeRasterFont.MAX_COORDINATE + 1))) height = tmp;
                        if(height < (tmp = Math.max(font.getSmallLettersHeight(), font.getCapitalLettersHeight()) + fb)) height = tmp;
                        font.setHeight(height);
                        repaint();
                    }
                    break;
                case LABEL_FONT_CAPITAL:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getCapitalLettersHeight() + (newcoords - oldcoords);
                        if(height > (tmp = font.getHeight() - font.getBaselineHeight())) height = tmp;
                        if(height < (tmp = font.getSmallLettersHeight())) height = tmp;
                        font.setCapitalLettersHeight(height);
                        repaint();
                    }
                    break;
                case LABEL_FONT_SMALL:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getSmallLettersHeight() + (newcoords - oldcoords);
                        if(height > (tmp = font.getCapitalLettersHeight())) height = tmp;
                        if(height < 0) height = 0;
                        font.setSmallLettersHeight(height);
                        repaint();
                    }
                    break;
                case LABEL_FONT_BASE:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int fh;
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getBaselineHeight() + (oldcoords - newcoords);
                        if(height > (tmp = Math.min((fh = font.getHeight()) - font.getCapitalLettersHeight(), -UnicodeRasterFont.MIN_COORDINATE))) height = tmp;
                        if(height < (tmp = Math.max(fh - (UnicodeRasterFont.MAX_COORDINATE + 1), 0))) height = tmp;
                        font.setBaselineHeight(height);
                        repaint();
                    }
                    break;
                default:
                    glyph.putPixel(gx, gy, opaque);
                    repaint();
                    break;
                }
                break;
            }
        }
    }

    protected void pointerReleased(int x, int y) {
        Glyph glyph;
        if((glyph = this.glyph) != null)
        {
            int gx = getGlyphX(x);
            int gy = getGlyphY(y);
            int oldcoords;
            int newcoords;
            switch(mode)
            {
            case MODE_DRAW:
                glyph.putPixel(gx, gy, opaque);
                repaint();
                break;
            case MODE_MOVE:
                if((oldcoords = coords) != (newcoords = gx & 0xffff | gy << 16))
                {
                    coords = newcoords;
                    glyph.move((short) newcoords - (short) oldcoords, (newcoords >> 16) - (oldcoords >> 16));
                    repaint();
                }
                break;
            case MODE_SIZE:
                switch(label)
                {
                case LABEL_GLYPH_WIDTH:
                    if((oldcoords = (short) coords) != (newcoords = (short) (coords = gx & 0xffff | gy << 16)))
                    {
                        int width = glyph.getWidth() + (newcoords - oldcoords);
                        if(width > UnicodeRasterFont.MAX_COORDINATE) width = UnicodeRasterFont.MAX_COORDINATE;
                        if(width < 0) width = 0;
                        glyph.setWidth(width);
                    }
                    break;
                case LABEL_FONT_HEIGTH:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int fb;
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getHeight() + (newcoords - oldcoords);
                        if(height > (tmp = (fb = font.getBaselineHeight()) + (UnicodeRasterFont.MAX_COORDINATE + 1))) height = tmp;
                        if(height < (tmp = Math.max(font.getSmallLettersHeight(), font.getCapitalLettersHeight()) + fb)) height = tmp;
                        font.setHeight(height);
                    }
                    break;
                case LABEL_FONT_CAPITAL:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getCapitalLettersHeight() + (newcoords - oldcoords);
                        if(height > (tmp = font.getHeight() - font.getBaselineHeight())) height = tmp;
                        if(height < (tmp = font.getSmallLettersHeight())) height = tmp;
                        font.setCapitalLettersHeight(height);
                    }
                    break;
                case LABEL_FONT_SMALL:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getSmallLettersHeight() + (newcoords - oldcoords);
                        if(height > (tmp = font.getCapitalLettersHeight())) height = tmp;
                        if(height < 0) height = 0;
                        font.setSmallLettersHeight(height);
                    }
                    break;
                case LABEL_FONT_BASE:
                    if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
                    {
                        int fh;
                        int tmp;
                        int height;
                        UnicodeRasterFont font = this.font;
                        height = font.getBaselineHeight() + (oldcoords - newcoords);
                        if(height > (tmp = Math.min((fh = font.getHeight()) - font.getCapitalLettersHeight(), -UnicodeRasterFont.MIN_COORDINATE))) height = tmp;
                        if(height < (tmp = Math.max(fh - (UnicodeRasterFont.MAX_COORDINATE + 1), 0))) height = tmp;
                        font.setBaselineHeight(height);
                    }
                    break;
                default:
                    glyph.putPixel(gx, gy, opaque);
                    break;
                }
                label = 0;
                repaint();
                break;
            }
        }
    }

    protected void traverseOut() {
        focused = false;
    }

    protected boolean traverse(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
        int l;
        int t;
        int w;
        int h;
        Glyph glyph;
        UnicodeRasterFont font;
        if(focused && direction != NONE) return false;
        focused = true;
        font = this.font;
        if((glyph = this.glyph) == null)
        {
            l = getEditorX(0);
            t = getEditorY(0);
            w = 0;
            h = 0;
        } else
        {
            int zoom = this.zoom;
            int fh = font.getHeight();
            l = getEditorX(0);
            t = getEditorY(fh - font.getBaselineHeight() - 1);
            w = glyph.getWidth() * zoom;
            h = fh * zoom;
        }
        visibleRectangle[0] = l - (viewportWidth - w) / 2;
        visibleRectangle[1] = t - (viewportHeight - h) / 2;
        visibleRectangle[2] = viewportWidth;
        visibleRectangle[3] = viewportHeight;
        return true;
    }

    protected int getMinContentWidth() {
        return getEditorX(UnicodeRasterFont.MAX_COORDINATE + 1);
    }

    protected int getMinContentHeight() {
        return getEditorY(UnicodeRasterFont.MIN_COORDINATE - 1);
    }

    private int getGlyphX(int editorX) {
        return editorX / zoom + UnicodeRasterFont.MIN_COORDINATE;
    }

    private int getGlyphY(int editorY) {
        return -editorY / zoom + UnicodeRasterFont.MAX_COORDINATE;
    }

    private int getEditorX(int glyphX) {
        return (glyphX - UnicodeRasterFont.MIN_COORDINATE) * zoom;
    }

    private int getEditorY(int glyphY) {
        return (UnicodeRasterFont.MAX_COORDINATE - glyphY) * zoom;
    }

    private int getLabelIndexAt(int x, int y) {
        int gw;
        int fh;
        int fb;
        int lw;
        int lh;
        int ex1;
        int ex2;
        int ofs;
        Image labels = LABELS;
        Glyph glyph = this.glyph;
        UnicodeRasterFont font = this.font;
        gw = glyph.getWidth();
        fh = font.getHeight();
        fb = font.getBaselineHeight();
        lw = labels.getWidth() / 2;
        lh = labels.getHeight() / 5;
        ex1 = getEditorX(0) - lw - 1;
        ex2 = getEditorX(gw);
        ofs = -lh / 2 + zoom;
        if(isPointerIn(ex1, getEditorY(-fb) + ofs, lw, lh, x, y)) return LABEL_FONT_BASE;
        if(isPointerIn(ex1, getEditorY(font.getSmallLettersHeight()) + ofs, lw, lh, x, y)) return LABEL_FONT_SMALL;
        if(isPointerIn(ex1, getEditorY(font.getCapitalLettersHeight()) + ofs, lw, lh, x, y)) return LABEL_FONT_CAPITAL;
        if(isPointerIn(ex1, getEditorY(fh - fb) + ofs, lw, lh, x, y)) return LABEL_FONT_HEIGTH;
        if(isPointerIn(ex2, getEditorY(0) + ofs, lw, lh, x, y)) return LABEL_GLYPH_WIDTH;
        return LABEL_NONE;
    }
}
