package math.axes;

import math.shapes.Arrow;
import math.utils.MathUtil;

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

public class Ax {
    private final Graphics2D g2;
    private static Font titleFont;
    private static Font axFont;
    private final float rx;
    private final float ry;
    private final float rw;
    private final float rh;
    private final float sx;
    private final float sy;
    private final float sw;
    private final float sh;
    private float sx0, sy0;
    private float rx0, ry0;
    private final float maxrx;
    private final float maxry;
    private final Rzutowanie rzut;
    private final float rxunit;
    private final float ryunit;
    private final int offset;

    public Ax(float sx, float sy, float sw, float sh, float rx, float ry,
              float rw, float rh, Graphics2D g2, float rxunit, float ryunit,
              int offset) {
        this.rx = rx;
        this.ry = ry;
        this.rw = rw;
        this.rh = rh;
        this.sx = sx;
        this.sy = sy;
        this.sw = sw;
        this.sh = sh;
        this.g2 = g2;
        this.rxunit = rxunit;
        this.ryunit = ryunit;
        this.offset = offset;
        rzut = new Rzutowanie(sx, sy, sw, sh, rx, ry, rw, rh);
        maxrx = rx + rw;
        if (rx >= 0) {
            sx0 = sx;
            rx0 = rx;
        } else if (rx < 0 && maxrx < 0) {
            sx0 = sx + sw;
            rx0 = rx + rw;
        } else if (rx < 0 && maxrx >= 0) {
            rx0 = 0;
            sx0 = sw * Math.abs(rx / rw);
        }
        maxry = ry + rh;
        if (ry >= 0) {
            sy0 = sy;
            ry0 = ry;
        } else if (ry < 0 && maxry < 0) {
            sy0 = sh + sy;
            ry0 = ry + rh;
        } else if (ry < 0 && maxry >= 0) {
            ry0 = 0;
            sy0 = sh * Math.abs(ry / rh);
        }
        int size1 = (int) (12 + (sw / 200.0f));
        titleFont = new Font("Dialog", Font.BOLD, size1);
        int size2 = (int) (10 + (sw / 200.0f));
        axFont = new Font("Dialog", Font.PLAIN, size2);
    }

    public void drawAx(Color xcolor, Color ycolor) {
        Arrow xr = null;
        Arrow xl = null;
        Arrow yt = null;
        Arrow yb = null;
        Color acolor = g2.getColor();
        g2.setColor(xcolor);
        if (rx >= 0) {
            xr = new Arrow(sx0 + offset, sh - sy0 + offset, sw, 1f, 0f, 15f,
                    10f, true);
            g2.fill(xr);
        } else if (rx < 0 && maxrx < 0) {
            xl = new Arrow(sx0 + offset, sy + offset, sw, 1f, 180f, 15f, 10f,
                    true);
            g2.fill(xl);
        } else if (rx < 0 && maxrx >= 0) {
            xr = new Arrow(sx0 + offset + sx, sh - sy0 + offset + sy, sw - sx0,
                    1f, 0f, 15f, 10f, true);
            g2.fill(xr);
            xl = new Arrow(sx0 + offset + sx, sh - sy0 + offset + sy, sx0, 1f,
                    180f, 15f, 10f, true);
            g2.fill(xl);
        }
        g2.setColor(ycolor);
        if (ry >= 0) {
            yt = new Arrow(sx0 + offset, sh + sy0 + offset, sh, 1f, 90f, 15f,
                    10f, true);
            g2.fill(yt);
        } else if (ry < 0 && maxry < 0) {
            yb = new Arrow(sx0 + offset, sy + offset, sh, 1f, 270f, 15f, 10f,
                    true);
            g2.fill(yb);
        } else if (ry < 0 && maxry >= 0) {
            yb = new Arrow(sx0 + offset + sx, sh - sy0 + offset + sy, sy0, 1f,
                    270f, 15f, 10f, true);
            g2.fill(yb);
            yt = new Arrow(sx0 + offset + sx, sh - sy0 + offset + sy, sh - sy0,
                    1f, 90f, 15f, 10f, true);
            g2.fill(yt);
        }
        g2.setColor(acolor);
    }

    public void drawString(String napis, float realx, float realy) {
        g2.drawString(napis, rzut.getSX(realx) + offset, sh - rzut.getSY(realy)
                + offset + 2 * sy);
    }

    public void drawTitle(String tytul) {
        Font actFont = g2.getFont();
        g2.setFont(titleFont);
        FontMetrics fm = g2.getFontMetrics(g2.getFont());
        int w = fm.stringWidth(tytul);
        float x = sx + (sw - w) / 2;
        int h = fm.getHeight();
        float y = sy + h;
        g2.drawString(tytul, x + offset, y + offset - 1.7f * h);
        g2.setFont(actFont);
    }

    public void drawPoint(float rx, float ry, float diam, PShape pshape,
                          Color kolor) {
        float xe = rzut.getSX(rx);
        float ye = rzut.getSY(ry);
        float r = diam / 2f;
        Color acolor = g2.getColor();
        g2.setColor(kolor);
        switch (pshape) {
            case Circle:
                Ellipse2D.Float el = new Ellipse2D.Float(xe - r + offset, sh
                        - ye - r + offset + 2 * sy, diam, diam);
                g2.draw(el);
                break;
            case Square:
                Rectangle2D.Float re = new Rectangle2D.Float(xe - r + offset,
                        sh - ye - r + offset + 2 * sy, diam, diam);
                g2.draw(re);
                break;
            case Cross:
                Line2D.Float li1 = new Line2D.Float(xe - r + offset, sh - ye
                        + offset + 2 * sy, xe + r + offset, sh - ye + offset
                        + 2 * sy);
                g2.draw(li1);
                Line2D.Float li2 = new Line2D.Float(xe + offset, sh - ye - r
                        + offset + 2 * sy, xe + offset, sh - ye + r + offset
                        + 2 * sy);
                g2.draw(li2);
                break;
        }
        g2.setColor(acolor);
    }

    public void drawLine(float rx1, float ry1, float rx2, float ry2, Color color) {
        Color actColor = g2.getColor();
        g2.setColor(color);
        Line2D.Float line = new Line2D.Float(rzut.getSX(rx1) + sx + offset, sh
                - rzut.getSY(ry1) + sy + offset, rzut.getSX(rx2) + sx + offset,
                sh - rzut.getSY(ry2) + sy + offset);
        g2.draw(line);
        g2.setColor(actColor);
    }

    public void fillPoint(float rx, float ry, float diam, PShape pshape,
                          Color kolor) {
        float xe = rzut.getSX(rx);
        float ye = rzut.getSY(ry);
        float r = diam / 2f;
        Color acolor = g2.getColor();
        g2.setColor(kolor);
        switch (pshape) {
            case Circle:
                Ellipse2D.Float el = new Ellipse2D.Float(xe - r + offset, sh
                        - ye - r + offset + 2 * sy, diam, diam);
                g2.fill(el);
                break;
            case Square:
                Rectangle2D.Float re = new Rectangle2D.Float(xe - r + offset,
                        sh - ye - r + offset + 2 * sy, diam, diam);
                g2.fill(re);
                break;
            case Cross:
                Line2D.Float li1 = new Line2D.Float(xe - r + offset, sh - ye
                        + offset + 2 * sy, xe + r + offset, sh - ye + offset
                        + 2 * sy);
                g2.draw(li1);
                Line2D.Float li2 = new Line2D.Float(xe + offset, sh - ye - r
                        + offset + 2 * sy, xe + offset, sh - ye + r + offset
                        + 2 * sy);
                g2.draw(li2);
                break;
        }
        g2.setColor(acolor);
    }

    public void drawXLegend(boolean noprec) {
        Font actFont = g2.getFont();
        g2.setFont(axFont);
        FontMetrics fm = g2.getFontMetrics(actFont);
        int w = 0;
        int h = fm.getHeight();
        float y = 0;
        if (ry >= 0) {
            y = sh + sy0 + offset;
            for (float i = rx; i <= rx + rw; i += rxunit) {
                String str = null;
                if (noprec) {
                    str = Integer.toString((int) i);
                } else {
                    str = Float.toString(round(i, rxunit));
                }
                w = fm.stringWidth(str);
                float x = rzut.getSX(round(i, rxunit)) - w / 2.0f;
                g2.drawString(str, x + offset, y + 0.75f * h);
            }
        } else if (ry < 0 && maxry < 0) {
            y = sy + offset;
            for (float i = rx; i <= rx + rw; i += rxunit) {
                String str = null;
                if (noprec) {
                    str = Integer.toString((int) i);
                } else {
                    str = Float.toString(round(i, rxunit));
                }
                w = fm.stringWidth(str);
                float x = rzut.getSX(round(i, rxunit)) - w / 2.0f;
                g2.drawString(str, x + offset, y - 0.2f * h);
            }
        } else if (ry < 0 && maxry > 0) {
            y = sh - sy0 + offset + sy;
            if (sh / 2.0f < sy0) {
                for (float i = rx; i <= rx + rw; i += rxunit) {
                    String str = null;
                    if (noprec) {
                        str = Integer.toString((int) i);
                    } else {
                        str = Float.toString(round(i, rxunit));
                    }
                    w = fm.stringWidth(str);
                    float x = rzut.getSX(round(i, rxunit)) - w / 2.0f;
                    g2.drawString(str, x + offset, y - 0.2f * h);
                }
            } else {
                for (float i = rx; i <= rx + rw; i += rxunit) {
                    String str = null;
                    if (noprec) {
                        str = Integer.toString((int) i);
                    } else {
                        str = Float.toString(round(i, rxunit));
                    }
                    w = fm.stringWidth(str);
                    float x = rzut.getSX(round(i, rxunit)) - w / 2.0f;
                    g2.drawString(str, x + offset, y + 0.75f * h);
                }
            }
        }
        g2.setFont(actFont);
    }

    public void drawYLegend(boolean noprec) {
        Font actFont = g2.getFont();
        g2.setFont(axFont);
        FontMetrics fm = g2.getFontMetrics(actFont);
        int w = 0;
        int h = fm.getHeight();
        float y = 0;
        if (ry >= 0) {
            for (float i = ry; i <= ry + rh; i += ryunit) {
                String str = null;
                if (noprec) {
                    str = Integer.toString((int) i);
                } else {
                    str = Float.toString(round(i, ryunit));
                }
                w = fm.stringWidth(str);
                y = sh - rzut.getSY(round(i, ryunit)) + 0.25f * h;
                g2.drawString(str, sx + offset - w, y + offset + 2 * sy);
            }
        } else if (ry < 0 && maxry < 0) {
            for (float i = ry; i <= ry + rh; i += ryunit) {
                String str = null;
                if (noprec) {
                    str = Integer.toString((int) i);
                } else {
                    str = Float.toString(round(i, ryunit));
                }
                w = fm.stringWidth(str);
                y = sh - rzut.getSY(round(i, ryunit)) + offset + 2 * sy + 0.25f
                        * h;
                g2.drawString(str, sx + sw + offset + 0.3f * w, y);
            }
        } else if (ry < 0 && maxry > 0) {
            if (sh / 2.0f < sy0) {
                for (float i = ry; i <= ry + rh; i += ryunit) {
                    String str = null;
                    if (noprec) {
                        str = Integer.toString((int) i);
                    } else {
                        str = Float.toString(round(i, ryunit));
                    }
                    w = fm.stringWidth(str);
                    y = sh - rzut.getSY(round(i, ryunit)) + offset + 2 * sy
                            + 0.25f * h;
                    g2.drawString(str, sx + sx0 + offset + 0.3f * w, y);
                }
            } else {
                for (float i = ry; i <= ry + rh; i += ryunit) {
                    String str = null;
                    if (noprec) {
                        str = Integer.toString((int) i);
                    } else {
                        str = Float.toString(round(i, ryunit));
                    }
                    w = fm.stringWidth(str);
                    y = sh - rzut.getSY(round(i, ryunit)) + 0.25f * h + sy;
                    g2.drawString(str, sx0 + sx + +offset - w, y + offset + sy);
                }
            }
        }
        g2.setFont(actFont);
    }

    private void drawVertLines() {
        if (sy0 == sy) {
            for (float i = rx; i <= rx + rw; i += rxunit) {
                float x = rzut.getSX(round(i, rxunit));
                Line2D.Float line = new Line2D.Float(x + offset, 0f + offset
                        + sy, x + offset, sh + offset + sy);
                g2.draw(line);
            }
        } else if (sy0 == sy + sh) {
            for (float i = rx; i <= rx + rw; i += rxunit) {
                float x = rzut.getSX(round(i, rxunit));
                Line2D.Float line1 = new Line2D.Float(x + offset, 0f + offset
                        + sy, x + offset, sh + offset + sy);
                g2.draw(line1);
            }
        } else {
            for (float i = rx; i <= rx + rw; i += rxunit) {
                float x = rzut.getSX(round(i, rxunit));
                Line2D.Float line1 = new Line2D.Float(x + offset, 0f + offset
                        + sy, x + offset, sh + offset + sy);
                g2.draw(line1);
            }
        }
    }

    private void drawHorizLines() {
        if (sy0 == sy) {
            for (float i = ry; i <= ry + rh; i += ryunit) {
                float y = rzut.getSY(round(i, ryunit));
                Line2D.Float line = new Line2D.Float(offset + sx, y + offset,
                        sx + sw + offset, y + offset);
                g2.draw(line);
            }
        } else if (sy0 == sy + sh) {
            for (float i = ry; i <= ry + rh; i += ryunit) {
                float y = rzut.getSY(round(i, ryunit));
                Line2D.Float line = new Line2D.Float(sx + offset, y + offset,
                        sx + offset + sw, y + offset);
                g2.draw(line);
            }
        } else {
            for (float i = ry; i <= ry + rh; i += ryunit) {
                float y = rzut.getSY(round(i, ryunit));
                Line2D.Float line = new Line2D.Float(sx + offset, y + offset,
                        sx + offset + sw, y + offset);
                g2.draw(line);
            }
        }
    }

    public void drawGrid(Color color) {
        Color actColor = g2.getColor();
        g2.setColor(color);
        drawVertLines();
        drawHorizLines();
        g2.setColor(actColor);
    }

    public float round(float value, float comp) {
        int dec = MathUtil.getFractLen(comp);
        return MathUtil.roundToDecimal(value, dec);
    }

    public Graphics2D getG2() {
        return g2;
    }

    public static Font getTitleFont() {
        return titleFont;
    }

    public static Font getAxFont() {
        return axFont;
    }

    public float getRx() {
        return rx;
    }

    public float getRy() {
        return ry;
    }

    public float getRw() {
        return rw;
    }

    public float getRh() {
        return rh;
    }

    public float getSx() {
        return sx;
    }

    public float getSy() {
        return sy;
    }

    public float getSw() {
        return sw;
    }

    public float getSh() {
        return sh;
    }

    public float getSx0() {
        return sx0;
    }

    public float getSy0() {
        return sy0;
    }

    public float getRx0() {
        return rx0;
    }

    public float getRy0() {
        return ry0;
    }

    public float getMaxrx() {
        return maxrx;
    }

    public float getMaxry() {
        return maxry;
    }

    public Rzutowanie getRzut() {
        return rzut;
    }

    public float getRxunit() {
        return rxunit;
    }

    public float getRyunit() {
        return ryunit;
    }

    public int getOffset() {
        return offset;
    }
}
