/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 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 file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */

package org.openoffice.xmerge.util;

import java.awt.Color;

/**
 * Utility class mapping RGB colour specifications to the colour indices used
 * in the Pocket PC.
 *
 * <p>The original converter was written for use with Pocket Word it was later
 * put into the utils so Pocket excel could use this code also. For this reason
 * the default values are those used by Pocket Word but a colour table can be
 * passed in through the constructor to map the 16 values to a colour table.</p>
 *
 * <p>These colour indices are based on the Windows VGA 16 colour palette, which
 * later was used as the basis for the named colours in the HTML 3.2
 * specification.</p>
 *
 * <p>In Pocket Word's case, the match to the VGA 16 palette is not exact as it
 * swaps Grey and Silver, with Silver being the darker colour (i.e. having the
 * lower RGB value).</p>
 */

public class ColourConverter {

    /** Colour table index for Black */
    private static final short BLACK = 0;

    /** Colour table index for Silver */
    private static final short SILVER = 1;

    /** Colour table index for Grey */
    private static final short GREY = 2;

    /** Colour table index for White */
    private static final short WHITE = 3;

    /** Colour table index for Red */
    private static final short RED = 4;

    /** Colour table index for Lime */
    private static final short LIME = 5;

    /** Colour table index for Blue */
    private static final short BLUE = 6;

    /** Colour table index for Aqua */
    private static final short AQUA = 7;

    /** Colour table index for Fuchsia */
    private static final short FUCHSIA = 8;

    /** Colour table index for Yellow */
    private static final short YELLOW = 9;

    /** Colour table index for Maroon */
    private static final short MAROON = 10;

    /** Colour table index for Green */
    private static final short GREEN = 11;

    /** Colour table index for Navy */
    private static final short NAVY = 12;

    /** Colour table index for Teal */
    private static final short TEAL = 13;

    /** Colour table index for Purple */
    private static final short PURPLE = 14;

    /** Colour table index for Olive */
    public static final short OLIVE = 15;

    private short tableLookup[] = null;

    /**
     * Default constructor used in the case where a lookup table is not required.
     */
    public ColourConverter() {

    }

    /**
     * Constructor that passes in the colour lookup table.
     *
     * <p>This is required in cases where the 16 colour values are something
     * other than there default values (e.g. in the case of pocket Excel).</p>
     *
     * @param  lookup  a 16 bit array mapping the 16 colours to their values.
     */
    public ColourConverter(short lookup[]) {
        tableLookup = lookup;
    }

    /**
     * Uses the colour table if it exists to translate default values to values
     * in the colorTable.
     */
    private short colourLookup(short colour) {
        if(tableLookup!=null) {
            return tableLookup[colour];
        } else {
            return colour;
        }
    }

    /**
     * Uses the colour table if it exists to translate default values to values
     * in the colorTable.
     */
    private short indexLookup(short index) {

        short result = 0;

        if(tableLookup!=null) {
            for(short i = 0;i < tableLookup.length;i++) {
                if(tableLookup[i]==index)
                    result = i;
            }
        } else {
            result = index;
        }

        return result;
    }
    /**
     * This method maps a Pocket Word colour index value to an RGB value as used
     * by OpenOffice.
     *
     * @param   colour  The index into Pocket Word's colour table.
     *
     * @return  A {@code Color} object representing the RGB value of the Pocket
     *          Word colour.
     */
    public Color convertToRGB (short colour) {

        short index = indexLookup(colour);

        int r = 0;
        int g = 0;
        int b = 0;

        switch (index) {
            case SILVER:
                r = g = b = 128;
                break;

            case GREY:
                r = g = b = 192;
                break;

            case WHITE:
                r = g = b = 255;
                break;

            case RED:
                r = 255;
                break;

            case LIME:
                g = 255;
                break;

            case BLUE:
                b = 255;
                break;

            case AQUA:
                g = b = 255;
                break;

            case FUCHSIA:
                r = b = 255;
                break;

            case YELLOW:
                r = g = 255;
                break;

            case MAROON:
                r = 128;
                break;

            case GREEN:
                g = 128;
                break;

            case NAVY:
                b = 128;
                break;

            case TEAL:
                b = g = 128;
                break;

            case PURPLE:
                r = b = 128;
                break;

            case OLIVE:
                r = g = 128;
                break;

            case BLACK:
            default:
                r = g = b = 0;
                break;
        }

        return new Color(r, g, b);
    }

    /**
     * This method approximates an RGB value (as used by Writer) to one of the
     * 16 available colours.
     *
     * <p>Most of the supported colours have their components set to either 0,
     * 128 or 255. The exception is 'Grey' which is {@literal 0xC0C0C0}.</p>
     *
     * @param   colour  {@code Color} object representing the RGB value of the
     *                  colour.
     *
     * @return  Index into the Pocket Word colour table which represents the
     *          closest match to the specified colour.
     */
    public short convertFromRGB (Color colour) {
        int matchedRGB = 0;
        short indexColour = 0;
        int reducedMap[] =  new int[] { 0, 0, 128 };

          int red = colour.getRed();
          int green = colour.getGreen();
          int blue = colour.getBlue();

        // We need to convert the pale colours to their base color rather than
        // white so we modify the rgb values if the colour is sufficiently white.
           if(red>0xC0 && green>0xC0 && blue>0xC0) {

            if(red!=0xFF)
                red = getClosest(red, reducedMap);
            if(green!=0xFF)
                green = getClosest(green, reducedMap);
            if(blue!=0xFF)
                blue = getClosest(blue, reducedMap);
        }

        // Need to derive an RGB value that has been rounded to match the ones
        // Pocket Word knows about.
        matchedRGB += getClosest(red)   << 16;
        matchedRGB += getClosest(green) << 8;
           matchedRGB += getClosest(blue);

        // The colour map used by Pocket Word doesn't have any combinations of
        // values beyond 0 and any other value.  A value of 255 in any RGB code
        // indicates a dominant colour.  Other colours are only modifiers to the
        // principal colour(s).  Thus, for this conversion, modifiers can be
        // dropped.
        if ((matchedRGB & 0xFF0000) == 0xFF0000 || (matchedRGB & 0xFF00) == 0xFF00
                || (matchedRGB & 0xFF) == 0xFF) {
                    if ((matchedRGB & 0xFF0000) == 0x800000) {
                        matchedRGB ^= 0x800000;
                    }
                    if ((matchedRGB & 0xFF00) == 0x8000) {
                        matchedRGB ^= 0x8000;
                    }
                    if ((matchedRGB & 0xFF) == 0x80) {
                        matchedRGB ^= 0x80;
                    }
        }

        /*
         * And now for the actual matching ...
         *
         * Colours are based on the Windows VGA 16 palette.  One difference
         * though is that Pocket Word seems to switch the RGB codes for Grey
         * and Silver.  In Pocket Word Silver is the darker colour leaving Grey
         * is closest to White.
         *
         * Shades of grey will be converted to either Silver or White, where
         * Grey may be a more appropriate colour.  This is handled specially
         * only for Silver and White matches.
         */
        switch (matchedRGB) {
            case 0x000000:
                indexColour = BLACK;
                break;

            case 0x808080:
                if (!isGrey(colour)) {
                    indexColour = SILVER;
                }
                else {
                    indexColour = GREY;
                }
                break;

            case 0xFFFFFF:
                if (!isGrey(colour)) {
                    indexColour = WHITE;
                }
                else {
                    indexColour = GREY;
                }
                break;

            case 0xFF0000:
                indexColour = RED;
                break;

            case 0x00FF00:
                indexColour = LIME;
                break;

            case 0x0000FF:
                indexColour = BLUE;
                break;

            case 0x00FFFF:
                indexColour = AQUA;
                break;

            case 0xFF00FF:
                indexColour = FUCHSIA;
                break;

            case 0xFFFF00:
                indexColour = YELLOW;
                break;

            case 0x800000:
                indexColour = MAROON;
                break;

            case 0x008000:
                indexColour = GREEN;
                break;

            case 0x000080:
                indexColour = NAVY;
                break;

            case 0x008080:
                indexColour = TEAL;
                break;

            case 0x800080:
                indexColour = PURPLE;
                break;

            case 0x808000:
                indexColour = OLIVE;
                break;

            default:        // Just in case!
                indexColour = BLACK;
                break;
        }

        return colourLookup(indexColour);
    }

    /**
     * Default implementation, checks for the closest of value to 0, 128 or 255.
     */
    private int getClosest(int value) {
        int points[] = new int[] { 0, 128, 255 };

        return getClosest(value, points);
    }

    /**
     * Utility method that returns the closest of the three points to the value
     * supplied.
     */
    private int getClosest(int value, int[] points) {

        if (value == points[0] || value == points[1] || value == points[2]) {
            return value;
        }

        if (value < points[1]) {
            int x = value - points[0];
            return (Math.round((float)x / (points[1] - points[0])) == 1 ? points[1] : points[0]);
        }
        else {
            int x = value - points[1];
            return (Math.round((float)x / (points[2] - points[1])) >= 1 ? points[2] : points[1]);
        }
    }

    /**
     * Checks to see if the supplied colour can be considered to be grey.
     */
    private boolean isGrey(Color c) {
        int matchedRGB = 0;
        int points[] = new int[] { 128, 192, 255 };

        matchedRGB += getClosest(c.getRed(), points) << 16;
        matchedRGB += getClosest(c.getGreen(), points) << 8;
        matchedRGB += getClosest(c.getBlue(), points);

        return matchedRGB == 0xC0C0C0;
    }
}