/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.android.tools.chartlib.AnimatedComponent;
import com.android.tools.chartlib.EventData;
import com.android.tools.chartlib.SunburstComponent;
import com.android.tools.chartlib.TimelineComponent;
import com.android.tools.chartlib.TimelineData;
import com.android.tools.chartlib.ValuedTreeNode;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.tree.DefaultMutableTreeNode;

public class AnimatedComponentVisualTests extends JDialog {

    private List<AnimatedComponent> mComponents = new LinkedList<AnimatedComponent>();

    public AnimatedComponentVisualTests() {
        JPanel contentPane = new JPanel(new BorderLayout());
        JButton close = new JButton("Close");
        JTabbedPane tabs = new JTabbedPane();
        tabs.addTab("PieChart", getPieChartExample());
        tabs.addTab("Timeline", getTimelineExample());

        contentPane.setPreferredSize(new Dimension(1280, 1024));
        contentPane.add(tabs, BorderLayout.CENTER);

        JPanel bottom = new JPanel(new BorderLayout());
        bottom.add(close, BorderLayout.EAST);
        contentPane.add(bottom, BorderLayout.SOUTH);

        JPanel controls = new JPanel();
        controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
        controls.add(Box.createRigidArea(new Dimension(100, 20)));
        final JCheckBox debug = new JCheckBox("Debug");
        debug.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                for (AnimatedComponent component : mComponents) {
                    component.setDrawDebugInfo(debug.isSelected());
                }
            }
        });
        controls.add(debug);

        final JButton step = new JButton("Step");
        step.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                for (AnimatedComponent component : mComponents) {
                    component.step();
                }
            }
        });
        final JCheckBox update = new JCheckBox("Update");
        update.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                for (AnimatedComponent component : mComponents) {
                    component.setUpdateData(update.isSelected());
                }
                step.setEnabled(!update.isSelected());
            }
        });
        update.setSelected(true);
        step.setEnabled(false);
        final JCheckBox dark = new JCheckBox("Dark");
        dark.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setDarkMode(dark.isSelected());
            }
        });
        controls.add(dark);
        controls.add(update);
        controls.add(step);
        contentPane.add(controls, BorderLayout.WEST);

        setDarkMode(false);
        setContentPane(contentPane);
        setModal(true);
        getRootPane().setDefaultButton(close);

        close.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dispose();
            }
        });
    }

    private void setDarkMode(boolean dark) {
        for (AnimatedComponent c : mComponents) {
            c.setBackground(dark ? new Color(60, 63, 65) : new Color(244, 244, 244));
        }
    }

    interface Value {
        void set(int v);
        int get();
    }

    private static JPanel createVaribleSlider(String name, final int a, final int b,
            final Value value) {
        JPanel panel = new JPanel(new BorderLayout());
        final JLabel text = new JLabel();
        final JSlider slider = new JSlider(a, b);
        ChangeListener listener = new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent changeEvent) {
                value.set(slider.getValue());
                text.setText(String.format("%d [%d,%d]", slider.getValue(), a, b));
            }
        };
        slider.setValue(value.get());
        listener.stateChanged(null);
        slider.addChangeListener(listener);
        panel.add(slider, BorderLayout.CENTER);
        panel.add(new JLabel(name + ": "), BorderLayout.WEST);
        panel.add(text, BorderLayout.EAST);
        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
        return panel;
    }

    private JPanel createControlledPane(JPanel panel, AnimatedComponent animated) {
        panel.setLayout(new BorderLayout());
        mComponents.add(animated);
        panel.add(animated, BorderLayout.CENTER);

        JPanel controls = new JPanel();
        LayoutManager manager = new BoxLayout(controls, BoxLayout.Y_AXIS);
        controls.setLayout(manager);
        controls.setPreferredSize(new Dimension(300, 800));
        panel.add(controls, BorderLayout.WEST);
        return controls;
    }

    static class DataNode extends DefaultMutableTreeNode implements ValuedTreeNode {

        private int mCount;
        private int mValue;

        public DataNode() {
            this(0, 0);
        }

        public DataNode(int count, int value) {
            mCount = count;
            mValue = value;
        }

        @Override
        public int getCount() {
            return mCount;
        }

        @Override
        public int getValue() {
            return mValue;
        }

        public void add(int count, int value) {
            mCount += count;
            mValue += value;
            if (parent instanceof DataNode) {
                ((DataNode)parent).add(count, value);
            }
        }

        public void addDataNode(DataNode dataNode) {
            super.add(dataNode);
            add(dataNode.getCount(), dataNode.getValue());
        }
    }

    private JPanel getPieChartExample() {

        final DataNode data = new DataNode();
        data.addDataNode(new DataNode(1, 10));

        final SunburstComponent layout = new SunburstComponent(data);

        JPanel panel = new JPanel();
        JPanel controls = createControlledPane(panel, layout);
        final JLabel info = new JLabel("<No information yet>");
        panel.add(info, BorderLayout.SOUTH);

        controls.add(createVaribleSlider("Gap", 0, 200, new Value() {
            @Override
            public void set(int v) {
                layout.setGap(v);
            }

            @Override
            public int get() {
                return (int) layout.getGap();
            }
        }));
        controls.add(createVaribleSlider("Size", 0, 200, new Value() {
            @Override
            public void set(int v) {
                layout.setSliceWidth(v);
            }

            @Override
            public int get() {
                return (int) layout.getSliceWidth();
            }
        }));
        controls.add(createVaribleSlider("Angle", 0, 360, new Value() {
            @Override
            public void set(int v) {
                layout.setAngle(v);
            }

            @Override
            public int get() {
                return (int) layout.getAngle();
            }
        }));
        controls.add(createVaribleSlider("Start", 0, 360, new Value() {
            @Override
            public void set(int v) {
                layout.setStart(v);
            }

            @Override
            public int get() {
                return (int) layout.getStart();
            }
        }));
        controls.add(createVaribleSlider("Fixed", 1, 100, new Value() {
            @Override
            public void set(int v) {
                layout.setFixed(v);
            }

            @Override
            public int get() {
                return (int) layout.getFixed();
            }
        }));
        controls.add(createVaribleSlider("Separator", 0, 100, new Value() {
            @Override
            public void set(int v) {
                layout.setSeparator(v);
            }

            @Override
            public int get() {
                return (int) layout.getSeparator();
            }
        }));
        controls.add(createButton("Generate", new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                generateLayoutData((DataNode) layout.getData(), 5);
            }
        }));
        controls.add(createButton("Tree A", new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                DataNode g = new DataNode();
                g.addDataNode(createTree(1));
                g.addDataNode(createValue());
                g.addDataNode(createTree(1));
                g.addDataNode(createValue());
                g.addDataNode(createTree(0));
                layout.setData(g);
            }
        }));
        controls.add(createButton("Tree B", new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                DataNode g = new DataNode();
                g.addDataNode(createValue());
                g.addDataNode(createValue());
                g.addDataNode(createTree(0));
                layout.setData(g);
            }
        }));
        controls.add(createButton("Value", new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                DataNode g = new DataNode();
                g.addDataNode(new DataNode(1, (int) (Math.random() * 50)));
                layout.setData(g);
            }
        }));
        controls.add(createCheckbox("Auto size", new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent itemEvent) {
                layout.setAutoSize(itemEvent.getStateChange() == ItemEvent.SELECTED);
            }
        }));
        controls.add(
                new Box.Filler(new Dimension(0, 0), new Dimension(300, Integer.MAX_VALUE),
                        new Dimension(300, Integer.MAX_VALUE)));

        layout.addSelectionListener(new SunburstComponent.SliceSelectionListener() {
            @Override
            public void valueChanged(SunburstComponent.SliceSelectionEvent e) {
                ValuedTreeNode node = e.getNode();
                info.setText(node == null ? "<No selection>" : String.format("Value %d Count %d",
                        node.getValue(), node.getCount()));
            }
        });
        return panel;
    }

    private static DataNode createValue() {
        return new DataNode(1, (int)(Math.random() * 50));
    }

    private static DataNode createTree(int depth) {
        DataNode b = depth == 0 ? createValue() : createTree(depth - 1);
        DataNode c = depth == 0 ? createValue() : createTree(depth - 1);
        DataNode a = new DataNode();
        a.addDataNode(b);
        a.addDataNode(c);
        return a;
    }

    private static Component createButton(String label, ActionListener action) {
        JButton button = new JButton(label);
        button.addActionListener(action);
        button.setMaximumSize(new Dimension(Integer.MAX_VALUE, button.getMaximumSize().height));
        return button;
    }

    private static Component createCheckbox(String label, ItemListener action) {
        JCheckBox button = new JCheckBox(label);
        button.addItemListener(action);
        button.setMaximumSize(new Dimension(Integer.MAX_VALUE, button.getMaximumSize().height));
        return button;
    }

    private static void generateLayoutData(DataNode data, int maxDepth) {
        Random random = new Random();
        int branch = random.nextInt(9) + 1;
        for (int i = 0; i < branch; i++) {
            int value = random.nextInt(1024);
            if (maxDepth > 0 && random.nextInt(4) == 0) {
                DataNode group = new DataNode();
                group.add(new DataNode(1, value));
                generateLayoutData(group, maxDepth - 1);
                data.addDataNode(group);
            } else {
                data.addDataNode(new DataNode(1, value));
            }
        }
    }

    private JPanel getTimelineExample() {
        final TimelineData data = new TimelineData(2, 2000);
        final EventData events = new EventData();
        final int streams = 2;
        final AtomicInteger variance = new AtomicInteger(10);
        final AtomicInteger delay = new AtomicInteger(100);
        final AtomicInteger type = new AtomicInteger(0);
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    float[] values = new float[streams];
                    while (true) {
                        int v = variance.get();
                        for (int i = 0; i < streams; i++) {
                            float delta = (float) Math.random() * variance.get() - v * 0.5f;
                            values[i] = Math.max(0, delta + values[i]);
                        }
                        synchronized (data) {
                            data.add(System.currentTimeMillis(), type.get() + (v == 0 ? 1 : 0), Arrays.copyOf(values,
                                    streams));
                        }
                        Thread.sleep(delay.get());
                    }
                } catch (InterruptedException e) {
                }
            }
        }.start();
        final TimelineComponent timeline = new TimelineComponent(data, events, 1.0f, 10.0f, 1000.0f,
                10.0f);
        timeline.configureStream(0, "Data 0", new Color(0x78abd9));
        timeline.configureStream(1, "Data 1", new Color(0xbaccdc));

        timeline.configureUnits("@");
        timeline.configureEvent(1, 0, UIManager.getIcon("Tree.leafIcon"),
                new Color(0x92ADC6),
                new Color(0x2B4E8C), false);
        timeline.configureEvent(2, 1, UIManager.getIcon("Tree.leafIcon"),
                new Color(255, 191, 176),
                new Color(76, 14, 29), true);
        timeline.configureType(1, TimelineComponent.Style.SOLID);
        timeline.configureType(2, TimelineComponent.Style.DASHED);

        final JPanel panel = new JPanel();
        final JPanel controls = createControlledPane(panel, timeline);
        controls.add(createVaribleSlider("Delay", 10, 5000, new Value() {
            @Override
            public void set(int v) {
                delay.set(v);
            }

            @Override
            public int get() {
                return delay.get();
            }
        }));
        controls.add(createVaribleSlider("Variance", 0, 50, new Value() {
            @Override
            public void set(int v) {
                variance.set(v);
            }

            @Override
            public int get() {
                return variance.get();
            }
        }));
        controls.add(createVaribleSlider("Type", 0, 2, new Value() {
            @Override
            public void set(int v) {
                type.set(v);
            }

            @Override
            public int get() {
                return type.get();
            }
        }));
        controls.add(createEventButton(1, events, variance));
        controls.add(createEventButton(1, events, null));
        controls.add(createEventButton(2, events, variance));

        controls.add(new Box.Filler(new Dimension(0, 0), new Dimension(300, Integer.MAX_VALUE), new Dimension(300, Integer.MAX_VALUE)));
        panel.add(timeline, BorderLayout.CENTER);
        return panel;
    }

    private Component createEventButton(final int type, final EventData events,
            final AtomicInteger variance) {
        final String start = "Start " + (variance != null ? "blocking " : "") + "event type " + type;
        final String stop = "Stop event type " + type;
        return createButton(start, new ActionListener() {
            EventData.Event event = null;
            int var = 0;

            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                JButton button = (JButton) actionEvent.getSource();
                if (event != null) {
                    event.stop(System.currentTimeMillis());
                    event = null;
                    if (variance != null) {
                        variance.set(var);
                    }
                    button.setText(start);
                } else {
                    event = events.start(System.currentTimeMillis(), type);
                    if (variance != null) {
                        var = variance.get();
                        variance.set(0);
                    }
                    button.setText(stop);
                }
            }
        });
    }

    public static void main(String[] args) {
        AnimatedComponentVisualTests dialog = new AnimatedComponentVisualTests();

        dialog.pack();
        dialog.setVisible(true);
        System.exit(0);
    }
}
