/**
 * Distribution License:
 * JSword is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License, version 2.1 or later
 * as published by the Free Software Foundation. This program 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.
 *
 * The License is available on the internet at:
 *      http://www.gnu.org/copyleft/lgpl.html
 * or by writing to:
 *      Free Software Foundation, Inc.
 *      59 Temple Place - Suite 330
 *      Boston, MA 02111-1307, USA
 *
 * © CrossWire Bible Society, 2007 - 2016
 *
 */
package org.crosswire.jsword.versification;

import java.util.Locale;
import java.util.regex.Pattern;

import org.crosswire.common.util.StringUtil;
import org.crosswire.jsword.book.CaseType;

/**
 * BookName represents the different ways a book of the bible is named.
 * 
 * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 * @author DM Smith
 */
public final class BookName {
    /**
     * Create a BookName for a Book of the Bible in a given language.
     * 
     * @param locale
     *            the language of this BookName
     * @param book
     *            the Book's canonical number
     * @param longName
     *            the Book's long name
     * @param shortName
     *            the Book's short name, if any
     * @param alternateNames
     *            optional comma separated list of alternates for the Book
     */
    public BookName(Locale locale, BibleBook book, String longName, String shortName, String alternateNames) {
        this.locale = locale;
        this.book = book;
        this.longName = longName;
        this.normalizedLongName = normalize(longName, locale);
        this.shortName = shortName;
        this.normalizedShortName = normalize(shortName, locale);

        if (alternateNames != null) {
            this.alternateNames = StringUtil.split(normalize(alternateNames, locale), ',');
        }
    }

    /**
     * Get the BibleBook to which this set of names is tied.
     * 
     * @return The book
     */
    public BibleBook getBook() {
        return book;
    }

    /**
     * Get the preferred name of a book. Altered by the case setting (see
     * setBookCase() and isFullBookName())
     * 
     * @return The preferred name of the book
     */
    public String getPreferredName() {
        if (BookName.isFullBookName()) {
            return getLongName();
        }
        return getShortName();
    }

    /**
     * Get the full name of a book (e.g. "Genesis"). Altered by the case setting
     * (see setBookCase())
     * 
     * @return The full name of the book
     */
    public String getLongName() {
        CaseType caseType = BookName.getDefaultCase();

        if (caseType == CaseType.LOWER) {
            return longName.toLowerCase(locale);
        }

        if (caseType == CaseType.UPPER) {
            return longName.toUpperCase(locale);
        }

        return longName;
    }

    /**
     * Get the short name of a book (e.g. "Gen"). Altered by the case setting
     * (see setBookCase())
     * 
     * @return The short name of the book
     */
    public String getShortName() {
        CaseType caseType = BookName.getDefaultCase();

        if (caseType == CaseType.LOWER) {
            return shortName.toLowerCase(locale);
        }

        if (caseType == CaseType.UPPER) {
            return shortName.toUpperCase(locale);
        }

        return shortName;
    }

    /**
     * @return the normalizedLongName
     */
    public String getNormalizedLongName() {
        return normalizedLongName;
    }

    /**
     * @return the normalizedShortName
     */
    public String getNormalizedShortName() {
        return normalizedShortName;
    }

    /**
     * Match the normalized name as closely as possible. It will match if:
     * <ol>
     * <li>it is a prefix of a normalized alternate name</li>
     * <li>a normalized alternate name is a prefix of it</li>
     * <li>it is a prefix of a normalized long name</li>
     * <li>it is a prefix of a normalized short name</li>
     * <li>a normalized short name is a prefix of it</li>
     * </ol>
     * 
     * @param normalizedName
     *            the already normalized name to match against.
     * @return true of false
     */
    public boolean match(String normalizedName) {
        // Does it match one of the alternative versions
        for (int j = 0; j < alternateNames.length; j++) {
            String targetBookName = alternateNames[j];
            if (targetBookName.startsWith(normalizedName) || normalizedName.startsWith(targetBookName)) {
                return true;
            }
        }

        // Does it match a long version of the book
        if (normalizedLongName.startsWith(normalizedName)) {
            return true;
        }

        // or a short version
        if (normalizedShortName.startsWith(normalizedName) || (normalizedShortName.length() > 0 && normalizedName.startsWith(normalizedShortName))) {
            return true;
        }

        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return book.hashCode();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        final BookName other = (BookName) obj;
        return book == other.book;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return getPreferredName();
    }

    /**
     * Normalize by stripping punctuation and whitespace and lowercasing.
     * 
     * @param str
     *            the string to normalize
     * @param locale the locale that should be used for normalization
     * @return the normalized string
     */
    public static String normalize(String str, Locale locale) {
        return normPattern.matcher(str).replaceAll("").toLowerCase(locale);
    }

    /**
     * This is only used by config.
     * 
     * @param bookCase
     *            The new case to use for reporting book names
     * @exception IllegalArgumentException
     *                If the case is not between 0 and 2
     * @see #getCase()
     */
    public static void setCase(int bookCase) {
        BookName.bookCase = CaseType.fromInteger(bookCase);
    }

    /**
     * How do we report the names of the books?. These are static. This is on
     * the assumption that we will not want to have different sections of the
     * app using a different format. I expect this to be a good assumption, and
     * it saves passing a Book class around everywhere. CaseType.MIXED is not
     * allowed
     * 
     * @param newBookCase
     *            The new case to use for reporting book names
     * @exception IllegalArgumentException
     *                If the case is not between 0 and 2
     * @see #getCase()
     */
    public static void setCase(CaseType newBookCase) {
        BookName.bookCase = newBookCase;
    }

    /**
     * This is only used by config
     * 
     * @return The current case setting
     * @see #setCase(CaseType)
     */
    public static int getCase() {
        return BookName.bookCase.toInteger();
    }

    /**
     * This is only used by config
     * 
     * @return Whether the name is long or short. Default is Full (true).
     * @see #setFullBookName(boolean)
     */
    public static boolean isFullBookName() {
        return BookName.fullBookName;
    }

    /**
     * Set whether the name should be full or abbreviated, long or short.
     * 
     * @param fullName
     *            The new case to use for reporting book names
     * @see #isFullBookName()
     */
    public static void setFullBookName(boolean fullName) {
        BookName.fullBookName = fullName;
    }

    /**
     * How do we report the names of the books?.
     * 
     * @return The current case setting
     * @see #setCase(int)
     */
    public static CaseType getDefaultCase() {
        return BookName.bookCase;
    }

    /** remove spaces and some punctuation in Book Name (make sure , is allowed) */
    private static Pattern normPattern = Pattern.compile("[. ]");

    private BibleBook book;
    private String longName;
    private String normalizedLongName;
    private String shortName;
    private String normalizedShortName;
    private String[] alternateNames;

    /** The locale for the Book Name */
    private Locale locale;

    /** How the book names are reported. */
    private static CaseType bookCase = CaseType.SENTENCE;

    /** Whether long or short, full or abbreviated names are used. */
    private static boolean fullBookName = true;

}
