/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alee.utils;

import com.alee.utils.swing.DataProvider;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * This utility class can be used to implement shape caching withing any painter or component.
 * This might be useful to improve component painting performance in case it uses complex shapes.
 *
 * @author Mikle Garin
 */

public class ShapeCache
{
    /**
     * todo 1. Improve shape settings key generation/usage performance
     */

    /**
     * Separator for settings cached within single key.
     */
    private static final String settingsSeparator = ";";

    /**
     * Shapes cache map.
     */
    private static final Map<Component, Map<String, CachedShape>> shapeCache = new WeakHashMap<Component, Map<String, CachedShape>> ( 10 );

    /**
     * Returns cached component shape.
     * If shape is not yet cached it will be created.
     * If shape settings are changed from the last time it was queued it will be re-created.
     *
     * @param component     component for which shape is cached
     * @param shapeId       unique shape ID
     * @param shapeProvider shape provider
     * @param settings      shape settings used as a shape key
     * @param <T>           shape type
     * @return cached component shape
     */
    public static <T extends Shape> T getShape ( final Component component, final String shapeId, final DataProvider<T> shapeProvider,
                                                 final Object... settings )
    {
        final String settingsKey = combineSettingsKey ( settings );
        Map<String, CachedShape> cacheById = shapeCache.get ( component );
        if ( cacheById == null )
        {
            // Shape is not yet cached
            final Shape shape = shapeProvider.provide ();
            cacheById = new HashMap<String, CachedShape> ( 1 );
            cacheById.put ( shapeId, new CachedShape ( settingsKey, shape ) );
            shapeCache.put ( component, cacheById );
            return ( T ) shape;
        }
        else
        {
            final CachedShape cachedShape = cacheById.get ( shapeId );
            if ( cachedShape == null || !cachedShape.getKey ().equals ( settingsKey ) )
            {
                // Shape is not yet cached or cache entry is outdated
                final Shape shape = shapeProvider.provide ();
                cacheById.put ( shapeId, new CachedShape ( settingsKey, shape ) );
                return ( T ) shape;
            }
            else
            {
                // Returning cached shape
                return ( T ) cachedShape.getShape ();
            }
        }
    }

    /**
     * Combines shape settings into a single key for cache map and returns it.
     *
     * @param settings settings to combine
     * @return key for the specified shape settings
     */
    private static String combineSettingsKey ( final Object... settings )
    {
        final StringBuilder stringBuilder = new StringBuilder ();
        for ( final Object object : settings )
        {
            if ( stringBuilder.length () > 0 )
            {
                stringBuilder.append ( settingsSeparator );
            }
            stringBuilder.append ( getSettingKey ( object ) );
        }
        return stringBuilder.toString ();
    }

    /**
     * Returns setting string representation.
     *
     * @param setting setting to be converted
     * @return setting string representation
     */
    private static String getSettingKey ( final Object setting )
    {
        if ( setting instanceof Insets )
        {
            final Insets i = ( Insets ) setting;
            return i.top + "," + i.left + "," + i.bottom + "," + i.right;
        }
        else if ( setting instanceof Rectangle )
        {
            final Rectangle r = ( Rectangle ) setting;
            return r.x + "," + r.y + "," + r.width + "," + r.height;
        }
        else if ( setting instanceof Point )
        {
            final Point p = ( Point ) setting;
            return p.x + "," + p.y;
        }
        else
        {
            return setting.toString ();
        }
    }

    /**
     * Cached shape class.
     */
    private static class CachedShape
    {
        private final String key;
        private final Shape shape;

        public CachedShape ( final String key, final Shape shape )
        {
            super ();
            this.key = key;
            this.shape = shape;
        }

        private String getKey ()
        {
            return key;
        }

        private Shape getShape ()
        {
            return shape;
        }
    }
}