/*
 * Copyright ©1998-2020 by Richard A. Wilkes. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, version 2.0. If a copy of the MPL was not distributed with
 * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, version 2.0.
 */

package com.trollworks.gcs.ui.widget.dock;

import com.trollworks.gcs.menu.file.CloseHandler;
import com.trollworks.gcs.menu.file.Saveable;
import com.trollworks.gcs.ui.Colors;
import com.trollworks.gcs.ui.UIUtilities;
import com.trollworks.gcs.ui.border.EmptyBorder;
import com.trollworks.gcs.ui.image.Images;
import com.trollworks.gcs.ui.layout.PrecisionLayout;
import com.trollworks.gcs.ui.layout.PrecisionLayoutData;
import com.trollworks.gcs.ui.widget.DataModifiedListener;
import com.trollworks.gcs.ui.widget.IconButton;
import com.trollworks.gcs.utility.I18n;
import com.trollworks.gcs.utility.text.NumericComparator;
import com.trollworks.gcs.utility.text.Text;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Path2D;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

/** Provides a tab that contains the {@link Dockable}'s icon, title, and close button, if any. */
public class DockTab extends JPanel implements ContainerListener, MouseListener, DragGestureListener, DataModifiedListener, Comparable<DockTab> {
    private Dockable mDockable;
    private JLabel   mTitle;

    /**
     * Creates a new {@link DockTab} for the specified {@link Dockable}.
     *
     * @param dockable The {@link Dockable} to work with.
     */
    public DockTab(Dockable dockable) {
        super(new PrecisionLayout().setMargins(2, 4, 2, 4).setMiddleVerticalAlignment());
        mDockable = dockable;
        setOpaque(false);
        setBorder(new EmptyBorder(2, 1, 0, 1));
        addContainerListener(this);
        mTitle = new JLabel(getFullTitle(), dockable.getTitleIcon(), SwingConstants.LEFT);
        IconButton closeButton = new IconButton(Images.DOCK_CLOSE, I18n.Text("Close"), this::attemptClose);
        add(mTitle, new PrecisionLayoutData().setGrabHorizontalSpace(true).setHeightHint(Math.max(mTitle.getPreferredSize().height, closeButton.getPreferredSize().height)));
        if (dockable instanceof CloseHandler) {
            add(closeButton, new PrecisionLayoutData().setEndHorizontalAlignment());
        }
        if (dockable instanceof Saveable) {
            ((Saveable) dockable).addDataModifiedListener(this);
        }
        addMouseListener(this);
        setToolTipText(Text.wrapPlainTextForToolTip(dockable.getTitleTooltip()));
        DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this);
    }

    /**
     * @return {@code true} if this {@link DockTab} is the current one for the {@link
     *         DockContainer}.
     */
    public boolean isCurrent() {
        DockContainer dc = getDockContainer();
        return dc != null && dc.getCurrentDockable() == mDockable;
    }

    /** @return The {@link Dockable} this tab represents. */
    public Dockable getDockable() {
        return mDockable;
    }

    /** @return The icon used by the tab. */
    public Icon getIcon() {
        return mDockable.getTitleIcon();
    }

    /** @return The full title used by the tab. */
    public String getFullTitle() {
        StringBuilder buffer = new StringBuilder();
        if (mDockable instanceof Saveable) {
            if (((Saveable) mDockable).isModified()) {
                buffer.append('*');
            }
        }
        buffer.append(mDockable.getTitle());
        return buffer.toString();
    }

    /** Update the title and icon from the {@link Dockable}. */
    public void updateTitle() {
        mTitle.setText(getFullTitle());
        mTitle.setIcon(mDockable.getTitleIcon());
        mTitle.revalidate();
    }

    @Override
    protected void paintComponent(Graphics g) {
        Insets        insets = getInsets();
        Path2D.Double path   = new Path2D.Double();
        int           bottom = getHeight();
        path.moveTo(0, bottom);
        path.lineTo(0, 6);
        path.curveTo(0, 6, 0, 1, 6, 1);
        int width = getWidth();
        path.lineTo(width - 7, 1);
        path.curveTo(width - 7, 1, width - 1, 1, width - 1, 7);
        path.lineTo(width - 1, bottom);
        DockContainer dc   = getDockContainer();
        Color         base = DockColors.BACKGROUND;
        if (dc != null) {
            if (dc.getCurrentDockable() == mDockable) {
                base = dc.isActive() ? DockColors.ACTIVE_TAB_BACKGROUND : DockColors.CURRENT_TAB_BACKGROUND;
            }
        }
        Graphics2D gc = (Graphics2D) g;
        gc.setPaint(new GradientPaint(new Point(insets.left, insets.top), base, new Point(insets.left, getHeight() - (insets.top + insets.bottom)), Colors.adjustBrightness(base, -0.1f)));
        gc.fill(path);
        gc.setColor(DockColors.SHADOW);
        gc.draw(path);
    }

    @Override
    public PrecisionLayout getLayout() {
        return (PrecisionLayout) super.getLayout();
    }

    @Override
    public void setLayout(LayoutManager mgr) {
        if (mgr instanceof PrecisionLayout) {
            super.setLayout(mgr);
        } else {
            throw new IllegalArgumentException("Must use a PrecisionLayout.");
        }
    }

    @Override
    public void componentAdded(ContainerEvent event) {
        getLayout().setColumns(getComponentCount());
    }

    @Override
    public void componentRemoved(ContainerEvent event) {
        getLayout().setColumns(getComponentCount());
    }

    private DockContainer getDockContainer() {
        return UIUtilities.getAncestorOfType(this, DockContainer.class);
    }

    public void attemptClose() {
        DockContainer dc = getDockContainer();
        if (dc != null) {
            dc.attemptClose(mDockable);
        }
    }

    @Override
    public void dragGestureRecognized(DragGestureEvent dge) {
        DockableTransferable transferable = new DockableTransferable(mDockable);
        if (DragSource.isDragImageSupported()) {
            Point offset = new Point(dge.getDragOrigin());
            offset.x = -offset.x;
            offset.y = -offset.y;
            dge.startDrag(null, DragSource.isDragImageSupported() ? UIUtilities.getImage(this) : null, offset, transferable, null);
        } else {
            dge.startDrag(null, transferable);
        }
    }

    @Override
    public void mouseEntered(MouseEvent event) {
        // Unused
    }

    @Override
    public void mousePressed(MouseEvent event) {
        DockContainer dc = getDockContainer();
        if (dc.getCurrentDockable() != mDockable) {
            dc.setCurrentDockable(mDockable);
            dc.acquireFocus();
        } else if (!dc.isActive()) {
            dc.acquireFocus();
        }
    }

    @Override
    public void mouseReleased(MouseEvent event) {
        // Unused
    }

    @Override
    public void mouseClicked(MouseEvent event) {
        // Unused
    }

    @Override
    public void mouseExited(MouseEvent event) {
        // Unused
    }

    @Override
    public void dataModificationStateChanged(Object obj, boolean modified) {
        String title = getFullTitle();
        if (!title.equals(mTitle.getText())) {
            mTitle.setText(title);
            mTitle.revalidate();
        }
    }

    @Override
    public int compareTo(DockTab other) {
        int result = NumericComparator.caselessCompareStrings(mDockable.getTitle(), other.mDockable.getTitle());
        if (result == 0) {
            int h1 = hashCode();
            int h2 = other.hashCode();
            if (h1 < h2) {
                result = -1;
            } else if (h1 > h2) {
                result = 1;
            }
        }
        return result;
    }
}