/*
 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.apple.laf;

import java.awt.*;
import java.beans.*;
import java.lang.reflect.Method;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;

import apple.laf.*;
import apple.laf.JRSUIConstants.*;

import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;

public class AquaUtilControlSize {
    protected final static String CLIENT_PROPERTY_KEY = "JComponent.sizeVariant";
    protected final static String SYSTEM_PROPERTY_KEY = "swing.component.sizevariant";

    interface Sizeable {
        void applySizeFor(final JComponent c, final Size size);
    }

    protected static final RecyclableSingleton<PropertySizeListener> sizeListener = new RecyclableSingletonFromDefaultConstructor<PropertySizeListener>(PropertySizeListener.class);
    protected static PropertySizeListener getSizeListener() {
        return sizeListener.get();
    }

    protected static void addSizePropertyListener(final JComponent c) {
        c.addPropertyChangeListener(CLIENT_PROPERTY_KEY, getSizeListener());
        PropertySizeListener.applyComponentSize(c, c.getClientProperty(CLIENT_PROPERTY_KEY));
    }

    protected static void removeSizePropertyListener(final JComponent c) {
        c.removePropertyChangeListener(CLIENT_PROPERTY_KEY, getSizeListener());
    }

    private static JRSUIConstants.Size getSizeFromString(final String name) {
        if ("regular".equalsIgnoreCase(name)) return Size.REGULAR;
        if ("small".equalsIgnoreCase(name)) return Size.SMALL;
        if ("mini".equalsIgnoreCase(name)) return Size.MINI;
        if ("large".equalsIgnoreCase(name)) return Size.LARGE;
        return null;
    }

    private static Size getDefaultSize() {
        final String sizeProperty = java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction(SYSTEM_PROPERTY_KEY));
        final JRSUIConstants.Size size = getSizeFromString(sizeProperty);
        if (size != null) return size;
        return JRSUIConstants.Size.REGULAR;
    }

    protected final static JRSUIConstants.Size defaultSize = getDefaultSize();
    protected static JRSUIConstants.Size getUserSizeFrom(final JComponent c) {
        final Object sizeProp = c.getClientProperty(CLIENT_PROPERTY_KEY);
        if (sizeProp == null) return defaultSize;
        final Size size = getSizeFromString(sizeProp.toString());
        if (size == null) return Size.REGULAR;
        return size;
    }

    protected static JRSUIConstants.Size applySizeForControl(final JComponent c, final AquaPainter<? extends JRSUIState> painter) {
        final JRSUIConstants.Size sizeFromUser = getUserSizeFrom(c);
        final JRSUIConstants.Size size = sizeFromUser == null ? JRSUIConstants.Size.REGULAR : sizeFromUser;
        painter.state.set(size);
        return size;
    }

    protected static Font getFontForSize(final Component c, final JRSUIConstants.Size size) {
        final Font initialFont = c.getFont();

        if (size == null || !(initialFont instanceof UIResource)) return initialFont;

        if (size == JRSUIConstants.Size.MINI) return initialFont.deriveFont(AquaFonts.getMiniControlTextFont().getSize2D());
        if (size == JRSUIConstants.Size.SMALL) return initialFont.deriveFont(AquaFonts.getSmallControlTextFont().getSize2D());

        return initialFont.deriveFont(AquaFonts.getControlTextFont().getSize2D());
    }

    private static void applyBorderForSize(final JComponent c, final Size size) {
        final Border border = c.getBorder();
        if (!(border instanceof AquaBorder)) return;
        final AquaBorder aquaBorder = (AquaBorder)border;

        if (aquaBorder.sizeVariant.size == size) return;
        final AquaBorder derivedBorder = aquaBorder.deriveBorderForSize(size);
        if (derivedBorder == null) return;

        c.setBorder(derivedBorder);
    }

    // call JComponent.getUI() if it exists, then call Sizeable.applySizeFor() if the UI is "Sizeable"
    // next best thing to -respondsToSelector: :-P
    private static void applyUISizing(final JComponent c, final Size size) {
        try {
            // see if this component has a "getUI" method
            final Class<? extends JComponent> clazz = c.getClass();
            final Method getUIMethod = clazz.getMethod("getUI", new Class[0]);

            // see if that UI is one of ours that understands sizing
            final Object ui = getUIMethod.invoke(c, new Object[0]);
            if (!(ui instanceof Sizeable)) return;

            // size it!
            final Sizeable sizeable = (Sizeable)ui;
            sizeable.applySizeFor(c, size);
        } catch (final Throwable e) { return; }
    }

    protected static class PropertySizeListener implements PropertyChangeListener {
        public void propertyChange(final PropertyChangeEvent evt) {
            final String key = evt.getPropertyName();
            if (!CLIENT_PROPERTY_KEY.equalsIgnoreCase(key)) return;

            final Object source = evt.getSource();
            if (!(source instanceof JComponent)) return;

            final JComponent c = (JComponent)source;
            applyComponentSize(c, evt.getNewValue());
        }

        protected static void applyComponentSize(final JComponent c, final Object value) {
            Size size = getSizeFromString(value == null ? null : value.toString());
            if (size == null) {
                size = getUserSizeFrom(c);
                if (size == Size.REGULAR) return;
            }

            applyBorderForSize(c, size);

            applyUISizing(c, size);

            final Font priorFont = c.getFont();
            if (!(priorFont instanceof FontUIResource)) return;
            c.setFont(getFontForSize(c, size));
        }
    }

    public static class SizeDescriptor {
        SizeVariant regular;
        SizeVariant small;
        SizeVariant mini;

        public SizeDescriptor(final SizeVariant variant) {
            regular = deriveRegular(variant);
            small = deriveSmall(new SizeVariant(regular));
            mini = deriveMini(new SizeVariant(small));
        }

        public SizeVariant deriveRegular(final SizeVariant v) {
            v.size = Size.REGULAR;
            return v;
        }

        public SizeVariant deriveSmall(final SizeVariant v) {
            v.size = Size.SMALL;
            return v;
        }

        public SizeVariant deriveMini(final SizeVariant v) {
            v.size = Size.MINI;
            return v;
        }

        public SizeVariant get(final JComponent c) {
            if (c == null) return regular;
            return get(getUserSizeFrom(c));
        }

        public SizeVariant get(final Size size) {
            if (size == Size.REGULAR) return regular;
            if (size == Size.SMALL) return small;
            if (size == Size.MINI) return mini;
            return regular;
        }

        public String toString() {
            return "regular[" + regular + "] small[" + small + "] mini[" + mini + "]";
        }
    }

    public static class SizeVariant {
        Size size = Size.REGULAR;
        Insets insets = new InsetsUIResource(0, 0, 0, 0);
        Insets margins = new InsetsUIResource(0, 0, 0, 0);
        Float fontSize;
        int w = 0;
        int h = 0;
    //    Integer textBaseline;

        public SizeVariant() { }

        public SizeVariant(final int minWidth, final int minHeight) {
            this.w = minWidth;
            this.h = minHeight;
        }

        public SizeVariant(final SizeVariant desc){
            this.size = desc.size;
            this.insets = new InsetsUIResource(desc.insets.top, desc.insets.left, desc.insets.bottom, desc.insets.right);
            this.margins = new InsetsUIResource(desc.margins.top, desc.margins.left, desc.margins.bottom, desc.margins.right);
            this.fontSize = desc.fontSize;
            this.w = desc.w;
            this.h = desc.h;
    //        this.textBaseline = desc.textBaseline;
        }

        public SizeVariant replaceInsets(final String insetName) {
            this.insets = UIManager.getInsets(insetName);
            return this;
        }

        public SizeVariant replaceInsets(final Insets i) {
            this.insets = new InsetsUIResource(i.top, i.left, i.bottom, i.right);
            return this;
        }

        public SizeVariant alterInsets(final int top, final int left, final int bottom, final int right) {
            insets = generateInsets(insets, top, left, bottom, right);
            return this;
        }

        public SizeVariant replaceMargins(final String marginName) {
            this.margins = UIManager.getInsets(marginName);
            return this;
        }

        public SizeVariant alterMargins(final int top, final int left, final int bottom, final int right) {
            margins = generateInsets(margins, top, left, bottom, right);
            return this;
        }

        public SizeVariant alterFontSize(final float newSize) {
            final float oldSize = fontSize == null ? 0.0f : fontSize.floatValue();
            fontSize = new Float(newSize + oldSize);
            return this;
        }

        public SizeVariant alterMinSize(final int width, final int height) {
            this.w += width; this.h += height;
            return this;
        }

//        public SizeVariant alterTextBaseline(final int baseline) {
//            final int oldSize = textBaseline == null ? 0 : textBaseline.intValue();
//            textBaseline = new Integer(baseline + oldSize);
//            return this;
//        }

        static Insets generateInsets(final Insets i, final int top, final int left, final int bottom, final int right) {
            if (i == null) return new InsetsUIResource(top, left, bottom, right);
            i.top += top;
            i.left += left;
            i.bottom += bottom;
            i.right += right;
            return i;
        }

        public String toString() {
            return "insets:" + insets + ", margins:" + margins + ", fontSize:" + fontSize;// + ", textBaseline:" + textBaseline;
        }
    }
}