/*
 * 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: PDFPageLabels.java 1421233 2012-12-13 11:30:57Z phancock $ */

package org.apache.fop.pdf;

import java.util.regex.Pattern;

/**
 * Class representing a PDF /PageLabels dictionary.
 */
public class PDFPageLabels extends PDFNumberTreeNode {

    private static final int DECIMAL = 1; // '0*1'
    private static final int LOWER_ALPHA = 2; // 'a'
    private static final int UPPER_ALPHA = 3; // 'A'
    private static final int LOWER_ROMAN = 4; // 'i'
    private static final int UPPER_ROMAN = 5; // 'I'
    private static final int PREFIX = 6;

    private static final PDFName S_DECIMAL = new PDFName("D");
    private static final PDFName S_UPPER_ROMAN = new PDFName("R");
    private static final PDFName S_LOWER_ROMAN = new PDFName("r");
    private static final PDFName S_UPPER_ALPHA = new PDFName("A");
    private static final PDFName S_LOWER_ALPHA = new PDFName("a");

    private static final Pattern MATCH_DECIMAL = Pattern.compile("\\d+");
    private static final Pattern MATCH_ROMAN = Pattern.compile(
            "^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$", Pattern.CASE_INSENSITIVE);
    private static final Pattern MATCH_LETTER = Pattern.compile("^[a-zA-Z]$");

    private int lastPageLabelType;
    private int lastPageNumber;
    private String lastZeroPaddingPrefix = "";

    /**
     * Create the /PageLabels dictionary
     */
    public PDFPageLabels() {
        super();
    }

    /**
     * Adds a new entry, if necessary, to the /PageLabels dictionary.
     * @param index the page index (0 for page 1)
     * @param pageLabel the page number as a string
     */
    public void addPageLabel(int index, String pageLabel) {
        boolean addNewPageLabel = false;
        String padding = "00000000";
        int currentPageNumber = 0;
        int currentPageLabelType = 0;
        String currentZeroPaddingPrefix = "";
        if (MATCH_DECIMAL.matcher(pageLabel).matches()) {
            // since an integer is the most common case we start with that
            currentPageLabelType = DECIMAL;
            currentPageNumber = Integer.parseInt(pageLabel);
            int zeroPadding = 0;
            if (pageLabel.charAt(zeroPadding) == '0') {
                do {
                    zeroPadding++;
                } while (pageLabel.charAt(zeroPadding) == '0');
                currentZeroPaddingPrefix = padding.substring(0, zeroPadding);
                if (currentZeroPaddingPrefix.length() != lastZeroPaddingPrefix.length()) {
                    addNewPageLabel = true;
                }
            } else {
                if (lastZeroPaddingPrefix.length() != 0) {
                    addNewPageLabel = true;
                }
            }
        } else if (MATCH_ROMAN.matcher(pageLabel).matches()) {
            if (pageLabel.toLowerCase().equals(pageLabel)) {
                currentPageLabelType = LOWER_ROMAN;
            } else {
                currentPageLabelType = UPPER_ROMAN;
            }
            currentPageNumber = romanToArabic(pageLabel);
        } else if (MATCH_LETTER.matcher(pageLabel).matches()) {
            char c = pageLabel.charAt(0);
            if (c > 'Z') {
                currentPageLabelType = LOWER_ALPHA;
            } else {
                currentPageLabelType = UPPER_ALPHA;
            }
            currentPageNumber = alphabeticToArabic(c);
        } else {
            // alphabetic numbering in XSL_FO and labelling in PDF are different after AA (AB versus BB)
            // we will use the /P (prefix) label in that case
            currentPageLabelType = PREFIX;
            addNewPageLabel = true;
        }
        if (lastPageLabelType != currentPageLabelType) {
            addNewPageLabel = true;
        }
        if (lastPageNumber != currentPageNumber - 1) {
            addNewPageLabel = true;
        }
        if (addNewPageLabel) {
            PDFNumsArray nums = getNums();
            PDFDictionary dict = new PDFDictionary(nums);
            PDFName pdfName = null;
            switch (currentPageLabelType) {
            case PREFIX:
                dict.put("P", pageLabel);
                break;
            default:
                switch (currentPageLabelType) {
                case DECIMAL:
                    pdfName = S_DECIMAL;
                    if (currentZeroPaddingPrefix.length() != 0) {
                        dict.put("P", currentZeroPaddingPrefix);
                    }
                    break;
                case LOWER_ROMAN:
                    pdfName = S_LOWER_ROMAN;
                    break;
                case UPPER_ROMAN:
                    pdfName = S_UPPER_ROMAN;
                    break;
                case LOWER_ALPHA:
                    pdfName = S_LOWER_ALPHA;
                    break;
                case UPPER_ALPHA:
                    pdfName = S_UPPER_ALPHA;
                    break;
                default:
                }
                dict.put("S", pdfName);
                if (currentPageNumber != 1) {
                    dict.put("St", currentPageNumber);
                }
            }
            nums.put(index, dict);
        }
        lastPageLabelType = currentPageLabelType;
        lastPageNumber = currentPageNumber;
        lastZeroPaddingPrefix = currentZeroPaddingPrefix;
    }

    private int romanToArabic(String roman) {
        int arabic = 0;
        int previousValue = 0;
        int newValue = 0;
        String upperRoman = roman.toUpperCase();
        for (int i = 0; i < upperRoman.length(); i++) {
            char romanDigit = upperRoman.charAt(i);
            switch (romanDigit) {
            case 'I':
                newValue = 1;
                break;
            case 'V':
                newValue = 5;
                break;
            case 'X':
                newValue = 10;
                break;
            case 'L':
                newValue = 50;
                break;
            case 'C':
                newValue = 100;
                break;
            case 'D':
                newValue = 500;
                break;
            case 'M':
                newValue = 1000;
                break;
            default:
            }
            if (previousValue < newValue) {
                arabic -= previousValue;
            } else {
                arabic += previousValue;
            }
            previousValue = newValue;
        }
        arabic += previousValue;
        return arabic;
    }

    private int alphabeticToArabic(char c) {
        int arabic = Character.toLowerCase(c) - 'a' + 1;
        return arabic;
    }
}
