/* XSSFPivotField.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		Apr 25, 2012 6:44:57 PM , Created by Sam
}}IS_NOTE

Copyright (C) 2012 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
}}IS_RIGHT
*/
package org.zkoss.poi.xssf.usermodel;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTItem;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTItems;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTPivotField;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STAxis;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STFieldSortType;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STItemType;
import org.zkoss.poi.ss.usermodel.Calculation;
import org.zkoss.poi.ss.usermodel.PivotCache.CacheField;
import org.zkoss.poi.ss.usermodel.PivotField;
import org.zkoss.poi.ss.usermodel.PivotTable;

public class XSSFPivotField implements PivotField, Serializable {
	private static final long serialVersionUID = -2333966183637773334L;
	
	private CTPivotField _pivotField;
	private CacheField _cacheField;
	private PivotTable _pivotTable;
	
	/*package*/XSSFPivotField(CTPivotField pivotField, CacheField cacheField, PivotTable pivotTable) {
		_pivotField = pivotField;
		_cacheField = cacheField;
		_pivotTable = pivotTable;
	}
	
	private void checkLastItemIsNotData(CTItems items) {
		List<CTItem> itemList = items.getItemList();
		if (itemList.get(itemList.size() - 1).getT() == STItemType.DATA) {
			throw new IllegalArgumentException("The last item shall not be data type");
		}
	}
	
	public void setItems(List<Object> items) {
		
		CTItems ctItems = _pivotField.getItems();
		if (ctItems != null) {
			_pivotField.unsetItems();
		}
		ctItems = _pivotField.addNewItems();
		
		IndexMapper indexMapper = new IndexMapper(_cacheField.getSharedItems());
		for (Object i : items) {
			if (i instanceof Item.Type) {
				Item.Type t = (Item.Type)i;
				ctItems.addNewItem().setT(XSSFItem.getCTItemType(t));
			} else {
				int idx = indexMapper.getIndex(i);
				if (idx < 0) {
					throw new IllegalArgumentException("Expecting " + i + ", but not found in SharedItem");
				}
				ctItems.addNewItem().setX(idx);
			}
		}
		
		if (_pivotField.getDefaultSubtotal()) {
			checkLastItemIsNotData(ctItems);
		}
		ctItems.setCount(ctItems.getItemList().size());
	}
	
	
	public List<Item> getItems() {
		CTItems items = _pivotField.getItems();
		if (items == null) {
			return Collections.emptyList();
		}
		
		List<Object> values = _cacheField.getSharedItems();
		ArrayList<Item> list = new ArrayList<Item>();
		for (CTItem item : items.getItemList()) {
			STItemType.Enum type = item.getT();
			
			if (type == STItemType.DATA) {
				list.add(new XSSFItem(item, values.get((int)item.getX())));
			} else if (type == STItemType.DEFAULT) {
				//assume DEFAULT means Grand
				list.add(new XSSFItem(item, _pivotTable.getGrandTotalCaption()));
			} else {
				list.add(new XSSFItem(item, null));
			}
		}
		return list;
	}
	
	
	public FieldGroup getFieldGroup() {
		int index = _cacheField.getFieldGroup();
		//the first PivotField won't be group
		//the last PivotField won't belong to another group
		if (index >= 0) {
			return new XSSFFieldGroup(this);
		}
		return null;
	}
	
	
	public void setName(String name) {
		_cacheField.setName(name);
	}

	
	public String getName() {
		return _cacheField.getName();
	}

	
	public void setType(Type type) {
		switch (type) {
		case ROW:
			if (_pivotField.getDataField())
				_pivotField.unsetDataField();
			_pivotField.setAxis(STAxis.AXIS_ROW);
			break;
		case COLUMN:
			if (_pivotField.getDataField())
				_pivotField.unsetDataField();
			_pivotField.setAxis(STAxis.AXIS_COL);
			break;
		case DATA:
			if (_pivotField.getAxis() != null) {
				_pivotField.unsetAxis();
			}
			_pivotField.setDataField(true);
			break;
		}
	}
	
	/*package*/ CTPivotField getCTPivotField() {
		return _pivotField;
	}
	
	
	public Type getType() {
		if (_pivotField.getDataField()) {
			return PivotField.Type.DATA;
		}
		STAxis.Enum type =_pivotField.getAxis();
		if (type == null) {
			return null;
		}
		if (type == STAxis.AXIS_COL) {
			return PivotField.Type.COLUMN;
		} else if (type == STAxis.AXIS_ROW) {
			return PivotField.Type.ROW;
		}
		return null;
	}
	
	
	public void setDefaultSubtotal(boolean defaultSubtotal) {
		_pivotField.setDefaultSubtotal(defaultSubtotal);
	}
	
	
	public boolean getDefaultSubtotal() {
		return _pivotField.getDefaultSubtotal();
	}
	
	
	public void setSubtotals(Set<Calculation> subtotals) {
		if (subtotals.contains(Calculation.SUM)) {
			_pivotField.setSumSubtotal(true);
		} else if (_pivotField.getSumSubtotal()) {
			_pivotField.unsetSumSubtotal();
		}
		
		if (subtotals.contains(Calculation.COUNT)) {
			_pivotField.setCountASubtotal(true);
		} else if (_pivotField.getCountASubtotal()) {
			_pivotField.unsetCountASubtotal();
		}
		
		if (subtotals.contains(Calculation.AVERAGE)) {
			_pivotField.setAvgSubtotal(true);
		} else if (_pivotField.getAvgSubtotal()) {
			_pivotField.unsetAvgSubtotal();
		}
		
		if (subtotals.contains(Calculation.MAX)) {
			_pivotField.setMaxSubtotal(true);
		} else if (_pivotField.getMaxSubtotal()) {
			_pivotField.unsetMaxSubtotal();
		}
		
		if (subtotals.contains(Calculation.MIN)) {
			_pivotField.setMinSubtotal(true);
		} else if (_pivotField.getMinSubtotal()) {
			_pivotField.unsetMinSubtotal();
		}
		
		if (subtotals.contains(Calculation.PRODUCT)) {
			_pivotField.setProductSubtotal(true);
		} else if (_pivotField.getProductSubtotal()) {
			_pivotField.setProductSubtotal(true);
		}
		
		if (subtotals.contains(Calculation.COUNT_NUMS)) {
			_pivotField.setCountSubtotal(true);
		} else if (_pivotField.getCountSubtotal()) {
			_pivotField.unsetCountSubtotal();
		}
		
		if (subtotals.contains(Calculation.STD_DEV)) {
			_pivotField.setStdDevSubtotal(true);
		} else if (_pivotField.getStdDevSubtotal()) {
			_pivotField.unsetStdDevSubtotal();
		}
		
		if (subtotals.contains(Calculation.STD_DEV_P)) {
			_pivotField.setStdDevPSubtotal(true);
		} else if (_pivotField.getStdDevPSubtotal()) {
			_pivotField.unsetStdDevPSubtotal();
		}
	}
	
	
	public Set<Calculation> getSubtotals() {
		LinkedHashSet<Calculation> set = new LinkedHashSet<Calculation>();
		
		if (_pivotField.getSumSubtotal()) {
			set.add(Calculation.SUM);
		}
		if (_pivotField.getCountASubtotal()) {
			set.add(Calculation.COUNT);
		}
		if (_pivotField.getAvgSubtotal()) {
			set.add(Calculation.AVERAGE);
		}
		if (_pivotField.getMaxSubtotal()) {
			set.add(Calculation.MAX);
		}
		if (_pivotField.getMinSubtotal()) {
			set.add(Calculation.MIN);
		}
		if (_pivotField.getProductSubtotal()) {
			set.add(Calculation.PRODUCT);
		}
		if (_pivotField.getCountSubtotal()) {
			set.add(Calculation.COUNT_NUMS);
		}
		if (_pivotField.getStdDevSubtotal()) {
			set.add(Calculation.STD_DEV);
		}
		if (_pivotField.getStdDevPSubtotal()) {
			set.add(Calculation.STD_DEV_P);
		}
		if (_pivotField.getVarSubtotal()) {
			set.add(Calculation.VARIANCE);
		}
		if (_pivotField.getVarPSubtotal()) {
			set.add(Calculation.VARIANCE_P);
		}
		return set;
	}

	
	public void setSortType(SortType type) {
		switch (type) {
		case ASCENDING:
			_pivotField.setSortType(STFieldSortType.ASCENDING);
			break;
		case DESCENDING:
			_pivotField.setSortType(STFieldSortType.DESCENDING);
			break;
		case MANUAL:
			_pivotField.setSortType(STFieldSortType.MANUAL);
			break;
		}
	}

	
	public SortType getSortType() {
		STFieldSortType.Enum type = _pivotField.getSortType();
		if (type == STFieldSortType.MANUAL) {
			return SortType.MANUAL;
		} else if (type == STFieldSortType.ASCENDING) {
			return SortType.ASCENDING;
		} else if (type == STFieldSortType.DESCENDING) {
			return SortType.DESCENDING;
		}
		return null;
	}
	
	public boolean getDatabaseField() {
		return _cacheField.getDatabaseField();
	}
	
	@Override
	public void setOutline(boolean outline) {
		_pivotField.setOutline(outline);
	}

	@Override
	public boolean getOutline() {
		return _pivotField.getOutline();
	}
	
	public static class XSSFItem implements Item {
		
		private final CTItem _item;
		private final Object _value;
		
		/*package*/ XSSFItem (CTItem item, Object value) {
			_item = item;
			_value = value;
		}
		
		
		public void setHide(boolean hide) {
			_item.setH(hide);
		}
		
		
		public boolean getHide() {
			return _item.getH();
		}
		
		
		public Object getValue() {
			return _value;
		}
		
		
		public void setShowDetail(boolean showDetail) {
			_item.setSd(showDetail);
		}

		
		public boolean getShowDetail() {
			return _item.getSd();
		}

		
		public void setType(Type type) {
			if (map == null) {
				initItemTypeMap();
			}
			_item.setT(map2.get(type));
		}
		
		
		public Type getType() {
			if (map == null) {
				initItemTypeMap();
			}
			return map.get(_item.getT());
		}
		
		
		public int hashCode() {
			return _value.hashCode();
		}

		
		public boolean equals(Object obj) {
			if (obj instanceof XSSFItem) {
				XSSFItem that = (XSSFItem)obj;
				return this._value.equals(that._value);
			}
			return false;
		}
		
		/*package*/ static STItemType.Enum getCTItemType(Item.Type type) {
			if (map2 == null) {
				initItemTypeMap();
			}
			return map2.get(type);
		}
		
		//TODO: bi-map, 1 <--> 1 map
		private static Map<STItemType.Enum, Item.Type> map;
		private static Map<Item.Type, STItemType.Enum> map2;
		private static void initItemTypeMap() {
			map = new HashMap<STItemType.Enum, Item.Type>();
			map.put(STItemType.AVG, Item.Type.AVERAGE);
			map.put(STItemType.BLANK, Item.Type.BLANK);
			map.put(STItemType.COUNT, Item.Type.COUNT_NUMS);
			map.put(STItemType.COUNT_A, Item.Type.COUNT);
			map.put(STItemType.DATA, Item.Type.DATA);
			map.put(STItemType.DEFAULT, Item.Type.DEFAULT);
			map.put(STItemType.GRAND, Item.Type.GRAND);
			map.put(STItemType.MAX, Item.Type.MAX);
			map.put(STItemType.MIN, Item.Type.MIN);
			map.put(STItemType.PRODUCT, Item.Type.PRODUCT);
			map.put(STItemType.STD_DEV, Item.Type.STD_DEV);
			map.put(STItemType.STD_DEV_P, Item.Type.STD_DEV_P);
			map.put(STItemType.SUM, Item.Type.SUM);
			map.put(STItemType.VAR, Item.Type.VARIANCE);
			map.put(STItemType.VAR_P, Item.Type.VARIANCE_P);
			
			map2 = new HashMap<Item.Type, STItemType.Enum>();
			map2.put(Item.Type.AVERAGE, STItemType.AVG);
			map2.put(Item.Type.BLANK, STItemType.BLANK);
			map2.put(Item.Type.COUNT_NUMS, STItemType.COUNT);
			map2.put(Item.Type.COUNT, STItemType.COUNT_A);
			map2.put(Item.Type.DATA, STItemType.DATA);
			map2.put(Item.Type.DEFAULT, STItemType.DEFAULT);
			map2.put(Item.Type.GRAND, STItemType.GRAND);
			map2.put(Item.Type.MAX, STItemType.MAX);
			map2.put(Item.Type.MIN, STItemType.MIN);
			map2.put(Item.Type.PRODUCT, STItemType.PRODUCT);
			map2.put(Item.Type.STD_DEV, STItemType.STD_DEV);
			map2.put(Item.Type.STD_DEV_P, STItemType.STD_DEV_P);
			map2.put(Item.Type.SUM, STItemType.SUM);
			map2.put(Item.Type.VARIANCE, STItemType.VAR);
			map2.put(Item.Type.VARIANCE_P, STItemType.VAR_P);
		}
	}
	
	private class XSSFFieldGroup implements FieldGroup {

		private final XSSFPivotField _fieldGroup;
		private final CacheField _cacheField;
		private final PivotTable _pivotTable;
		
		XSSFFieldGroup(XSSFPivotField fieldGroup) {
			_fieldGroup = fieldGroup;
			_cacheField = fieldGroup._cacheField;
			_pivotTable = fieldGroup._pivotTable;
		}
		
		private int getGroupIndex() {
			//assume Group locate on last
			List<Object> items = getItems();
			if (items != null) {
				return items.size() - 1;
			}
			return -1;
		}
		
		
		public Item getItem() {
			
			int groupIndex = getGroupIndex();
			if (groupIndex >= 0) {
				CTItems items = _pivotField.getItems();
				List<Object> values = _cacheField.getSharedItems();
				for (CTItem item : items.getItemList()) {
					int index = (int)item.getX();
					if (index == groupIndex) {
						return new XSSFItem(item, values.get(index));
					}
				}	
			}
			
			return null;
		}
		
		
		public List<Object> getItems() {
			return _cacheField.getSharedItems();
		}

		public PivotField getBase() {
			return _pivotTable.getPivotFields().get(_cacheField.getGroupBase());
		}
		
		
		public Set<Object> getGroup() {
			XSSFPivotField base = (XSSFPivotField) getBase();
			Iterator<Object> iterator = base._cacheField.getSharedItems().iterator();
			
			int groupIndex = getGroupIndex();
			
			LinkedHashSet<Object> set = new LinkedHashSet<Object>();
			List<Integer> discrete = _cacheField.getGroupDiscrete();
			for (int i = 0; i < discrete.size(); i++) {
				Object str = iterator.next();
				if (discrete.get(i) == groupIndex) {
					set.add(str);
				}
			} 
			return set;
		}
		
		
		public FieldGroup getParent() {
			int index = _cacheField.getFieldGroup();
			if (index > 0) {
				return new XSSFFieldGroup((XSSFPivotField)_pivotTable.getPivotFields().get(index));
			}
			return null;
		}
	}
	
	private class IndexMapper {
		HashMap<Object, Integer> _map = new HashMap<Object, Integer>();
		IndexMapper(List<Object> src) {
			for (int i = 0; i < src.size(); i++) {
				Object obj = src.get(i);
				if (obj instanceof Calendar) {
					_map.put(((Calendar)obj).getTime(), i);
				} else {
					_map.put(obj, i);
				}
			}
		}
		
		int getIndex(Object obj) {
			Integer idx = _map.get(obj);
			if (idx != null) {
				return idx;
			}
			
			return -1;
		}
	}
}