/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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.
 *
 */
package com.queplix.core.client.common.ui;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MouseListenerAdapter;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.app.Application;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.ui.resizable.Resizable;
import com.queplix.core.client.common.ui.resizable.ResizableEnforcer;
import com.queplix.core.client.common.ui.resizable.ResizableScrollPanel;

/**
 * Splitted panel.
 *
 * @author Sultan Tezadov
 * @since 21 Oct 2006
 */
public class SplittedPanel extends Composite implements Resizable {
    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;

    private static final String CSS_CLASS = "Splitter_handle_";

    private AbsolutePanel panel;
    private Label handle;
    private float firstSizeFactor;
    private int panelOrientation;
    ResizableEnforcer firstWidget;
    ResizableEnforcer secondWidget;
    private boolean splitterHasBeenDragged;
    private boolean splitterCollapsed;

    private static final int MIN_VERTICAL_OFFSET =
            Application.isInternetExplorer() ? 2 : 0; // in IE need to limit by at least 2 pixels
    // otherwise there is a visual bug

    private static final int UNSET = -1;
    private int[] minHeights = {UNSET, UNSET};

    public SplittedPanel(Resizable firstWidget,
                         Resizable secondWidget,
                         int splitterOffsetPercent,
                         int panelOrientation) {
        if(!(firstWidget instanceof Widget)) {
            throw new IllegalArgumentException(
                    "Illegal argument: firstWidget. Must be instance of Widget or a subclass of Widget.");
        }
        if(!(secondWidget instanceof Widget)) {
            throw new IllegalArgumentException(
                    "Illegal argument: secondWidget. Must be instance of Widget or a subclass of Widget.");
        }
        if((splitterOffsetPercent < 1) || (splitterOffsetPercent > 99)) {
            throw new IllegalArgumentException(
                    "Illegal argument: splitterOffsetPercent. Must be percent value in the range 1..99.");
        }
        if((panelOrientation != HORIZONTAL) && (panelOrientation != VERTICAL)) {
            throw new IllegalArgumentException(
                    "Illegal argument: panelOrientation. Valid values: SplittedPanel.HORIZONTAL, SplittedPanel.VERTICAL.");
        }
        this.firstSizeFactor = splitterOffsetPercent / 100.0f;
        this.panel = new AbsolutePanel();
        this.panelOrientation = panelOrientation;
        this.firstWidget = new ResizableEnforcer(firstWidget);
        this.secondWidget = new ResizableEnforcer(secondWidget);
        this.splitterHasBeenDragged = false;

        initHandle(panelOrientation);

        panel.add(this.firstWidget);
        panel.add(handle);
        panel.add(this.secondWidget);
        initWidget(panel);
    }

    // =================== Resizable implementation ================
    private int offsetHeight;
    private int offsetWidth;

    public int getOffsetWidth() {
        return (offsetWidth > 0) ? offsetWidth:super.getOffsetWidth();
    }

    public int getOffsetHeight() {
        return (offsetHeight > 0) ? offsetHeight:super.getOffsetHeight();
    }

    public void setOffsetHeight(int height) {
        if(height < 0) {
            return; // invalid argument
        }
        if(panelOrientation == HORIZONTAL) {
            String heightSize = StringUtil.pixelToSize(height);
            handle.setHeight(heightSize);
            firstWidget.setOffsetHeight(height);
            secondWidget.setOffsetHeight(height);
            panel.setHeight(heightSize);
        } else if(panelOrientation == VERTICAL) {
            offsetHeight = height;
            if(isAttached()) {
                layoutChildren();
            }
        }
    }

    // public int getOffsetHeight(); -- already implemented in Widget

    public void setOffsetWidth(int width) {
        if(width < 0) {
            return; // invalid argument
        }
        if(panelOrientation == HORIZONTAL) {
            offsetWidth = width;
            if(isAttached()) {
                layoutChildren();
            }
        } else if(panelOrientation == VERTICAL) {
            String widthSize = StringUtil.pixelToSize(width);
            handle.setWidth(widthSize);
            firstWidget.setOffsetWidth(width);
            secondWidget.setOffsetWidth(width);
            panel.setWidth(widthSize);
        }
    }

    // public int getOffsetWidth(); -- already implemented in Widget

    protected void onAttach() {
        super.onAttach();
        layoutChildren();
    }

    public void setHeight(String height) {
        offsetHeight = -1; // unset offsetHeight
        super.setHeight(height);
    }

    public void setWidth(String width) {
        offsetWidth = -1; // unset offsetWidth
        super.setWidth(width);
    }

    public void setSize(String width, String height) {
        offsetHeight = -1; // unset offsetHeight
        offsetWidth = -1; // unset offsetWidth
        super.setSize(width, height);
    }

    public void setPixelSize(int width, int height) {
        offsetHeight = -1; // unset offsetHeight
        offsetWidth = -1; // unset offsetWidth
        super.setPixelSize(width, height);
    }
    // =================== End of Resizable implementation ==================

    // =================== Splitter handle implementation ===================

    private boolean isMouseDown = false;

    private int beginX = 0;
    private int beginY = 0;

    private void initHandle(int panelOrientation) {
        handle = new Label(" ");
        switch(panelOrientation) {
            case HORIZONTAL:
                handle.setStyleName(CSS_CLASS + "h");
                break;
            case VERTICAL:
                handle.setStyleName(CSS_CLASS + "v");
                break;
            default:
                throw new IllegalArgumentException(
                        "Illegal argument: panelOrientation. Valid values: SplittedPanel.HORIZONTAL, SplittedPanel.VERTICAL.");
        }

        handle.addMouseListener(new MouseListenerAdapter() {
            public void onMouseDown(Widget sender, int x, int y) {
                isMouseDown = true;
                DOM.setCapture(sender.getElement());

                beginX = x;
                beginY = y;
            }

            public void onMouseMove(Widget sender, int x, int y) {
                if(isMouseDown) {
                    int offsetX = x - beginX;
                    int offsetY = y - beginY;
                    moveSplitter(offsetX, offsetY);
                }
            }

            public void onMouseUp(Widget sender, int x, int y) {
                splitterHasBeenDragged = true;
                DOM.releaseCapture(sender.getElement());
                isMouseDown = false;
                layoutChildren();
            }
        });
    }

    private void moveSplitter(int offsetX, int offsetY) {
        int splitterOffset = 0;
        if(panelOrientation == HORIZONTAL) {
            splitterOffset = panel.getWidgetLeft(handle) + offsetX;
        } else if(panelOrientation == VERTICAL) {
            splitterOffset = panel.getWidgetTop(handle) + offsetY;
        }
        setSplitterOffset(splitterOffset);
    }

    private void layoutChildren() {
        layoutChildren(getSplitterOffset());
    }

    private void layoutChildren(int splitterOffset) {
        int offsetHeight = getOffsetHeight();
        int offsetWidth = getOffsetWidth();
        if((offsetHeight > 0) && (offsetWidth > 0)) { // both are set
            if(panelOrientation == HORIZONTAL) {
                layoutChildrenHorizontal(splitterOffset);
            } else if(panelOrientation == VERTICAL) {
                layoutChildrenVertical(splitterOffset);
            }
            super.setPixelSize(offsetWidth, offsetHeight);
        }
    }

    private void setSplitterOffset(int splitterOffset) {
        int newX = (panelOrientation == HORIZONTAL) ? splitterOffset:0;
        int newY = (panelOrientation == VERTICAL) ? splitterOffset:0;
        panel.setWidgetPosition(handle, newX, newY);
    }

    private int getSplitterOffset() {
        int splitterOffset;
        if(splitterCollapsed) {
            splitterOffset = 0;
        } else if(splitterHasBeenDragged) {
            splitterOffset = getActualSplitterOffset();
        } else {
            splitterOffset = getDefaultSplitterOffset();
        }
        return splitterOffset;
    }

    private int getDefaultSplitterOffset() {
        int offset = (panelOrientation == HORIZONTAL) ?
                getOffsetWidth():
                getOffsetHeight();
        int defaultOffset = (int) (offset * firstSizeFactor);
        return defaultOffset;
    }

    private int getActualSplitterOffset() {
        int offset = (panelOrientation == HORIZONTAL) ?
                panel.getWidgetLeft(handle):
                panel.getWidgetTop(handle);
        return offset;
    }

    private void layoutChildrenHorizontal(int splitterOffset) {
        int offsetHeight = getOffsetHeight();
        int offsetWidth = getOffsetWidth();
        splitterOffset = (splitterOffset > 0) ? splitterOffset:0;
        int offsetLimit = offsetWidth - handle.getOffsetWidth();
        splitterOffset = (splitterOffset < offsetLimit) ? splitterOffset
                :offsetLimit;
        panel.setWidgetPosition(firstWidget, 0, 0);
        firstWidget.setOffsetHeight(offsetHeight);
        firstWidget.setOffsetWidth(splitterOffset - 1);
        panel.setWidgetPosition(handle, splitterOffset, 0);
        int secondLeft = splitterOffset + handle.getOffsetWidth();
        panel.setWidgetPosition(secondWidget, secondLeft, 0);
        secondWidget.setOffsetHeight(offsetHeight);
        secondWidget.setOffsetWidth(offsetWidth - secondLeft);
    }

    private void layoutChildrenVertical(int splitterOffset) {
        int offsetHeight = getOffsetHeight();
        int offsetWidth = getOffsetWidth();
        if(minHeights[0] != UNSET) {
            splitterOffset = (splitterOffset > minHeights[0]) ? splitterOffset : minHeights[0];
        }
        if(minHeights[1] != UNSET){
            if (offsetHeight - splitterOffset < minHeights[1]) {
                splitterOffset = offsetHeight - minHeights[1];
            }
        }
        splitterOffset = (splitterOffset > MIN_VERTICAL_OFFSET) ? splitterOffset : MIN_VERTICAL_OFFSET;
        int offsetLimit = offsetHeight - handle.getOffsetHeight();
        splitterOffset = (splitterOffset < offsetLimit) ? splitterOffset
                :offsetLimit;
        panel.setWidgetPosition(firstWidget, 0, 0);
        firstWidget.setOffsetHeight(splitterOffset - 1);
        firstWidget.setOffsetWidth(offsetWidth);
        panel.setWidgetPosition(handle, 0, splitterOffset);
        int secondTop = splitterOffset + handle.getOffsetHeight();
        panel.setWidgetPosition(secondWidget, 0, secondTop);
        secondWidget.setOffsetHeight(offsetHeight - secondTop);
        secondWidget.setOffsetWidth(offsetWidth);
    }
    // =================== End of Splitter handle implementation ============

    private void setSplitterPosition(int position) {
        setSplitterOffset(position);
        layoutChildren(position);
    }

    public boolean isSplitterCollapsed() {
        return splitterCollapsed;
    }

    public void setSplitterCollapsed(boolean splitterCollapsed) {
        this.splitterCollapsed = splitterCollapsed;
        int offset = splitterCollapsed ? 0:getDefaultSplitterOffset();
        setSplitterPosition(offset);
    }

    public void setMinHeight(Resizable widget, int minHeight) {
        if(panelOrientation != VERTICAL){
            throw new UnsupportedOperationException("Minimal height can be set "+
                                "only if SplittedPanel has vertical orientation");
        } else {
            if(widget == firstWidget.getWidget()) {
                minHeights[0] = minHeight;
            } else if(widget == secondWidget.getWidget()){
                minHeights[1] = minHeight;
            } else {
                throw new IllegalArgumentException("The widget is not present in the SplittedPanel");
            }
        }
    }
}
