package math.fractals;

import math.utils.Tuple3d;

import javax.swing.*;
import java.awt.*;
import java.util.Stack;

public class Turtle extends JPanel {
    private static final long serialVersionUID = -5183600697149041028L;
    private static final int w = 800;
    private static final int h = 800;
    private double tx;
    private double ty;
    private double headDir;
    private final double width;
    private final String[] vars;
    private final String start;
    private final String[] rules;
    private final double scale;
    private final int steps;
    private final double angle;
    private final double distance;
    private final String frules;
    private final Stack<Tuple3d> memo;
    private final int dx;
    private final int dy;

    public Turtle(double tx, double ty, double headDir, double width,
                  String[] vars, String start, String[] rules, double scale,
                  int steps, double angle, int dx, int dy) {
        this.tx = tx;
        this.ty = ty;
        this.headDir = headDir;
        this.width = width;
        this.vars = vars;
        this.start = start;
        this.rules = rules;
        this.scale = scale;
        this.steps = steps;
        this.distance = getDistance(steps, scale);
        this.frules = process(steps, start, rules, vars);
        this.angle = angle;
        this.dx = dx;
        this.dy = dy;
        memo = new Stack<>();
        setPreferredSize(new Dimension(w, h));
        setBackground(Color.WHITE);
        setSize(w, h);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2.drawRect(0, 0, w - 1, h - 1);
        g2.translate(dx, dy);
        String a = "";
        for (int i = 0; i < this.frules.length(); i++) {
            a = this.frules.substring(i, i + 1);
            switch (a) {
                case "F":
                case "G":
                    move(g2, this.distance, true);
                    break;
                case "X":
                case "Y":
                case "Z":
                    break;
                case "f":
                    this.move(g2, this.distance, false);
                    break;
                case "+":
                    this.headDir += this.angle;
                    break;
                case "-":
                    this.headDir -= this.angle;
                    break;
                case "[": {
                    Tuple3d t = new Tuple3d(this.tx, this.ty, this.headDir);
                    memo.push(t);
                    break;
                }
                case "]": {
                    Tuple3d t = memo.pop();
                    tx = t.getX();
                    ty = t.getY();
                    headDir = t.getZ();
                    break;
                }
            }
        }
    }

    public void tailDown(Graphics2D g2, double x, double y) {
        g2.drawLine((int) this.tx, (int) this.ty, (int) this.tx + (int) x,
                (int) this.ty + (int) y);
    }

    public void move(Graphics2D g2, double distance, boolean tailDown) {
        double x = Math.cos(this.headDir) * distance;
        double y = Math.sin(this.headDir) * distance;
        if (tailDown) {
            tailDown(g2, x, y);
        }
        this.tx += x;
        this.ty += y;
        //Util.print(tx + " " + ty);
    }

    public String process(int steps, String start, String[] rules, String[] vars) {
        String result = start;
        String result1 = "";
        for (int i = 0; i < steps; i++) {
            for (int j = 0; j < result.length(); j++) {
                String temp = result.substring(j, j + 1);
                for (int k = 0; k < vars.length; k++) {
                    if (temp.equals(vars[k])) {
                        result1 = result1.concat(rules[k]);
                    }
                }
            }
            result = result1;
            result1 = "";
        }
        return result;
    }

    public double getDistance(int steps, double scale) {
        double wt1 = this.width;
        for (int i = 0; i < steps; i++) {
            wt1 *= scale;
        }
        return wt1;
    }

    public static int getW() {
        return w;
    }

    public static int getH() {
        return h;
    }

    public double getTx() {
        return tx;
    }

    public double getTy() {
        return ty;
    }

    public double getHeadDir() {
        return headDir;
    }

    public String[] getVars() {
        return vars;
    }

    public String getStart() {
        return start;
    }

    public String[] getRules() {
        return rules;
    }

    public double getScale() {
        return scale;
    }

    public int getSteps() {
        return steps;
    }

    public double getAngle() {
        return angle;
    }

    public double getDistance() {
        return distance;
    }

    public String getFrules() {
        return frules;
    }
}
