package edu.sc.seis.gee;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.Rectangle2D;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import org.apache.log4j.Logger;
import edu.sc.seis.fissuresUtil.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.gee.configurator.ConfigurationException;
import edu.sc.seis.gee.configurator.Configure;
import edu.sc.seis.gee.configurator.DefaultParamNames;
import edu.sc.seis.gee.task.Task;

/**
 * FrameManager.java
 * 
 * 
 * Created: Tue Apr 8 14:37:17 2003
 * 
 * @author <a href="mailto:crotwell@owl.seis.sc.edu">Philip Crotwell </a>
 * @version 1.0
 */
public class FrameManager {

    private FrameManager() {}

    public static final FrameManager getManager() {
        return frameManager;
    }

    public JFrame createNewMother() {
        String newName = "GEE";
        int windowCount = 1;
        while(true) {
            boolean foundName = false;
            Iterator it = motherFrames.iterator();
            while(it.hasNext() && !foundName) {
                JFrame current = (JFrame)it.next();
                if(current.getName().equals(newName)) {
                    foundName = true;
                }
            }
            if(!foundName) {
                return createNewMother(newName);
            }
            newName = "GEE " + windowCount++;
        }
    }

    public JFrame createNewMother(String name) {
        return createNewMother(name, new JPanel());
    }

    public JFrame createNewMother(String name, JPanel panel) {
        return createNewMother(name, panel, new Dimension(600, 500));
    }

    public JFrame createNewMother(String name, JPanel panel, Dimension dim) {
        return createNewMother(name,
                               panel,
                               dim,
                               getNextMotherLoc(dim,
                                                panel.getGraphicsConfiguration()));
    }

    public JFrame createNewMother(String name,
                                  JPanel panel,
                                  Dimension dim,
                                  Point loc) {
        JFrame nf = createNewFrame(name, dim, loc, panel);
        motherFrames.add(nf);
        CommonAccess commonAccess = CommonAccess.getCommonAccess();
        if(commonAccess == null) {
            throw new RuntimeException("CommonAccess is NULL");
        }
        setMenuBar(nf);
        return nf;
    }

    public JFrame createNewChild(String name, JFrame motherFrame) {
        return createNewChild(name, motherFrame, new Dimension(400, 400));
    }

    public JFrame createNewChild(String name, JFrame motherFrame, Dimension size) {
        return createNewChild(name, motherFrame, size, new JPanel());
    }

    public JFrame createNewChild(String name, JFrame motherFrame, JPanel panel) {
        return createNewChild(name, motherFrame, (Dimension)null, panel);
    }

    public JFrame createNewChild(String name,
                                 JFrame motherFrame,
                                 Dimension size,
                                 JPanel panel) {
        return createNewChild(name, motherFrame, size, panel, (Point)null);
    }

    public JFrame createNewChild(String name,
                                 JFrame motherFrame,
                                 JPanel panel,
                                 Point location) {
        return createNewChild(name,
                              motherFrame,
                              (Dimension)null,
                              panel,
                              location);
    }

    public JFrame createNewChild(String name,
                                 JFrame motherFrame,
                                 Dimension size,
                                 JPanel panel,
                                 Point location) {
        if(!motherFrames.contains(motherFrame)) {
            throw new IllegalArgumentException("Child frame found dressing up in mothers clothes.");
        }
        JFrame frame = createNewFrame(name, size, location, panel);
        childFrames.put(frame, motherFrame);
        return frame;
    }

    /**
     * removes all frames and creates a new main frame.
     */
    public JFrame reset() {
        JFrame[] frames = getAllFrames();
        Point p;
        Dimension size = new Dimension(800, 600);
        if(currentFrame != null && getCurrentMainFrame() != null) {
            p = getCurrentMainFrame().getLocation();
        } else {
            p = getNextMotherLoc(size, null);
        }
        switchCurrent = false;
        currentFrame = createNewMother("GEE", null, size, p);
        currentFrame.setVisible(true);
        for(int i = 0; i < frames.length; i++) {
            removeFrame(frames[i]);
        }
        switchCurrent = true;
        return currentFrame;
    }

    public JPanel createNewTab(JFrame frame, String name) {
        if(frame == null) {
            // frame = createNewMother("GEE");
            throw new IllegalArgumentException("frame for tab is null, name="
                    + name);
        } // end of if ()
        JPanel panel = new JPanel();
        panel.setName(name);
        JTabbedPane tab = null;
        if(frameToPanel.get(frame) == null) {
            // case of new frame with no panel inside
            frame.getContentPane().add(panel);
            frameToPanel.put(frame, panel);
        } else {
            if(frameToPanel.get(frame) instanceof JTabbedPane) {
                tab = (JTabbedPane)frameToPanel.get(frame);
            }
            if(tab == null) {
                // TODO figure out some way to save frame's contents if need be
                tab = new JTabbedPane();
                frameToPanel.put(frame, tab);
                frame.getContentPane().add(tab, BorderLayout.CENTER);
            }
            tab.add(panel, tab.getComponentCount());
        }
        frame.setVisible(true);
        return panel;
    }

    public JFrame getCurrentFrame() {
        return currentFrame;
    }

    public JFrame getCurrentMainFrame() {
        if(currentFrame == null) {
            throw new RuntimeException("Current frame is null");
        } else if(motherFrames.contains(currentFrame)) {
            return currentFrame;
        }
        return (JFrame)childFrames.get(currentFrame);
    }

    public JPanel getCurrentPanel() {
        JPanel panel = getPanelForFrame(currentFrame);
        if(panel == null) {
            panel = new JPanel();
            currentFrame.getContentPane().add(panel);
            frameToPanel.put(currentFrame, panel);
        }
        return panel;
    }

    public void setCurrentPanel(JPanel panel) {
        JFrame frame = getFrameForPanel(panel);
        if(frame != null) {
            if(!frame.isVisible()) {
                frame.setVisible(true);
            }
            if(frame.getState() == Frame.ICONIFIED) {
                frame.setState(JFrame.NORMAL);
            }
            JComponent framePanel = (JComponent)frameToPanel.get(frame);
            if(framePanel instanceof JTabbedPane) {
                ((JTabbedPane)framePanel).setSelectedComponent(panel);
            }
        }
    }

    /**
     * returns the panel from the current main frame.
     */
    public JPanel getCurrentMainPanel() {
        return getPanelForFrame(getCurrentMainFrame());
    }

    public JPanel getPanelForFrame(JFrame frame) {
        JComponent panel = (JComponent)frameToPanel.get(frame);
        if(panel instanceof JTabbedPane) {
            return (JPanel)((JTabbedPane)panel).getSelectedComponent();
        }
        return (JPanel)panel;
    }

    /**
     * Method getFrameForPanel returns the frame that contains the passed in
     * JPanel. The frame may have a JTabbedPane as its content pane and the
     * JPanel may be inside there, or the content pane of the frame may be the
     * panel.
     * 
     * @returns the frame that contains the passed in panel if it is contained
     *          in a known frame, else null is returned
     */
    public JFrame getFrameForPanel(JPanel panel) {
        Iterator it = frameToPanel.keySet().iterator();
        while(it.hasNext()) {
            JFrame curFrame = (JFrame)it.next();
            JComponent curComp = (JComponent)frameToPanel.get(curFrame);
            if(curComp instanceof JTabbedPane) {
                JTabbedPane tabPane = (JTabbedPane)curComp;
                for(int i = 0; i < tabPane.getComponentCount(); i++) {
                    if(tabPane.getComponentAt(i) == panel) {
                        return curFrame;
                    }
                }
            } else {
                if(curComp == panel) {
                    return curFrame;
                }
            }
        }
        return null;
    }

    /**
     * sets the size of a frame to the passed in dimension if it's larger than
     * the current size and positions the window in the center. Otherwise, it
     * leaves the frame alone
     */
    public void resize(JFrame frame, Dimension size) {
        Dimension frameSize = frame.getSize();
        int width = size.width;
        if(frameSize.getWidth() > width) {
            width = frameSize.width;
        }
        int height = size.height;
        if(frameSize.getHeight() > height) {
            height = frameSize.height;
        }
        setSize(frame, new Dimension(width, height));
    }

    /**
     * sets the size of the frame to be the passed in size, and positions it in
     * the center of the screen
     */
    public void setSize(JFrame frame, Dimension size) {
        frame.setSize(size);
    }

    /**
     * Method getAllFrames returns an array containing all existing frames
     */
    public JFrame[] getAllFrames() {
        List allFrames = new ArrayList(motherFrames);
        allFrames.addAll(childFrames.keySet());
        return (JFrame[])allFrames.toArray(new JFrame[allFrames.size()]);
    }

    /**
     * Method cleanPanel destroys any tasks that happen to live in this
     * component.
     */
    public void clearComponent(Component comp) {
        if(comp instanceof Task) {
            Task task = (Task)comp;
            CommonAccess.getCommonAccess().getTaskAction(task).reset(false);
        } else if(comp instanceof Container) {
            Container container = (Container)comp;
            Component[] components = container.getComponents();
            for(int i = 0; i < components.length; i++) {
                clearComponent(components[i]);
            }
        }
    }

    /**
     * If the panel is a tab in a tabbed pane this changes the tab to the
     * panel's name.
     */
    public void updateName(JPanel panel) {
        JFrame frame = getFrameForPanel(panel);
        if(frameToPanel.get(frame) instanceof JTabbedPane) {
            JTabbedPane tab = (JTabbedPane)frameToPanel.get(frame);
            for(int i = 0; i < tab.getComponentCount(); i++) {
                if(tab.getComponentAt(i) == panel) {
                    tab.setTitleAt(i, panel.getName());
                    return;
                }
            }
        }
    }

    public void removeFrame(JFrame frame) {
        if(motherFrames.contains(frame)) {
            Iterator it = childFrames.keySet().iterator();
            List toRemove = new ArrayList();
            while(it.hasNext()) {
                JFrame curChild = (JFrame)it.next();
                JFrame curFrame = (JFrame)childFrames.get(curChild);
                if(curFrame.equals(frame)) {
                    toRemove.add(curChild);
                }
            }
            it = toRemove.iterator();
            while(it.hasNext()) {
                removeFrame((JFrame)it.next());
            }
            motherFrames.remove(frame);
        } else if(childFrames.containsKey(frame)) {
            childFrames.remove(frame);
        }
        if(currentFrame != null && currentFrame == frame) {
            currentFrame = null;
        }
        Object obj = frameToPanel.get(frame);
        if(obj != null) {
            if(obj instanceof Component) {
                clearComponent((Component)obj);
            } // end of if ()
        } // end of if ()
        frameToPanel.remove(frame);
        frame.removeWindowListener(defWindowListener);
        frame.dispose();
        Iterator it = listeners.iterator();
        while(it.hasNext()) {
            ((FrameEventListener)it.next()).frameRemoved(frame);
        }
        if(motherFrames.size() == 0) {
            CommonAccess.getCommonAccess().appQuit();
        }
    }

    public void addFrameEventListener(FrameEventListener listener) {
        listeners.add(listener);
    }

    public void removeFrameEventListener(FrameEventListener listener) {
        listeners.remove(listener);
    }

    public static Point getLoc(String justification, Dimension objectSize) {
        int x = 0, y = 0;
        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension screenSize = tk.getScreenSize();
        if(justification.equals(DefaultParamNames.JUSTIFY_CENTER)) {
            x = (screenSize.width - objectSize.width) / 2;
            y = (screenSize.height - objectSize.height) / 2;
        } else if(justification.equals(DefaultParamNames.JUSTIFY_TOPLEFT)) {
            x = 0;
            y = 0;
        } else if(justification.equals(DefaultParamNames.JUSTIFY_TOPRIGHT)) {
            x = screenSize.width - objectSize.width;
            y = 0;
        } else if(justification.equals(DefaultParamNames.JUSTIFY_BOTTOMLEFT)) {
            x = 0;
            y = screenSize.height - objectSize.height - 40;
        } else if(justification.equals(DefaultParamNames.JUSTIFY_BOTTOMRIGHT)) {
            x = screenSize.width - objectSize.width;
            y = screenSize.height - objectSize.height - 40;
        }
        return new Point(x, y);
    }

    private Point getNextMotherLoc(Dimension motherSize,
                                   GraphicsConfiguration graphics) {
        return getNextMotherLoc(motherSize, graphics, null);
    }

    private Point getNextMotherLoc(Dimension motherSize,
                                   GraphicsConfiguration graphics,
                                   JFrame frame) {
        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension screenSize = tk.getScreenSize();
        Rectangle usableScreen = getUsableScreen(graphics);
        Iterator it = motherFrames.iterator();
        Point curPoint = new Point((int)(usableScreen.getCenterX() - motherSize.width / 2),
                                   (int)(usableScreen.getCenterY() - motherSize.height / 2));
        while((curPoint.x + motherSize.width) < screenSize.width
                && (curPoint.y + motherSize.height) < screenSize.height) {
            boolean occupied = false;
            while(it.hasNext() && !occupied) {
                JFrame curFrame = (JFrame)it.next();
                if(frame != curFrame && curFrame.getX() == curPoint.getX()
                        && curFrame.getY() == curPoint.getY()) {
                    occupied = true;
                }
            }
            if(!occupied) {
                return curPoint;
            }
            curPoint.x += 30;
            curPoint.y += 30;
        }
        return new Point(usableScreen.x, usableScreen.y);
    }

    private Rectangle getUsableScreen(GraphicsConfiguration graphics) {
        Toolkit tk = Toolkit.getDefaultToolkit();
        Insets insets;
        Dimension size = tk.getScreenSize();
        if(graphics != null) {
            insets = tk.getScreenInsets(graphics);
        } else {
            JFrame frame = new JFrame();
            insets = tk.getScreenInsets(frame.getGraphicsConfiguration());
            frame.dispose();
        }
        int width = size.width - insets.left - insets.right;
        int height = size.height - insets.top - insets.bottom;
        return new Rectangle(insets.left, insets.top, width, height);
    }

    private Point getNextChildLoc(Dimension childSize,
                                  GraphicsConfiguration graphics) {
        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension screenSize = tk.getScreenSize();
        Insets screenInsets = tk.getScreenInsets(graphics);
        int screenHeight = screenSize.height - screenInsets.top
                - screenInsets.bottom;
        int screenWidth = screenSize.width - screenInsets.left
                - screenInsets.right;
        int childHeight = childSize.height;
        int childWidth = childSize.width;
        Coverage coveredX = new Coverage();
        Coverage coveredY = new Coverage();
        Iterator it = childFrames.keySet().iterator();
        while(it.hasNext()) {
            JFrame current = (JFrame)it.next();
            Point curLoc = current.getLocation();
            Dimension curSize = current.getSize();
            if(curSize.height + curLoc.getY() > screenHeight - childHeight) {
                coveredX.add((int)curLoc.getX(), (int)curLoc.getX()
                        + curSize.width);
            }
            if(curSize.width + curLoc.getX() > screenWidth - childWidth) {
                coveredY.add((int)curLoc.getY(), (int)curLoc.getY()
                        + curSize.height);
            }
        }
        int[][] xCoverage = coveredX.getCoveredAreas();
        // No other child windows, put in bottom left
        if(xCoverage.length == 0) {
            return new Point(screenInsets.left, screenHeight - childHeight);
        }
        int curPos = 0;
        int[] xSpace = {0, 0};
        // xSpace[0] = largest gap on bottom row xSpace[1] = xPos of gap start
        for(int i = 0; i < xCoverage.length; i++) {
            int spaceAvailable = xCoverage[i][0] - curPos;
            // Space in between two existing child windows, add there
            if(spaceAvailable >= childWidth) {
                return new Point(curPos, screenHeight - childHeight);
            }
            if(spaceAvailable > xSpace[0]) {
                xSpace[0] = spaceAvailable;
                xSpace[1] = curPos;
            }
            curPos = xCoverage[i][1];
        }
        // Space at end of bottom row
        if(curPos + childWidth < screenWidth) {
            return new Point(curPos, screenHeight - childHeight);
        }
        // No space on bottom row, start coming down right column
        int[][] yCoverage = coveredY.getCoveredAreas();
        if(yCoverage.length == 0) {
            return new Point(screenWidth - childWidth, screenInsets.top);
        }
        curPos = 0;
        int[] ySpace = {0, 0};
        for(int i = 0; i < yCoverage.length; i++) {
            int spaceAvailable = yCoverage[i][0] - curPos;
            if(spaceAvailable >= childHeight) {
                return new Point(screenWidth - childWidth, curPos);
            }
            if(spaceAvailable > ySpace[0]) {
                ySpace[0] = spaceAvailable;
                ySpace[1] = curPos;
            }
            curPos = yCoverage[i][1];
        }
        // Largest gap in x axis, put child there
        if(xSpace[0] > ySpace[0]) {
            return new Point(xSpace[1], screenHeight - childHeight);
        } // Largest gap in y axis, put child there
        return new Point(screenWidth - childWidth, ySpace[1]);
    }

    private class Coverage {

        private void add(int begin, int end) {
            Rectangle2D newCoverage = new Rectangle2D.Float(begin, 0, end
                    - begin, 1);
            for(int i = 0; i < coveredAreas.size(); i++) {
                Rectangle2D current = (Rectangle2D)coveredAreas.get(i);
                if(newCoverage.intersects(current)
                        || Math.abs(newCoverage.getMinX() - current.getMaxX()) <= 1
                        || Math.abs(newCoverage.getMaxX() - current.getMinX()) <= 1) {
                    coveredAreas.remove(i);
                    Rectangle2D.union(newCoverage, current, newCoverage);
                    add((int)newCoverage.getMinX(), (int)newCoverage.getMaxX());
                    return;
                }
            }
            coveredAreas.add(newCoverage);
        }

        private int[][] getCoveredAreas() {
            List orderedCoveredAreas = new ArrayList();
            Iterator it = coveredAreas.iterator();
            for(int i = 0; i < coveredAreas.size(); i++) {
                Rectangle2D current = (Rectangle2D)it.next();
                int[] covAreas = {(int)current.getX(),
                                  (int)(current.getMaxX() - current.getMinX())};
                ListIterator orIt = orderedCoveredAreas.listIterator();
                while(orIt.hasNext()) {
                    int[] curOrIt = (int[])orIt.next();
                    if(current.getMinX() < curOrIt[0]) {
                        orIt.previous();
                        orIt.add(covAreas);
                        break;
                    }
                }
                orderedCoveredAreas.add(covAreas);
            }
            int[][] twoDArray = new int[orderedCoveredAreas.size()][2];
            for(int i = 0; i < orderedCoveredAreas.size(); i++) {
                twoDArray[i] = (int[])orderedCoveredAreas.get(i);
            }
            return twoDArray;
        }

        private List coveredAreas = new ArrayList();
    }

    private JFrame createNewFrame(String name,
                                  Dimension size,
                                  Point location,
                                  JPanel panel) {
        JFrame nf = new JFrame(name);
        nf.setName(name);
        String resource = "edu/sc/seis/gee/data/images/gee-icon.gif";
        URL url = getClass().getClassLoader().getResource(resource);
        nf.setIconImage(new ImageIcon(url).getImage());
        currentFrame = nf;
        nf.addWindowListener(defWindowListener);
        nf.getContentPane().setLayout(new BorderLayout());
        if(panel != null) {
            panel.setName(name);
            frameToPanel.put(nf, panel);
            nf.getContentPane().add(panel, BorderLayout.CENTER);
        }
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension dimension = toolkit.getScreenSize();
        // first check for the maximum height and width of the screen
        // coordinates.
        if(size == null) {
            nf.pack();
            size = nf.getSize();
        }
        if(size.width > dimension.width) {
            size.width = dimension.width;
        }
        if(size.height > dimension.height) {
            size.height = dimension.height;
        }
        nf.setSize(size);
        nf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        if(location == null) {
            location = getNextChildLoc(size, nf.getGraphicsConfiguration());
        }
        nf.setLocation(location);
        Iterator it = listeners.iterator();
        while(it.hasNext()) {
            ((FrameEventListener)it.next()).frameAdded(nf);
        }
        return nf;
    }

    public void setMenuBar(JFrame frame) {
        try {
            Configure configure = CommonAccess.getCommonAccess().getConfigure();
            JMenuBar menubar = configure.getMenuBar();
            if(menubar != null) {
                if(menubar.getComponentCount() > 0)
                    menubar.add(new WindowMenu());
                frame.setJMenuBar(menubar);
            } else {
                frame.setJMenuBar(new JMenuBar());
            }
        } catch(ConfigurationException e) {
            GlobalExceptionHandler.handle("Can't get the menubar due to a configuration problem.",
                                          e);
        }
    }

    private class WindowMenu extends JMenu implements FrameEventListener {

        public WindowMenu() {
            super("Windows");
            JFrame[] currentFrames = getAllFrames();
            for(int i = 0; i < currentFrames.length; i++) {
                frameAdded(currentFrames[i]);
            }
            addFrameEventListener(this);
        }

        public void destroy() {
            FrameManager manager = FrameManager.getManager();
            manager.removeFrameEventListener(this);
        }

        public void frameRemoved(JFrame jf) {
            if(frameMenuItem.get(jf) != null) {
                remove((FrameMenuItem)frameMenuItem.get(jf));
                frameMenuItem.remove(jf);
            }
        }

        public void frameAdded(JFrame jf) {
            frameMenuItem.put(jf, new FrameMenuItem(jf));
            add((FrameMenuItem)frameMenuItem.get(jf));
        }

        private class FrameMenuItem extends JMenuItem {

            private FrameMenuItem(JFrame frame) {
                super(frame.getName());
                jframe = frame;
                this.addActionListener(new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        if(jframe.getState() == JFrame.ICONIFIED) {
                            jframe.setState(JFrame.NORMAL);
                        }
                        jframe.toFront();
                    }
                });
            }

            private final JFrame jframe;
        }

        private Map frameMenuItem = new HashMap();
    }

    private List listeners = new ArrayList();

    private List motherFrames = new ArrayList();

    private Map childFrames = new HashMap();

    private JFrame currentFrame;

    private HashMap frameToPanel = new HashMap();

    private WindowListener defWindowListener = new MyWindowListener();

    private static FrameManager frameManager = new FrameManager();

    private static Logger logger = Logger.getLogger(FrameManager.class);

    private boolean switchCurrent;

    private class MyWindowListener extends WindowAdapter {

        public void windowClosed(WindowEvent e) {
            removeFrame((JFrame)e.getWindow());
        }

        public void windowActivated(WindowEvent e) {
            if(switchCurrent) {
                currentFrame = (JFrame)e.getWindow();
            }
        }
    }
} // FrameManager
