/*
 * Copyright 2004-2005 The Trix Development Team.
 *
 * 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.trix.cuery.value;

import java.lang.reflect.Field;
import java.math.BigDecimal;

import org.apache.commons.lang.StringUtils;

import org.trix.cuery.property.PropertyRegistry;

import org.w3c.css.sac.LexicalUnit;
import org.w3c.dom.DOMException;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.w3c.dom.css.Counter;
import org.w3c.dom.css.RGBColor;
import org.w3c.dom.css.Rect;

/**
 * DOCUMENT.
 * 
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: ValueUtil.java,v 1.03 2005/09/20 4:45:08 Teletha Exp $
 */
public final class ValueUtil {

    /** The zero pixel. */
    public static final PixelLength ZERO_PIXEL = new PixelLength(0);

    /** The standard scale size. */
    private static final int SCALE = 1;

    /**
     * Avoid creating ValueUtil instance.
     */
    private ValueUtil() {
    }

    /**
     * Convert a value of absolute length to pixel length.
     * 
     * @param length A css value for length.
     * @return A pixel length value.
     */
    public static PixelLength convertToPixelLength(CSSLength length) {
        return new PixelLength(convertToPixelLength(length.getFloatValue(), length.getLexicalUnitType()));
    }

    /**
     * Convert a value of absolute length to pixel length.
     * 
     * @param value A float value for length.
     * @param type A unit type.
     * @return A pixel length value.
     */
    public static float convertToPixelLength(float value, short type) {
        float result;
        int dpi = PropertyRegistry.getFontConfiguration().getDisplayDPI();

        switch (type) {
        case LexicalUnit.SAC_CENTIMETER:
            result = value * dpi / 15.24f;
            break;

        case LexicalUnit.SAC_INCH:
            result = value * dpi;
            break;

        case LexicalUnit.SAC_MILLIMETER:
            result = value * dpi / 152.4f;
            break;

        case LexicalUnit.SAC_PICA:
            result = value * dpi / 6;
            break;

        case LexicalUnit.SAC_PIXEL:
            result = value;
            break;

        case LexicalUnit.SAC_POINT:
            result = value * dpi / 72;
            break;

        default:
            result = value;
        }
        return round(result);
    }

    /**
     * Divide CSSLength by CSSPercentage.
     * 
     * @param length A base length
     * @param scale A scale.
     * @return A computed CSSLength.
     */
    public static CSSLength divide(CSSLength length, float scale) {
        // check null
        if (length == null) {
            return ZERO_PIXEL;
        }

        // compute
        return createCSSLength(length.getLexicalUnitType(), round(length.getFloatValue() / scale));
    }

    /**
     * Check wether this css value is CSSLength. This is a faster than by using instanceOf
     * operation.
     * 
     * @param value A target value.
     * @return A result.
     */
    public static boolean isCSSLength(CSSValue value) {
        short type = value.getPrimitiveType();
        return CSSPrimitiveValue.CSS_EMS <= type && type <= CSSPrimitiveValue.CSS_PC;
    }

    /**
     * Check wether this css value is absolute CSSLength. This is a faster than by using instanceOf
     * operation.
     * 
     * @param value A target value.
     * @return A result.
     */
    public static boolean isAbsoluteCSSLength(CSSValue value) {
        short type = value.getPrimitiveType();
        return CSSPrimitiveValue.CSS_PX <= type && type <= CSSPrimitiveValue.CSS_PC;
    }

    /**
     * Multiplt CSSLength and CSSPercentage.
     * 
     * @param length A base length
     * @param percentage A scale.
     * @return A computed CSSLength.
     */
    public static CSSLength multiply(CSSLength length, CSSPercentage percentage) {
        // check null
        if (percentage == null) {
            return multiply(length, 1);
        }
        return multiply(length, percentage.floatValue / 100);
    }

    /**
     * Multiplt CSSLength and scale value.
     * 
     * @param length A base length
     * @param scale A scale.
     * @return A computed CSSLength.
     */
    public static CSSLength multiply(CSSLength length, float scale) {
        // check null
        if (length == null) {
            return ZERO_PIXEL;
        }

        // compute
        return createCSSLength(length.getLexicalUnitType(), round(length.getFloatValue() * scale));
    }

    /**
     * Replace a current value to the new one.
     * 
     * @param current A current value to replace.
     * @param newValue A new value.
     * @return A replaced value.
     */
    public static CSSValue replace(CSSValue current, CSSValue newValue) {
        Field length = null;
        Field position = null;
        Field next = null;
        Field previous = null;

        try {
            length = AbstractCSSValue.class.getDeclaredField("length");
            length.setAccessible(true);
            length.setInt(newValue, length.getInt(current));

            position = AbstractCSSValue.class.getDeclaredField("position");
            position.setAccessible(true);
            position.setInt(newValue, position.getInt(current));

            next = AbstractCSSValue.class.getDeclaredField("next");
            next.setAccessible(true);

            previous = AbstractCSSValue.class.getDeclaredField("previous");
            previous.setAccessible(true);

            if (current.getPreviousValue() != null) {
                next.set(current.getPreviousValue(), newValue);
                previous.set(newValue, current.getPreviousValue());
            }

            if (current.getNextValue() != null) {
                next.set(newValue, current.getNextValue());
                previous.set(current.getNextValue(), newValue);
            }
        } catch (SecurityException e) {
            // do nothing
        } catch (IllegalArgumentException e) {
            // do nothing
        } catch (NoSuchFieldException e) {
            // do nothing
        } catch (IllegalAccessException e) {
            // do nothing
        } finally {
            if (length != null) {
                length.setAccessible(false);
            }

            if (position != null) {
                position.setAccessible(false);
            }

            if (next != null) {
                next.setAccessible(false);
            }

            if (previous != null) {
                previous.setAccessible(false);
            }
        }
        return newValue;
    }

    /**
     * Round float value off in the first place of the decimal point.
     * 
     * @param value A target value.
     * @return A result.
     */
    public static float round(float value) {
        return new BigDecimal(String.valueOf(value)).setScale(SCALE, BigDecimal.ROUND_HALF_EVEN).floatValue();
    }

    /**
     * Return a single css value.
     * 
     * @param value A target value.
     * @return A single css value.
     */
    public static CSSValue singleValue(CSSValue value) {
        // check length
        if (value.getLength() == 1) {
            return value;
        }
        return new SingleValue(value);
    }

    /**
     * Return a human readable css value.
     * 
     * @param value A css value.
     * @return A readable css value.
     */
    public static String toString(CSSValue value) {
        // check null
        if (value == null) {
            return StringUtils.EMPTY;
        }

        StringBuffer buffer = new StringBuffer();
        CSSValue current = value;

        while (current != null) {
            buffer.append(current.toString());

            // skip operator
            int type = current.getLexicalUnitType();

            if (type == LexicalUnit.SAC_OPERATOR_SLASH) {
                current = current.getNextValue();
                continue;
            }

            current = current.getNextValue();

            if (current != null) {
                type = current.getLexicalUnitType();

                if (type == LexicalUnit.SAC_OPERATOR_SLASH || type == LexicalUnit.SAC_OPERATOR_COMMA) {
                    continue;
                }
                buffer.append(' ');
            }
        }
        return buffer.toString();
    }

    /**
     * Create CSSLength by type with a value.
     * 
     * @param type A length type.
     * @param value A value.
     * @return A created CSSLength.
     */
    private static CSSLength createCSSLength(short type, float value) {
        switch (type) {
        case LexicalUnit.SAC_CENTIMETER:
            return new CentimeterLength(value);

        case LexicalUnit.SAC_EM:
            return new EMLength(value);

        case LexicalUnit.SAC_EX:
            return new EXLength(value);

        case LexicalUnit.SAC_INCH:
            return new InchLength(value);

        case LexicalUnit.SAC_MILLIMETER:
            return new MillimeterLength(value);

        case LexicalUnit.SAC_PICA:
            return new PicaLength(value);

        case LexicalUnit.SAC_PIXEL:
            return new PixelLength(value);

        case LexicalUnit.SAC_POINT:
            return new PointLength(value);

        default:
            return ZERO_PIXEL;
        }
    }

    /**
     * DOCUMENT.
     * 
     * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
     * @version $ Id: SingleValue.java,v 1.0 2005/09/01 10:43:30 Teletha Exp $
     */
    private static final class SingleValue implements CSSValue {

        /** The actual value. */
        private CSSValue value;

        /**
         * Create SingleValue instance.
         * 
         * @param value An actual value.
         */
        public SingleValue(CSSValue value) {
            this.value = value;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#get(int)
         */
        public CSSValue get(int index) {
            if (index != 1) {
                return null;
            }
            return value;
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getCounterValue()
         */
        public Counter getCounterValue() throws DOMException {
            return value.getCounterValue();
        }

        /**
         * @see org.w3c.dom.css.CSSValue#getCssText()
         */
        public String getCssText() {
            return value.getCssText();
        }

        /**
         * @see org.w3c.dom.css.CSSValue#getCssValueType()
         */
        public short getCssValueType() {
            return value.getCssValueType();
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getDimensionUnitText()
         */
        public String getDimensionUnitText() {
            return value.getDimensionUnitText();
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getFloatValue()
         */
        public float getFloatValue() {
            return value.getFloatValue();
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getFloatValue(short)
         */
        public float getFloatValue(short arg0) throws DOMException {
            return value.getFloatValue(arg0);
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getFunctionName()
         */
        public String getFunctionName() {
            return value.getFunctionName();
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getIntegerValue()
         */
        public int getIntegerValue() {
            return value.getIntegerValue();
        }

        /**
         * @see org.w3c.dom.css.CSSValueList#getLength()
         */
        public int getLength() {
            return 1;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getLexicalUnitType()
         */
        public short getLexicalUnitType() {
            return value.getLexicalUnitType();
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getNextLexicalUnit()
         */
        public LexicalUnit getNextLexicalUnit() {
            return null;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getNextValue()
         */
        public CSSValue getNextValue() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getParameters()
         */
        public LexicalUnit getParameters() {
            return value.getParameters();
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getPosition()
         */
        public int getPosition() {
            return 0;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getPreviousLexicalUnit()
         */
        public LexicalUnit getPreviousLexicalUnit() {
            return null;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getPreviousValue()
         */
        public CSSValue getPreviousValue() {
            return null;
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getPrimitiveType()
         */
        public short getPrimitiveType() {
            return value.getPrimitiveType();
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getRectValue()
         */
        public Rect getRectValue() throws DOMException {
            return value.getRectValue();
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getRGBColorValue()
         */
        public RGBColor getRGBColorValue() throws DOMException {
            return value.getRGBColorValue();
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getStringValue()
         */
        public String getStringValue() {
            return value.getStringValue();
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getSubValues()
         */
        public LexicalUnit getSubValues() {
            return value.getSubValues();
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getType()
         */
        public Class getType() {
            return value.getType();
        }

        /**
         * @see org.w3c.dom.css.CSSValueList#item(int)
         */
        public org.w3c.dom.css.CSSValue item(int index) {
            if (index != 1) {
                return null;
            }
            return value;
        }

        /**
         * @see org.w3c.dom.css.CSSValue#setCssText(java.lang.String)
         */
        public void setCssText(String arg0) throws DOMException {
            value.setCssText(arg0);
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#setFloatValue(short, float)
         */
        public void setFloatValue(short arg0, float arg1) throws DOMException {
            value.setFloatValue(arg0, arg1);
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#setStringValue(short, java.lang.String)
         */
        public void setStringValue(short arg0, String arg1) throws DOMException {
            value.setStringValue(arg0, arg1);
        }

        /**
         * @see java.lang.Object#toString()
         */
        public String toString() {
            return value.toString();
        }
    }
}
