/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0

   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 org.bodington.util;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * This class take a pair of forground and background colours and allows them
 * to be adjusted to allow for people with visual problems.
 * @author John Maber
 */
public class ColourPreferenceMapper
{
    public static final Adjust TYPE_NORMAL = new Adjust("normal");
    public static final Adjust TYPE_SOFT = new Adjust("soft");
    public static final Adjust TYPE_CONTRAST = new Adjust("contrast");
    public static final Adjust TYPE_BLACK_ON_WHITE = new Adjust("black");
    public static final Adjust TYPE_WHITE_ON_BLACK = new Adjust("white");
    
    public static class Adjust extends IntStringEnum
    {
        protected Adjust(String name)
        {
            super(name, ColourPreferenceMapper.class);
        }
        
        public static Adjust getMapping(int index)
        {
            return (Adjust)getConst(index, ColourPreferenceMapper.class);
        }
        
        public static Adjust getMapping(String name)
        {
            return (Adjust)getConst(name, ColourPreferenceMapper.class);
        }
        
    }
    
    private static final int HUE = 0;
    private static final int SATURATION = 1;
    private static final int VALUE = 2;
    
    Color reference_background = Color.white;
    Color reference_foreground = Color.black;
    
    boolean dark_background;
    Color adjusted_background;
    
    Adjust type = TYPE_NORMAL;
    boolean valid = false;
    
    /** Creates a new instance of ColourPreferenceMapper */
    public ColourPreferenceMapper()
    {
    }

    public ColourPreferenceMapper( String code )
    {
        if ( code!=null )
        {
            StringTokenizer t = new StringTokenizer( code, "." );
            try
            {
                if ( t.hasMoreTokens() )
                    type = Adjust.getMapping(Integer.decode( t.nextToken() ).intValue());
                if ( type == null)
                    type = TYPE_NORMAL;
                if ( t.hasMoreTokens() )
                    reference_background =  Color.decode( t.nextToken() );
                if ( t.hasMoreTokens() )
                    reference_foreground =  Color.decode( t.nextToken() );
            }
            catch ( NumberFormatException nfe )
            {
                // just leave properties at default if code is invalid
            }
        }
    }

    public synchronized void setType( Adjust type )
    {
        this.type = type;
        valid = false;
    }

    public synchronized void setReferenceColours( Color b, Color f )
    {
    reference_background = b;
    reference_foreground = f;
    valid = false;
    }

    public synchronized Color getReferenceBackgroundColor()
    {
        return reference_background;
    }
    
    public synchronized Color getReferenceForegroundColor()
    {
        return reference_foreground;
    }
    
    public synchronized Color getPreferredBackgroundColor()
    {
        if ( !valid )
            readjust();
        
        return adjusted_background;
    }
    
    public synchronized Color getPreferredForegroundColor( Color input )
    {
        if ( !valid )
            readjust();
        
        if ( type == TYPE_NORMAL )
            return input;
        if ( type == TYPE_BLACK_ON_WHITE )
            return Color.black;
        if ( type == TYPE_WHITE_ON_BLACK )
            return Color.white;

        float value_diff;
        float[] col_components_f = new float[3], col_components_b = new float[3];

        Color.RGBtoHSB( 
                adjusted_background.getRed(), 
                adjusted_background.getGreen(), 
                adjusted_background.getBlue(),
                col_components_b );
        Color.RGBtoHSB( 
                input.getRed(), 
                input.getGreen(), 
                input.getBlue(),
                col_components_f );

        value_diff = col_components_f[VALUE] - col_components_b[VALUE];

        if ( type == TYPE_CONTRAST )
        {
            if ( dark_background )
            {
                if ( value_diff < 0.6 )
                    col_components_f[VALUE] = col_components_b[VALUE] + (float)0.6;
            }
            else
            {
                if ( value_diff > -0.6 )
                    col_components_f[VALUE] = col_components_b[VALUE] - (float)0.6;
            }
        }
        
        if ( type == TYPE_SOFT )
        {
            if ( col_components_f[SATURATION] > 0.5 )
                col_components_f[SATURATION] = (float)0.5;
                
            if ( dark_background )
            {
                if ( value_diff > 0.4 )
                    col_components_f[VALUE] = col_components_b[VALUE] + (float)0.4;
            }
            else
            {
                if ( value_diff < -0.4 )
                    col_components_f[VALUE] = col_components_b[VALUE] - (float)0.4;
            }
        }
        
    return Color.getHSBColor( col_components_f[HUE], col_components_f[SATURATION], col_components_f[VALUE] );        
    }
    
    private synchronized void readjust()
    {
        valid = true;
        
        if ( type == TYPE_NORMAL )
        {
            adjusted_background = reference_background;
            return;
        }
        if ( type == TYPE_BLACK_ON_WHITE  )
        {
            adjusted_background = Color.white;
            return;
        }
        if ( type == TYPE_WHITE_ON_BLACK )
        {
            adjusted_background = Color.black;
            return;
        }

        float[] col_components_f = new float[3], col_components_b = new float[3];

        Color.RGBtoHSB( 
                reference_background.getRed(), 
                reference_background.getGreen(), 
                reference_background.getBlue(),
                col_components_b );
        Color.RGBtoHSB( 
                reference_foreground.getRed(), 
                reference_foreground.getGreen(), 
                reference_foreground.getBlue(),
                col_components_f );

        dark_background = col_components_b[VALUE] < col_components_f[VALUE];
        
        // work out if background needs to be adjusted to increase
        // contrast or reduce its colour saturation
        if ( type == TYPE_CONTRAST )
        {
            if ( dark_background )
            {
                if ( col_components_b[VALUE] <= 0.4 )
                    {
                        adjusted_background = reference_background;
                        return;
                    }
                col_components_b[VALUE] = (float)0.4;
            }
            else
            {
                if ( col_components_b[VALUE] >= 0.6 )
                    {
                        adjusted_background = reference_background;
                        return;
                    }
                col_components_b[VALUE] = (float)0.6;
            }
        }
        
        if ( type == TYPE_SOFT )
        {
            if ( col_components_b[SATURATION] < 0.3 )
            {
                adjusted_background = reference_background;
                return;
            }
                
            // reduce saturated colours
            if ( col_components_b[SATURATION] > 0.3 )
                col_components_b[SATURATION] = (float)0.3;
        }

        // convert altered colors back to RGB
        adjusted_background = Color.getHSBColor( col_components_b[HUE], col_components_b[SATURATION], col_components_b[VALUE] );
    }
    
    
    /** Creates a string code to represent the content of the instance */
    public String toString()
    {
        StringBuffer code = new StringBuffer();
        code.append( Integer.toHexString( type.getId() ) );
        code.append( ".0x" );
        code.append( Integer.toHexString( reference_background.getRGB() & 0xffffff ) );
        code.append( ".0x" );
        code.append( Integer.toHexString( reference_foreground.getRGB() & 0xffffff ) );
        
        return code.toString();
    }
}
