/*
 * For work developed by the HSQL Development Group:
 *
 * Copyright (c) 2001-2011, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *
 * For work originally developed by the Hypersonic SQL Group:
 *
 * Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 */


package org.hsqldb.util;

import java.util.Vector;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Panel;
import java.awt.Scrollbar;
import java.awt.SystemColor;
import java.awt.Toolkit;

/**
 *
 * @author Thomas Mueller (Hypersonic SQL Group)
 * @version 1.7.0
 * @since Hypersonic SQL
 */
class Tree extends Panel {

    // static
    private static Font        fFont;
    private static FontMetrics fMetrics;
    private static int         iRowHeight;
    private static int         iIndentWidth;
    private int                iMaxTextLength;

    // drawing
    private Dimension dMinimum;
    private Graphics  gImage;
    private Image     iImage;

    // height / width
    private int iWidth, iHeight;
    private int iFirstRow;
    private int iTreeWidth, iTreeHeight;
    private int iX, iY;

    // data
    private Vector vData;
    private int    iRowCount;

    // scrolling
    private Scrollbar sbHoriz, sbVert;
    private int       iSbWidth, iSbHeight;

    static {
        fFont        = new Font("Dialog", Font.PLAIN, 12);
        fMetrics     = Toolkit.getDefaultToolkit().getFontMetrics(fFont);
        iRowHeight   = getMaxHeight(fMetrics);
        iIndentWidth = 12;
    }

    /**
     * Constructor declaration
     *
     */
    Tree() {

        super();

        vData = new Vector();

        setLayout(null);

        sbHoriz = new Scrollbar(Scrollbar.HORIZONTAL);

        add(sbHoriz);

        sbVert = new Scrollbar(Scrollbar.VERTICAL);

        add(sbVert);
    }

    /**
     * Method declaration
     *
     *
     * @param d
     */
    public void setMinimumSize(Dimension d) {
        dMinimum = d;
    }

    /**
     * Method declaration
     *
     *
     * @param x
     * @param y
     * @param w
     * @param h
     */

// fredt@users 20011210 - patch 450412 by elise@users
// with additional replacement of deprecated methods
    public void setBounds(int x, int y, int w, int h) {

        super.setBounds(x, y, w, h);

        iSbHeight = sbHoriz.getPreferredSize().height;
        iSbWidth  = sbVert.getPreferredSize().width;
        iHeight   = h - iSbHeight;
        iWidth    = w - iSbWidth;

        sbHoriz.setBounds(0, iHeight, iWidth, iSbHeight);
        sbVert.setBounds(iWidth, 0, iSbWidth, iHeight);
        adjustScroll();

        iImage = null;

        repaint();
    }

    /**
     * Method declaration
     *
     */
    public void removeAll() {

        vData     = new Vector();
        iRowCount = 0;

        adjustScroll();

        iMaxTextLength = 10;

        repaint();
    }

    /**
     * Method declaration
     *
     *
     * @param key
     * @param value
     * @param state
     * @param color
     */
    public void addRow(String key, String value, String state, int color) {

        String[] row = new String[4];

        if (value == null) {
            value = "";
        }

        row[0] = key;
        row[1] = value;
        row[2] = state;    // null / "-" / "+"
        row[3] = String.valueOf(color);

        vData.addElement(row);

        int len = fMetrics.stringWidth(value);

        if (len > iMaxTextLength) {
            iMaxTextLength = len;
        }

        iRowCount++;
    }

    /**
     * Method declaration
     *
     *
     * @param key
     * @param value
     */
    public void addRow(String key, String value) {
        addRow(key, value, null, 0);
    }

    /**
     * Method declaration
     *
     */
    public void update() {
        adjustScroll();
        repaint();
    }

    /**
     * Method declaration
     *
     */
    void adjustScroll() {

        iTreeHeight = iRowHeight * (iRowCount + 1);

        // correct would be iMaxTextLength + iMaxIndent*iIndentWidth
        iTreeWidth = iMaxTextLength * 2;

        sbHoriz.setValues(iX, iWidth, 0, iTreeWidth);

        int v = iY / iRowHeight,
            h = iHeight / iRowHeight;

        sbVert.setValues(v, h, 0, iRowCount + 1);

        iX = sbHoriz.getValue();
        iY = iRowHeight * sbVert.getValue();
    }

    /**
     * Method declaration
     *
     *
     * @param e
     */

// fredt@users 20020130 - comment by fredt
// to remove this deprecated method we need to rewrite the Tree class as a
// ScrollPane component
    public boolean handleEvent(Event e) {

        switch (e.id) {

            case Event.SCROLL_LINE_UP :
            case Event.SCROLL_LINE_DOWN :
            case Event.SCROLL_PAGE_UP :
            case Event.SCROLL_PAGE_DOWN :
            case Event.SCROLL_ABSOLUTE :
                iX = sbHoriz.getValue();
                iY = iRowHeight * sbVert.getValue();

                repaint();

                return true;
        }

        return super.handleEvent(e);
    }

    /**
     * Method declaration
     *
     *
     * @param g
     */
    public void paint(Graphics g) {

        if (g == null || iWidth <= 0 || iHeight <= 0) {
            return;
        }

        g.setColor(SystemColor.control);
        g.fillRect(iWidth, iHeight, iSbWidth, iSbHeight);

        if (iImage == null) {
            iImage = createImage(iWidth, iHeight);
            gImage = iImage.getGraphics();

            gImage.setFont(fFont);
        }

        gImage.setColor(Color.white);
        gImage.fillRect(0, 0, iWidth, iHeight);

        int[]    lasty = new int[100];
        String[] root  = new String[100];

        root[0] = "";

        int currentindent = 0;
        int y             = iRowHeight;

        y -= iY;

        boolean closed = false;

        for (int i = 0; i < iRowCount; i++) {
            String[] s      = (String[]) vData.elementAt(i);
            String   key    = s[0];
            String   data   = s[1];
            String   folder = s[2];
            int      ci     = currentindent;

            for (; ci > 0; ci--) {
                if (key.startsWith(root[ci])) {
                    break;
                }
            }

            if (root[ci].length() < key.length()) {
                ci++;
            }

            if (closed && ci > currentindent) {
                continue;
            }

            closed   = folder != null && folder.equals("+");
            root[ci] = key;

            int x = iIndentWidth * ci - iX;

            gImage.setColor(Color.lightGray);
            gImage.drawLine(x, y, x + iIndentWidth, y);
            gImage.drawLine(x, y, x, lasty[ci]);

            lasty[ci + 1] = y;

            int py = y + iRowHeight / 3;
            int px = x + iIndentWidth * 2;

            if (folder != null) {
                lasty[ci + 1] += 4;

                int rgb = Integer.parseInt(s[3]);

                gImage.setColor(rgb == 0 ? Color.white
                                         : new Color(rgb));
                gImage.fillRect(x + iIndentWidth - 3, y - 3, 7, 7);
                gImage.setColor(Color.black);
                gImage.drawRect(x + iIndentWidth - 4, y - 4, 8, 8);
                gImage.drawLine(x + iIndentWidth - 2, y,
                                x + iIndentWidth + 2, y);

                if (folder.equals("+")) {
                    gImage.drawLine(x + iIndentWidth, y - 2,
                                    x + iIndentWidth, y + 2);
                }
            } else {
                px -= iIndentWidth;
            }

            gImage.setColor(Color.black);
            gImage.drawString(data, px, py);

            currentindent = ci;
            y             += iRowHeight;
        }

        g.drawImage(iImage, 0, 0, this);
    }

    /**
     * Method declaration
     *
     *
     * @param g
     */
    public void update(Graphics g) {
        paint(g);
    }

    /**
     * Method declaration
     */
    public Dimension preferredSize() {
        return dMinimum;
    }

    /**
     * Method declaration
     */
    public Dimension getPreferredSize() {
        return dMinimum;
    }

    /**
     * Method declaration
     */
    public Dimension getMinimumSize() {
        return dMinimum;
    }

    /**
     * Method declaration
     */
    public Dimension minimumSize() {
        return dMinimum;
    }

    /**
     * Method declaration
     *
     *
     * @param e
     * @param x
     * @param y
     */
    public boolean mouseDown(Event e, int x, int y) {

        if (iRowHeight == 0 || x > iWidth || y > iHeight) {
            return true;
        }

        y += iRowHeight / 2;

        String[] root = new String[100];

        root[0] = "";

        int     currentindent = 0;
        int     cy            = iRowHeight;
        boolean closed        = false;
        int     i             = 0;

        y += iY;

        for (; i < iRowCount; i++) {
            String[] s      = (String[]) vData.elementAt(i);
            String   key    = s[0];
            String   folder = s[2];
            int      ci     = currentindent;

            for (; ci > 0; ci--) {
                if (key.startsWith(root[ci])) {
                    break;
                }
            }

            if (root[ci].length() < key.length()) {
                ci++;
            }

            if (closed && ci > currentindent) {
                continue;
            }

            if (cy <= y && cy + iRowHeight > y) {
                break;
            }

            root[ci]      = key;
            closed        = folder != null && folder.equals("+");
            currentindent = ci;
            cy            += iRowHeight;
        }

        if (i >= 0 && i < iRowCount) {
            String[] s      = (String[]) vData.elementAt(i);
            String   folder = s[2];

            if (folder != null && folder.equals("+")) {
                folder = "-";
            } else if (folder != null && folder.equals("-")) {
                folder = "+";
            }

            s[2] = folder;

            vData.setElementAt(s, i);
            repaint();
        }

        return true;
    }

    /**
     * Method declaration
     *
     *
     * @param f
     */
    private static int getMaxHeight(FontMetrics f) {
        return f.getHeight() + 2;
    }
}
