/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.services.html.refs;

import java.util.Comparator;

import org.exoplatform.services.chars.CharsUtil;

/**
 * Author : Nhu Dinh Thuan nhudinhthuan@yahoo.com May 8, 2006
 */
public class RefsDecoder {

    private static final CharRefs charRefs = new CharRefs();

    static Comparator<CharRef> comparator = new Comparator<CharRef>() {
        public int compare(CharRef o1, CharRef o2) {
            return o1.getName().compareTo(o2.getName());
        }
    };

    public String decode(String text) {
        return new String(decodeChars(text.toCharArray()));
    }

    public char[] decode(char[] chars) {
        return decodeChars(chars);
    }

    private char[] decodeChars(char[] chars) {
        if (!charRefs.isSorted())
            charRefs.sort(comparator);
        int index = CharsUtil.indexOf(chars, '&', 0);
        if (index < 0 || CharsUtil.indexOf(chars, ';', index) < 0)
            return chars;
        CharsSequence decode = new CharsSequence(chars.length);
        int temp = 0;
        int end = 0;
        boolean ref = true;
        char[] ckey = null;
        while (index < chars.length) {
            if (temp < index)
                copy(chars, decode, temp, index);
            end = CharsUtil.indexOf(chars, ';', index);
            if (end < 0)
                break;
            if (chars[index + 1] == '#') {
                ckey = new char[end - index - 2];
                System.arraycopy(chars, index + 2, ckey, 0, ckey.length);
                ref = false;
            } else {
                ckey = new char[end - index - 1];
                System.arraycopy(chars, index + 1, ckey, 0, ckey.length);
                ref = true;
            }
            if (!ref) {
                ref = decode(ckey, decode);
            } else {
                if (Character.isLetter(ckey[0])) {
                    CharRef item = charRefs.searchByName(new String(ckey), comparator);
                    if (item != null) {
                        decode.append((char) item.getValue());
                    } else {
                        copy(chars, decode, index, end + 1);
                    }
                } else {
                    copy(chars, decode, index, end);
                }
            }
            temp = end + 1;
            index = CharsUtil.indexOf(chars, '&', temp);
            if (index < 0)
                break;
        }
        copy(chars, decode, temp, chars.length);
        return decode.getValues();
    }

    private void copy(char[] chars, CharsSequence decode, int start, int end) {
        while (start < chars.length) {
            if (start == end)
                break;
            decode.append(chars[start]);
            start++;
        }
    }

    private boolean decode(char[] values, CharsSequence decode) {
        char character;
        int number = 0;
        int radix = 0;
        boolean done = false;
        int i = 0;
        while (i < values.length && !done) {
            character = values[i];
            if (Character.isDigit(character)) {
                if (radix == 0)
                    radix = 10;
                number = number * radix + (character - '0');
            } else {
                switch (character) {
                    case 'A':
                    case 'B':
                    case 'C':
                    case 'D':
                    case 'E':
                    case 'F':
                        if (radix == 16)
                            number = number * radix + (character - 'A' + 10);
                        else
                            done = true;
                        break;
                    case 'a':
                    case 'b':
                    case 'c':
                    case 'd':
                    case 'e':
                    case 'f':
                        if (radix == 16)
                            number = number * radix + (character - 'a' + 10);
                        else
                            done = true;
                        break;
                    case 'x':
                    case 'X':
                        if (radix == 0)
                            radix = 16;
                        else
                            done = true;
                        break;
                    case ';':
                        done = true;
                        i++;
                        break;
                    default:
                        done = true;
                        break;
                }
            }
            if (!done)
                i++;
        }
        if (number == 0)
            return true;
        decode.append((char) number);
        return false;
    }
}
