import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

public class MandalaDemo extends JPanel implements ActionListener, KeyListener {

    // Canvas size
    private final int WIDTH = 1024;
    private final int HEIGHT = 576;
    private final int CENTER_X = WIDTH / 2;
    private final int CENTER_Y = HEIGHT / 2;

    private Timer timer;
    private double time = 0;

    // Layer structure
    abstract class Layer {
        Color color;
        abstract void draw(Graphics2D g2d, double t);
    }

    class CircleLayer extends Layer {
        int radius, thickness;
        CircleLayer(int r, int t, Color c) { radius = r; thickness = t; color = c; }
        void draw(Graphics2D g2d, double t) {
            g2d.setColor(color);
            for(int i=0;i<thickness;i++)
                g2d.drawOval(CENTER_X - radius - i, CENTER_Y - radius - i, (radius+i)*2, (radius+i)*2);
        }
    }

    class PolygonLayer extends Layer {
        int radius, sides;
        PolygonLayer(int r, int s, Color c){ radius=r; sides=s; color=c;}
        void draw(Graphics2D g2d, double t){
            int symmetry = 12;
            for(int rot=0; rot<symmetry; rot++){
                double baseAngle = rot * 2*Math.PI/symmetry;
                int[] xs = new int[sides];
                int[] ys = new int[sides];
                for(int i=0;i<sides;i++){
                    double angle = baseAngle + i*2*Math.PI/sides;
                    xs[i] = CENTER_X + (int)(radius*Math.cos(angle));
                    ys[i] = CENTER_Y + (int)(radius*Math.sin(angle));
                }
                g2d.setColor(color);
                g2d.drawPolygon(xs, ys, sides);
            }
        }
    }

    class StarLayer extends Layer {
        int radius, innerRadius, points;
        StarLayer(int r, int ir, int p, Color c){ radius=r; innerRadius=ir; points=p; color=c;}
        void draw(Graphics2D g2d, double t){
            int[] xs = new int[points*2];
            int[] ys = new int[points*2];
            for(int i=0;i<points*2;i++){
                double angle = i*Math.PI/points - Math.PI/2;
                int r = (i%2==0)? radius : innerRadius;
                xs[i] = CENTER_X + (int)(r*Math.cos(angle));
                ys[i] = CENTER_Y + (int)(r*Math.sin(angle));
            }
            g2d.setColor(color);
            g2d.fillPolygon(xs, ys, points*2);
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.drawPolygon(xs, ys, points*2);
        }
    }

    class PetalLayer extends Layer {
        int radius, count, size;
        PetalLayer(int r, int c, int s, Color col){ radius=r; count=c; size=s; color=col;}
        void draw(Graphics2D g2d, double t){
            for(int i=0;i<count;i++){
                double angle = i*2*Math.PI/count + t*0.5;
                int cx = CENTER_X + (int)(radius*Math.cos(angle));
                int cy = CENTER_Y + (int)(radius*Math.sin(angle));
                int[] xs = new int[20];
                int[] ys = new int[20];
                for(int j=0;j<20;j++){
                    double theta = j*Math.PI/10;
                    xs[j] = cx + (int)(size*Math.cos(theta)*Math.cos(angle));
                    ys[j] = cy + (int)(size*Math.cos(theta)*Math.sin(angle));
                }
                g2d.setColor(color);
                g2d.fillPolygon(xs, ys, xs.length);
                g2d.setColor(Color.WHITE);
                g2d.drawPolygon(xs, ys, xs.length);
            }
        }
    }

    private ArrayList<Layer> layers = new ArrayList<>();
    private Color[] colors;

    public MandalaDemo() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setBackground(Color.BLACK);
        generateColors();
        createLayers();
        timer = new Timer(50, this);
        timer.start();
        addKeyListener(this);
        setFocusable(true);
    }

    private void generateColors(){
        colors = new Color[20];
        for(int i=0;i<20;i++){
            double hue = i*18;
            int r = (int)(127.5*(1+Math.sin(Math.toRadians(hue))));
            int g = (int)(127.5*(1+Math.sin(Math.toRadians(hue+120))));
            int b = (int)(127.5*(1+Math.sin(Math.toRadians(hue+240))));
            colors[i] = new Color(r,g,b);
        }
    }

    private void createLayers(){
        layers.add(new CircleLayer(250,5, colors[0]));
        layers.add(new StarLayer(230,200,12, colors[1]));
        layers.add(new CircleLayer(190,3, colors[2]));
        layers.add(new PetalLayer(160,12,30, colors[3]));
        layers.add(new CircleLayer(140,2, colors[4]));
        layers.add(new PolygonLayer(120,6, colors[5]));
        layers.add(new StarLayer(100,70,8, colors[6]));
        layers.add(new CircleLayer(60,4, colors[7]));
        layers.add(new PetalLayer(50,8,15, colors[8]));
        layers.add(new CircleLayer(30,2, colors[9]));
    }

    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        // Draw radial lines
        int symmetry = 12;
        for(int i=0;i<symmetry*2;i++){
            double angle = i*Math.PI/symmetry;
            int x2 = CENTER_X + (int)(400*Math.cos(angle));
            int y2 = CENTER_Y + (int)(400*Math.sin(angle));
            g2d.setColor(i%2==0? new Color(50,50,50) : new Color(30,30,30));
            g2d.drawLine(CENTER_X, CENTER_Y, x2, y2);
        }
        for(Layer l : layers) l.draw(g2d, time);
        // Center circle
        g2d.setColor(Color.WHITE);
        g2d.fillOval(CENTER_X-10, CENTER_Y-10, 20,20);
        g2d.setColor(colors[0]);
        g2d.fillOval(CENTER_X-8, CENTER_Y-8, 16,16);
    }

    @Override
    public void actionPerformed(ActionEvent e){
        time += 0.05;
        repaint();
    }

    @Override
    public void keyPressed(KeyEvent e){
        if(e.getKeyCode()==KeyEvent.VK_ESCAPE) System.exit(0);
        if(e.getKeyCode()==KeyEvent.VK_S){
            BufferedImage bi = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = bi.createGraphics();
            paint(g2d);
            g2d.dispose();
            try{
                javax.imageio.ImageIO.write(bi, "png", new java.io.File("mandala.png"));
                System.out.println("Saved mandala.png");
            }catch(Exception ex){ex.printStackTrace();}
        }
    }
    @Override public void keyReleased(KeyEvent e){}
    @Override public void keyTyped(KeyEvent e){}

    public static void main(String[] args){
        JFrame frame = new JFrame("Mandala");
        MandalaDemo demo = new MandalaDemo();
        frame.add(demo);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}
