/*
 * 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
 *
 * 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.
 */

/* $Id$ */

package org.apache.fop.render.txt.border;

import java.util.Arrays;

/**
 * This class is responsible for solid and double border elements managing.
 */
public class SolidAndDoubleBorderElement extends AbstractBorderElement {

    private static final char LIGHT_HORIZONTAL = '\u2500';

    private static final char LIGHT_VERTICAL = '\u2502';

    private static final char LIGHT_DOWN_AND_RIGHT = '\u250C';

    private static final char LIGHT_DOWN_AND_LEFT = '\u2510';

    private static final char LIGHT_UP_AND_RIGHT = '\u2514';

    private static final char LIGHT_UP_AND_LEFT = '\u2518';

    private static final char LIGHT_VERTICAL_AND_RIGHT = '\u251C';

    private static final char LIGHT_VERTICAL_AND_LEFT = '\u2524';

    private static final char LIGHT_DOWN_AND_HORIZONTAL = '\u252C';

    private static final char LIGHT_UP_AND_HORIZONTAL = '\u2534';

    private static final char LIGHT_VERTICAL_AND_HORIZONTAL = '\u253C';

    private static final char DOUBLE_HORIZONTAL = '\u2550';

    private static final char DOUBLE_VERTICAL = '\u2551';

    private static final char DOUBLE_DOWN_AND_RIGHT = '\u2554';

    private static final char DOUBLE_DOWN_AND_LEFT = '\u2557';

    private static final char DOUBLE_UP_AND_RIGHT = '\u255A';

    private static final char DOUBLE_UP_AND_LEFT = '\u255D';

    private static final char DOUBLE_VERTICAL_AND_RIGHT = '\u2560';

    private static final char DOUBLE_VERTICAL_AND_LEFT = '\u2563';

    private static final char DOUBLE_DOWN_AND_HORIZONTAL = '\u2566';

    private static final char DOUBLE_UP_AND_HORIZONTAL = '\u2569';

    private static final char DOUBLE_VERTICAL_AND_HORIZONTAL = '\u256C';

    private static final char DOWN_SINGLE_AND_RIGHT_DOUBLE = '\u2552';

    private static final char DOWN_DOUBLE_AND_RIGHT_SINGLE = '\u2553';

    private static final char DOWN_SINGLE_AND_LEFT_DOUBLE = '\u2555';

    private static final char DOWN_DOUBLE_AND_LEFT_SINGLE = '\u2556';

    private static final char UP_SINGLE_AND_RIGHT_DOUBLE = '\u2558';

    private static final char UP_DOUBLE_AND_RIGHT_SINGLE = '\u2559';

    private static final char UP_SINGLE_AND_LEFT_DOUBLE = '\u255B';

    private static final char UP_DOUBLE_AND_LEFT_SINGLE = '\u255C';

    private static final char VERTICAL_SINGLE_AND_RIGHT_DOUBLE = '\u255E';

    private static final char VERTICAL_DOUBLE_AND_RIGHT_SINGLE = '\u255F';

    private static final char VERTICAL_SINGLE_AND_LEFT_DOUBLE = '\u2561';

    private static final char VERTICAL_DOUBLE_AND_LEFT_SINGLE = '\u2562';

    private static final char DOWN_SINGLE_AND_HORIZONTAL_DOUBLE = '\u2564';

    private static final char DOWN_DOUBLE_AND_HORIZONTAL_SINGLE = '\u2565';

    private static final char UP_SINGLE_AND_HORIZONTAL_DOUBLE = '\u2567';

    private static final char UP_DOUBLE_AND_HORIZONTAL_SINGLE = '\u2568';

    private static final char VERTICAL_SINGLE_AND_HORIZONTAL_DOUBLE = '\u256A';

    private static final char VERTICAL_DOUBLE_AND_HORIZONTAL_SINGLE = '\u256B';

    private static final char UNDEFINED = '?';

    private static final int UP3 = 1;

    private static final int DOWN3 = 3;

    private static final int LEFT3 = 9;

    private static final int RIGHT3 = 27;

    private static final char[] MAP = new char[100];

    static {
        Arrays.fill(MAP, UNDEFINED);
        MAP[0] = ' ';
        MAP[UP3] = LIGHT_VERTICAL;
        MAP[DOWN3] = LIGHT_VERTICAL;
        MAP[RIGHT3] = LIGHT_HORIZONTAL;
        MAP[LEFT3] = LIGHT_HORIZONTAL;
        MAP[UP3 + DOWN3] = LIGHT_VERTICAL;
        MAP[LEFT3 + RIGHT3] = LIGHT_HORIZONTAL;
        MAP[UP3 + LEFT3] = LIGHT_UP_AND_LEFT;
        MAP[LEFT3 + DOWN3] = LIGHT_DOWN_AND_LEFT;
        MAP[DOWN3 + RIGHT3] = LIGHT_DOWN_AND_RIGHT;
        MAP[UP3 + RIGHT3] = LIGHT_UP_AND_RIGHT;
        MAP[UP3 + DOWN3 + RIGHT3] = LIGHT_VERTICAL_AND_RIGHT;
        MAP[UP3 + LEFT3 + DOWN3] = LIGHT_VERTICAL_AND_LEFT;
        MAP[LEFT3 + DOWN3 + RIGHT3] = LIGHT_DOWN_AND_HORIZONTAL;
        MAP[UP3 + LEFT3 + RIGHT3] = LIGHT_UP_AND_HORIZONTAL;
        MAP[UP3 + LEFT3 + DOWN3 + RIGHT3] = LIGHT_VERTICAL_AND_HORIZONTAL;
        //DOUBLE
        MAP[2 * UP3] = DOUBLE_VERTICAL;
        MAP[2 * DOWN3] = DOUBLE_VERTICAL;
        MAP[2 * RIGHT3] = DOUBLE_HORIZONTAL;
        MAP[2 * LEFT3] = DOUBLE_HORIZONTAL;
        MAP[2 * UP3 + 2 * DOWN3] = DOUBLE_VERTICAL;
        MAP[2 * LEFT3 + 2 * RIGHT3] = DOUBLE_HORIZONTAL;
        MAP[2 * UP3 + 2 * LEFT3] = DOUBLE_UP_AND_LEFT;
        MAP[2 * LEFT3 + 2 * DOWN3] = DOUBLE_DOWN_AND_LEFT;
        MAP[2 * DOWN3 + 2 * RIGHT3] = DOUBLE_DOWN_AND_RIGHT;
        MAP[2 * UP3 + 2 * RIGHT3] = DOUBLE_UP_AND_RIGHT;
        MAP[2 * UP3 + 2 * DOWN3 + 2 * RIGHT3] = DOUBLE_VERTICAL_AND_RIGHT;
        MAP[2 * UP3 + 2 * DOWN3 + 2 * LEFT3] = DOUBLE_VERTICAL_AND_LEFT;
        MAP[2 * DOWN3 + 2 * RIGHT3 + 2 * LEFT3] = DOUBLE_DOWN_AND_HORIZONTAL;
        MAP[2 * UP3 + 2 * RIGHT3 + 2 * LEFT3] = DOUBLE_UP_AND_HORIZONTAL;
        MAP[2 * UP3 + 2 * DOWN3 + 2 * RIGHT3 + 2 * LEFT3] = DOUBLE_VERTICAL_AND_HORIZONTAL;
        //DOUBLE&SINGLE
        MAP[DOWN3 + 2 * RIGHT3] = DOWN_SINGLE_AND_RIGHT_DOUBLE;
        MAP[2 * DOWN3 + RIGHT3] = DOWN_DOUBLE_AND_RIGHT_SINGLE;
        MAP[DOWN3 + 2 * LEFT3] = DOWN_SINGLE_AND_LEFT_DOUBLE;
        MAP[2 * DOWN3 + LEFT3] = DOWN_DOUBLE_AND_LEFT_SINGLE;
        MAP[UP3 + 2 * RIGHT3] = UP_SINGLE_AND_RIGHT_DOUBLE;
        MAP[2 * UP3 + RIGHT3] = UP_DOUBLE_AND_RIGHT_SINGLE;
        MAP[UP3 + 2 * LEFT3] = UP_SINGLE_AND_LEFT_DOUBLE;
        MAP[2 * UP3 + LEFT3] = UP_DOUBLE_AND_LEFT_SINGLE;
        MAP[UP3 + DOWN3 + 2 * RIGHT3] = VERTICAL_SINGLE_AND_RIGHT_DOUBLE;
        MAP[2 * UP3 + 2 * DOWN3 + RIGHT3] = VERTICAL_DOUBLE_AND_RIGHT_SINGLE;
        MAP[UP3 + DOWN3 + 2 * LEFT3] = VERTICAL_SINGLE_AND_LEFT_DOUBLE;
        MAP[2 * UP3 + 2 * DOWN3 + LEFT3] = VERTICAL_DOUBLE_AND_LEFT_SINGLE;
        MAP[DOWN3 + 2 * LEFT3 + 2 * RIGHT3] = DOWN_SINGLE_AND_HORIZONTAL_DOUBLE;
        MAP[2 * DOWN3 + LEFT3 + RIGHT3] = DOWN_DOUBLE_AND_HORIZONTAL_SINGLE;
        MAP[UP3 + 2 * LEFT3 + 2 * RIGHT3] = UP_SINGLE_AND_HORIZONTAL_DOUBLE;
        MAP[2 * UP3 + LEFT3 + RIGHT3] = UP_DOUBLE_AND_HORIZONTAL_SINGLE;
        MAP[UP3 + DOWN3 + 2 * LEFT3 + 2 * RIGHT3] = VERTICAL_SINGLE_AND_HORIZONTAL_DOUBLE;
        MAP[2 * UP3 + 2 * DOWN3 + LEFT3 + RIGHT3] = VERTICAL_DOUBLE_AND_HORIZONTAL_SINGLE;
    }

    /**
     * Initializes a newly created <code>SolidAndDoubleBorderElement</code>
     * object so that it represents an empty border element.
     */
    public SolidAndDoubleBorderElement() {
    }

    /**
     * Constructs a newly allocated <code>SolidAndDoubleBorderElement</code>
     * object. Fills <code>data</code> using binary representation of
     * <code>type</code>. If border style is EN_DOUBLE, multiplies
     * <code>data[side]</code> by 2 for every side to distinguish EN_SOLID and
     * EN_DOUBLE.
     *
     * @param style integer, representing border style.
     * @param type binary representation of type gives <code>data</code>
     */
    public SolidAndDoubleBorderElement(int style, int type) {
        super(type);
        if (style == EN_DOUBLE) {
            for (int i = 0; i < 4; i++) {
                data[i] *= 2;
            }
        }
    }

    /**
     * Merges with <code>sde</code>.
     * @param sde instance of SolidAndDoubleBorderElement
     * @return instance of AbstractBorderElement
     */
    public AbstractBorderElement mergeSolid(SolidAndDoubleBorderElement sde) {
        AbstractBorderElement e = new SolidAndDoubleBorderElement(EN_SOLID, 0);
        for (int i = 0; i < 4; i++) {
            if (sde.getData(i) != 0) {
                e.setData(i, sde.getData(i));
            } else {
                e.setData(i, data[i]);
            }
        }
        return e;
    }

    /**
     * Merges with e.
     * @param e instance of AbstractBorderElement
     * @return instance of AbstractBorderElement
     */
    public AbstractBorderElement merge(AbstractBorderElement e) {
        AbstractBorderElement abe = this;
        if (e instanceof SolidAndDoubleBorderElement) {
            abe = mergeSolid((SolidAndDoubleBorderElement) e);
        } else if (e instanceof DottedBorderElement) {
            abe = e;
        } else if (e instanceof DashedBorderElement) {
            abe = e.merge(this);
        }
        return abe;
    }

    /**
     * Maps to char.
     * @return resulting mapping char
     */
    private char map2Char() {
        int key = 0;
        key += data[UP] * UP3;
        key += data[LEFT] * LEFT3;
        key += data[DOWN] * DOWN3;
        key += data[RIGHT] * RIGHT3;
        return MAP[key];
    }

    /**
     * Modifies data to nearest normal internal representation.
     */
    private void modifyData() {
        int c1 = 0;
        int c2 = 0;
        for (int i = 0; i < 4; i++) {
            c1 += (data[i] == 1) ? 1 : 0;
            c2 += (data[i] == 2) ? 1 : 0;
        }
        int m = c1 > c2 ? 1 : 0;
        int[] p = {0, m, 2 * (1 - m)};
        for (int i = 0; i < 4; i++) {
            data[i] = p[data[i]];
        }
    }

    /**
     * {@inheritDoc}
     */
    public char convert2Char() {
        char ch = map2Char();
        if (ch == UNDEFINED) {
            modifyData();
            ch = map2Char();
        }
        return ch;
    }
}
