/* $Id$
 *
 * Copyright 2007 by Howard Shank (hgshank@yahoo.com)
 *
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the License.
 *
 * The Original Code is 'iText, a free JAVA-PDF library'.
 *
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 * the Initial Developer are Copyright (C) 1999-2006 by Bruno Lowagie.
 * All Rights Reserved.
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 * are Copyright (C) 2000-2006 by Paulo Soares. All Rights Reserved.
 *
 * Contributor(s): all the names of the contributors are added in the source code
 * where applicable.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * LGPL license (the ?GNU LIBRARY GENERAL PUBLIC LICENSE?), in which case the
 * provisions of LGPL are applicable instead of those above.  If you wish to
 * allow use of your version of this file only under the terms of the LGPL
 * License and not to allow others to use your version of this file under
 * the MPL, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the LGPL.
 * If you do not delete the provisions above, a recipient may use your version
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the MPL as stated above or under the terms of the GNU
 * Library General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or any later version.
 *
 * This library 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 Library general Public License for more
 * details.
 *
 * If you didn't download this code from the following link, you should check if
 * you aren't using an obsolete version:
 * http://www.lowagie.com/iText/
 */
package com.lowagie.text.rtf.parser.properties;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.lowagie.text.rtf.parser.ctrlwords.RtfCtrlWordData;

/**
 * <code>RtfProperty</code> handles document, paragraph, etc. property values
 * 
 * @author Howard Shank (hgshank@yahoo.com)
 * @since 2.0.8
 */
public class RtfProperty {
	public static final int OFF = 0;
	public static final int ON = 1;
	
	/* property groups */
	public static final String COLOR = "color.";
	public static final String CHARACTER = "character.";
	public static final String PARAGRAPH = "paragraph.";
	public static final String SECTION = "section.";
	public static final String DOCUMENT = "document.";
	
	/* color properties */
	public static final String COLOR_FG = COLOR + "fg"; //Color Object
	public static final String COLOR_BG = COLOR + "bg"; //Color Object
	
	/* character properties */
	public static final String CHARACTER_BOLD = CHARACTER + "bold";	
	public static final String CHARACTER_UNDERLINE = CHARACTER + "underline";
	public static final String CHARACTER_ITALIC = CHARACTER + "italic";
	public static final String CHARACTER_SIZE = CHARACTER + "size"; 
	public static final String CHARACTER_FONT = CHARACTER + "font";
	public static final String CHARACTER_STYLE = CHARACTER + "style";

	/* paragraph properties */
	/** Justify left */
	public static final int JUSTIFY_LEFT = 0;
	/** Justify right */
	public static final int JUSTIFY_RIGHT = 1;
	/** Justify center */
	public static final int JUSTIFY_CENTER = 2;
	/** Justify full */
	public static final int JUSTIFY_FULL = 3;
	
	public static final String PARAGRAPH_INDENT_LEFT = PARAGRAPH + "indentLeft";	//  twips
	public static final String PARAGRAPH_INDENT_RIGHT = PARAGRAPH + "indentRight";	// twips
	public static final String PARAGRAPH_INDENT_FIRST_LINE = PARAGRAPH + "indentFirstLine";	// twips
	public static final String PARAGRAPH_JUSTIFICATION = PARAGRAPH + "justification";
	
	public static final String PARAGRAPH_BORDER = PARAGRAPH + "border";
	public static final String PARAGRAPH_BORDER_CELL = PARAGRAPH + "borderCell";
	
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_NIL = 0;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_BOTTOM = 1;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_TOP = 2;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_LEFT = 4;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_RIGHT = 8;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_DIAGONAL_UL_LR = 16;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_DIAGONAL_UR_LL = 32;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_TABLE_HORIZONTAL = 64;
	/** possible border settting */
	public static final int PARAGRAPH_BORDER_TABLE_VERTICAL = 128;
	
	/* section properties */
	/** Decimal number format */
	public static final int PGN_DECIMAL = 0; 
	/** Uppercase Roman Numeral */
	public static final int PGN_ROMAN_NUMERAL_UPPERCASE = 1;
	/** Lowercase Roman Numeral */
	public static final int PGN_ROMAN_NUMERAL_LOWERCASE = 2;
	/** Uppercase Letter */
	public static final int PGN_LETTER_UPPERCASE = 3;
	/** Lowercase Letter */
	public static final int PGN_LETTER_LOWERCASE = 4;
	/** Section Break None */
	public static final int SBK_NONE = 0;
	/** Section Break Column break */
	public static final int SBK_COLUMN = 1;
	/** Section Break Even page break */
	public static final int SBK_EVEN = 2;
	/** Section Break Odd page break */
	public static final int SBK_ODD = 3;
	/** Section Break Page break */
	public static final int SBK_PAGE = 4;
	
	public static final String SECTION_NUMBER_OF_COLUMNS =  SECTION + "numberOfColumns";
	public static final String SECTION_BREAK_TYPE = SECTION + "SectionBreakType";
	public static final String SECTION_PAGE_NUMBER_POSITION_X = SECTION + "pageNumberPositionX";
	public static final String SECTION_PAGE_NUMBER_POSITION_Y = SECTION + "pageNumberPositionY";
	public static final String SECTION_PAGE_NUMBER_FORMAT = SECTION + "pageNumberFormat";
	
	/* document properties */
	/** Portrait orientation */
	public static final String PAGE_PORTRAIT = "0";
	/** Landscape orientation */
	public static final String PAGE_LANDSCAPE = "1";
	
	public static final String DOCUMENT_PAGE_WIDTH_TWIPS = DOCUMENT + "pageWidthTwips";
	public static final String DOCUMENT_PAGE_HEIGHT_TWIPS = DOCUMENT + "pageHeightTwips";
	public static final String DOCUMENT_MARGIN_LEFT_TWIPS = DOCUMENT + "marginLeftTwips";
	public static final String DOCUMENT_MARGIN_TOP_TWIPS = DOCUMENT + "marginTopTwips";
	public static final String DOCUMENT_MARGIN_RIGHT_TWIPS = DOCUMENT + "marginRightTwips";
	public static final String DOCUMENT_MARGIN_BOTTOM_TWIPS = DOCUMENT + "marginBottomTwips";
	public static final String DOCUMENT_PAGE_NUMBER_START = DOCUMENT + "pageNumberStart";
	public static final String DOCUMENT_ENABLE_FACING_PAGES = DOCUMENT + "enableFacingPages";
	public static final String DOCUMENT_PAGE_ORIENTATION = DOCUMENT + "pageOrientation";
	public static final String DOCUMENT_DEFAULT_FONT_NUMER = DOCUMENT + "defaultFontNumber";
	
	/** Properties for this RtfProperty object */
	protected HashMap properties = new HashMap();
	
	private boolean modifiedCharacter = false; 
	private boolean modifiedParagraph = false; 
	private boolean modifiedSection = false; 
	private boolean modifiedDocument = false; 

	
	/** The <code>RtfPropertyListener</code>. */
    private ArrayList listeners = new ArrayList();
	/**
	 * Set all property objects to default values.
	 * @since 2.0.8
	 */
	public void setToDefault() {
		setToDefault(COLOR);
		setToDefault(CHARACTER);
		setToDefault(PARAGRAPH);
		setToDefault(SECTION);
		setToDefault(DOCUMENT);
	}
	/**
	 * Set individual property group to default values.
	 * @param propertyGroup <code>String</code> name of the property group to set to default.
	 * @since 2.0.8
	 */
	public void setToDefault(String propertyGroup) {
		if(COLOR.equals(propertyGroup)) {
			setProperty(COLOR_FG, new Color(0,0,0));
			setProperty(COLOR_BG, new Color(255,255,255));
			return;
		}
		if(CHARACTER.equals(propertyGroup)) {
			setProperty(CHARACTER_BOLD, 0);
			setProperty(CHARACTER_UNDERLINE, 0);
			setProperty(CHARACTER_ITALIC, 0);
			setProperty(CHARACTER_SIZE, 24);// 1/2 pt sizes
			setProperty(CHARACTER_FONT, 0);
			return;
		}
		if(PARAGRAPH.equals(propertyGroup)) {
			setProperty(PARAGRAPH_INDENT_LEFT, 0);
			setProperty(PARAGRAPH_INDENT_RIGHT, 0);
			setProperty(PARAGRAPH_INDENT_FIRST_LINE, 0);
			setProperty(PARAGRAPH_JUSTIFICATION, JUSTIFY_LEFT);
			setProperty(PARAGRAPH_BORDER, PARAGRAPH_BORDER_NIL);
			setProperty(PARAGRAPH_BORDER_CELL, PARAGRAPH_BORDER_NIL);
			return;
		}
		if(SECTION.equals(propertyGroup)) {
			setProperty(SECTION_NUMBER_OF_COLUMNS, 0);
			setProperty(SECTION_BREAK_TYPE, SBK_NONE);
			setProperty(SECTION_PAGE_NUMBER_POSITION_X, 0);
			setProperty(SECTION_PAGE_NUMBER_POSITION_Y, 0);
			setProperty(SECTION_PAGE_NUMBER_FORMAT, PGN_DECIMAL);
			return;
		}
		if(DOCUMENT.equals(propertyGroup)) {
			setProperty(DOCUMENT_PAGE_WIDTH_TWIPS, 12240);
			setProperty(DOCUMENT_PAGE_HEIGHT_TWIPS, 15480);
			setProperty(DOCUMENT_MARGIN_LEFT_TWIPS, 1800);
			setProperty(DOCUMENT_MARGIN_TOP_TWIPS, 1440);
			setProperty(DOCUMENT_MARGIN_RIGHT_TWIPS, 1800);
			setProperty(DOCUMENT_MARGIN_BOTTOM_TWIPS, 1440);
			setProperty(DOCUMENT_PAGE_NUMBER_START, 1);
			setProperty(DOCUMENT_ENABLE_FACING_PAGES, 1);
			setProperty(DOCUMENT_PAGE_ORIENTATION, PAGE_PORTRAIT);
			setProperty(DOCUMENT_DEFAULT_FONT_NUMER, 0);	
			return;
		}
	}


	/**
	 * Toggle the value of the property identified by the <code>RtfCtrlWordData.specialHandler</code> parameter.
	 * Toggle values are assumed to be integer values per the RTF spec with a value of 0=off or 1=on.
	 * 
	 * @param ctrlWordData The property name to set
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> is <code>null</code> or <i>blank</i>
	 */
	public boolean toggleProperty(RtfCtrlWordData ctrlWordData) { //String propertyName) {
		
		String propertyName = ctrlWordData.specialHandler;
		
		if(propertyName == null || propertyName.length() == 0) return false;
		
		Object propertyValue = getProperty(propertyName);
		if(propertyValue == null) {
			propertyValue = new Integer(RtfProperty.ON);
		} else {
			if(propertyValue instanceof Integer) {
				int value = ((Integer)propertyValue).intValue();
				if(value != 0) {
					removeProperty(propertyName);
				}
				return true;
			} else {
				if(propertyValue instanceof Long) {
					long value = ((Long)propertyValue).intValue();
					if(value != 0) {
						removeProperty(propertyName);
					}
					return true;
				}
			}
		}
		setProperty(propertyName, propertyValue);
		return true;
	}
	/**
	 * Set the value of the property identified by the parameter.
	 * 
	 * @param ctrlWordData The controlword with the name to set
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> or <code>propertyValue</code> is <code>null</code>
	 */
	public boolean setProperty(RtfCtrlWordData ctrlWordData) { //String propertyName, Object propertyValueNew) {
		String propertyName = ctrlWordData.specialHandler;
		Object propertyValueNew = ctrlWordData.param;
		// depending on the control word, set mulitiple or reset settings, etc.
		//if pard then reset settings
		//
		setProperty(propertyName, propertyValueNew);
		return true;
	}
	/**
	 * Set the value of the property identified by the parameter.
	 * 
	 * @param propertyName The property name to set
	 * @param propertyValueNew The object to set the property value to
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> or <code>propertyValue</code> is <code>null</code>
	 */
	private boolean setProperty(String propertyName, Object propertyValueNew) {
		if(propertyName == null || propertyValueNew == null) return false;
		
		Object propertyValueOld = getProperty(propertyName);
		if(propertyValueOld instanceof Integer && propertyValueNew instanceof Integer) {
			int valueOld = ((Integer)propertyValueOld).intValue();
			int valueNew = ((Integer)propertyValueNew).intValue();
			if (valueOld==valueNew) return true;
		} else {
			if(propertyValueOld instanceof Long && propertyValueNew instanceof Long) {
				long valueOld = ((Long)propertyValueOld).intValue();
				long valueNew = ((Long)propertyValueNew).intValue();
				if (valueOld==valueNew) return true;
			}
		}
		beforeChange(propertyName);
		properties.put(propertyName, propertyValueNew);
		afterChange(propertyName);
		setModified(propertyName, true);
		return true;
	}
	/**
	 * Set the value of the property identified by the parameter.
	 * 
	 * @param propertyName The property name to set
	 * @param propertyValueNew The object to set the property value to
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> is <code>null</code>
	 */
	private boolean setProperty(String propertyName, int propertyValueNew) {
		if(propertyName == null) return false;
		Object propertyValueOld = getProperty(propertyName);
		if(propertyValueOld instanceof Integer) {
			int valueOld = ((Integer)propertyValueOld).intValue();
			if (valueOld==propertyValueNew) return true;
		} 
		beforeChange(propertyName);
		properties.put(propertyName, new Integer(propertyValueNew));
		afterChange(propertyName);
		setModified(propertyName, true);
		return true;
	}
	/**
	 * Add the value of the property identified by the parameter.
	 * 
	 * @param propertyName The property name to set
	 * @param propertyValue The object to set the property value to
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> is <code>null</code>
	 */
	private boolean addToProperty(String propertyName, int propertyValue) {
		if(propertyName == null) return false;
		int value = ((Integer)properties.get(propertyName)).intValue();
		if((value | propertyValue) == value) return true;
		value |= propertyValue;
		beforeChange(propertyName);
		properties.put(propertyName, new Integer(value));
		afterChange(propertyName);
		setModified(propertyName, true);
		return true;
	}
	/**
	 * Set the value of the property identified by the parameter.
	 * 
	 * @param propertyName The property name to set
	 * @param propertyValueNew The object to set the property value to
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> is <code>null</code>
	 */
	private boolean setProperty(String propertyName, long propertyValueNew) {
		if(propertyName == null) return false;
		Object propertyValueOld = getProperty(propertyName);
		if(propertyValueOld instanceof Long) {
			long valueOld = ((Long)propertyValueOld).longValue();
			if (valueOld==propertyValueNew) return true;
		} 
		beforeChange(propertyName);
		properties.put(propertyName, new Long(propertyValueNew));
		afterChange(propertyName);
		setModified(propertyName, true);
		return true;
	}
	/**
	 * Add the value of the property identified by the parameter.
	 * 
	 * @param propertyName The property name to set
	 * @param propertyValue The object to set the property value to
	 * @return <code>true</code> for handled or <code>false</code> if <code>propertyName</code> is <code>null</code>
	 */
	private boolean addToProperty(String propertyName, long propertyValue) {
		if(propertyName == null) return false;
		long value = ((Long)properties.get(propertyName)).longValue();
		if((value | propertyValue) == value) return true;
		value |= propertyValue;
		beforeChange(propertyName);
		properties.put(propertyName, new Long(value));
		afterChange(propertyName);
		setModified(propertyName, true);
		return true;
	}
	private boolean removeProperty(String propertyName) {
		if(propertyName == null) return false;
		if(properties.containsKey(propertyName)) {
			beforeChange(propertyName);
			properties.remove(propertyName);
			afterChange(propertyName);
			setModified(propertyName, true);
		}
		return true;
	}
	/**
	 * Get the value of the property identified by the parameter.
	 * 
	 * @param propertyName String containing the property name to get
	 * @return Property Object requested or null if not found in map.
	 */
	public Object getProperty(String propertyName) {
		return properties.get(propertyName);
	}
	/**
	 * Get a group of properties.
	 * 
	 * @param propertyGroup The group name to obtain.
	 * @return Properties object with requested values.
	 */
	public HashMap getProperties(String propertyGroup) {
		HashMap props = new HashMap();
		if(!properties.isEmpty()) {
			//properties.get
			Iterator it = properties.keySet().iterator();
			while(it.hasNext()) {
				String key = (String)it.next();
				if(key.startsWith(propertyGroup)) {
					props.put(key, this.properties.get(key));
				}
			}
		}
		return props;
	}
	
	/**
	 * @return the modified
	 */
	public boolean isModified() {
		return modifiedCharacter || modifiedParagraph || modifiedSection || modifiedDocument;
	}
	/**
	 * @param propertyName the propertyName that is modified
	 * @param modified the modified to set
	 */
	public void setModified(String propertyName, boolean modified) {
		if(propertyName.startsWith(CHARACTER)) {
			this.setModifiedCharacter(modified);
		} else {
			if(propertyName.startsWith(PARAGRAPH)) {
				this.setModifiedParagraph(modified);
			} else {
				if(propertyName.startsWith(SECTION)) {
					this.setModifiedSection(modified);
				} else {
					if(propertyName.startsWith(DOCUMENT)) {
						this.setModifiedDocument(modified);
					}
				}
			}
		}
	}
	/**
	 * @return the modifiedCharacter
	 */
	public boolean isModifiedCharacter() {
		return modifiedCharacter;
	}
	/**
	 * @param modifiedCharacter the modifiedCharacter to set
	 */
	public void setModifiedCharacter(boolean modifiedCharacter) {
		this.modifiedCharacter = modifiedCharacter;
	}
	/**
	 * @return the modifiedParagraph
	 */
	public boolean isModifiedParagraph() {
		return modifiedParagraph;
	}
	/**
	 * @param modifiedParagraph the modifiedParagraph to set
	 */
	public void setModifiedParagraph(boolean modifiedParagraph) {
		this.modifiedParagraph = modifiedParagraph;
	}
	/**
	 * @return the modifiedSection
	 */
	public boolean isModifiedSection() {
		return modifiedSection;
	}
	/**
	 * @param modifiedSection the modifiedSection to set
	 */
	public void setModifiedSection(boolean modifiedSection) {
		this.modifiedSection = modifiedSection;
	}
	/**
	 * @return the modifiedDocument
	 */
	public boolean isModifiedDocument() {
		return modifiedDocument;
	}
	/**
	 * @param modifiedDocument the modifiedDocument to set
	 */
	public void setModifiedDocument(boolean modifiedDocument) {
		this.modifiedDocument = modifiedDocument;
	}
	
	/**
	 * Adds a <CODE>RtfPropertyListener</CODE> to the <CODE>RtfProperty</CODE>.
	 *
	 * @param listener
	 *            the new RtfPropertyListener.
	 */
	public void addRtfPropertyListener(RtfPropertyListener listener) {
		listeners.add(listener);
	}
	/**
	 * Removes a <CODE>RtfPropertyListener</CODE> from the <CODE>RtfProperty</CODE>.
	 *
	 * @param listener
	 *            the new RtfPropertyListener.
	 */
	public void removeRtfPropertyListener(RtfPropertyListener listener) {
		listeners.remove(listener);
	}
	
	public void beforeChange(String propertyName) {
		// call listener for all
		RtfPropertyListener listener;
		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
            listener = (RtfPropertyListener) iterator.next();
            listener.beforePropertyChange(propertyName);
        }
		
		if(propertyName.startsWith(CHARACTER)) {
			// call listener for character chane
		} else {
			if(propertyName.startsWith(PARAGRAPH)) {
				// call listener for paragraph change
			} else {
				if(propertyName.startsWith(SECTION)) {
					// call listener for section change
				} else {
					if(propertyName.startsWith(DOCUMENT)) {
						// call listener for document change
					}
				}
			}
		}
	}
	
	public void afterChange(String propertyName) {
		// call listener for all
		RtfPropertyListener listener;
		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
            listener = (RtfPropertyListener) iterator.next();
            listener.afterPropertyChange(propertyName);
        }

		if(propertyName.startsWith(CHARACTER)) {
			// call listener for character chane
		} else {
			if(propertyName.startsWith(PARAGRAPH)) {
				// call listener for paragraph change
			} else {
				if(propertyName.startsWith(SECTION)) {
					// call listener for section change
				} else {
					if(propertyName.startsWith(DOCUMENT)) {
						// call listener for document change
					}
				}
			}
		}
	}
}
