package cn.net.connor.std.processedit.meopviewfx.ui;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import com.teamcenter.rac.aif.kernel.AIFComponentContext;
import com.teamcenter.rac.cme.framework.util.ExcludeOccurrenceTypeUtils;
import com.teamcenter.rac.common.create.DisplayTypeInfo;
import com.teamcenter.rac.kernel.ListOfValuesInfo;
import com.teamcenter.rac.kernel.TCComponent;
import com.teamcenter.rac.kernel.TCComponentBOMLine;
import com.teamcenter.rac.kernel.TCComponentBOMLineType;
import com.teamcenter.rac.kernel.TCComponentItem;
import com.teamcenter.rac.kernel.TCComponentItemRevision;
import com.teamcenter.rac.kernel.TCComponentItemRevisionType;
import com.teamcenter.rac.kernel.TCComponentItemType;
import com.teamcenter.rac.kernel.TCComponentListOfValues;
import com.teamcenter.rac.kernel.TCComponentType;
import com.teamcenter.rac.kernel.TCException;
import com.teamcenter.rac.kernel.TCPropertyDescriptor;
import com.teamcenter.rac.kernel.TCSession;
import com.teamcenter.rac.stylesheet.gdt.GdtDialog;
import com.teamcenter.rac.util.Registry;
import com.teamcenter.rac.util.UIUtilities;
import com.teamcenter.soa.client.model.LovValue;

import cn.net.connor.std.aifrcp.util.KUtil;
import cn.net.connor.std.fx.KFXEditableStringAreaTableCell;
import cn.net.connor.std.fx.KFXTextLocator;
import cn.net.connor.std.gdtex.KGdtEditor;
import cn.net.connor.std.processedit.meopviewfx.bean.KTableRow;
import cn.net.connor.std.processedit.meopviewfx.bean.PropConfig;
import cn.net.connor.std.processedit.meopviewfx.reorder.ReorderDialog;
import cn.net.connor.std.processedit.searchbysql.SearchBySqlDialog;
import cn.net.connor.std.processedit.util.KChineseSplitter;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Control;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.util.Callback;

public class TableLoader extends KFXTextLocator {

	protected List<PropConfig> propConfigs = new ArrayList<PropConfig>();
	protected TableView<KTableRow> table;
	private TCComponentItemType itemType;
	private int precise = 0;
	private TCComponentItemRevisionType revType;
	private TCComponentType itemMasterType;
	private TCComponentType revMasterType;
	// protected List<KTableRow> newRows = new ArrayList<KTableRow>();
	protected List<KTableRow> removeRows = new ArrayList<KTableRow>();
	protected Map<PropConfig, Integer> sequenceConfigMap = new HashMap<>();
	protected Map<PropConfig, Integer> sequenceConfigMapReorder = new HashMap<>();
	protected String[] gdtPropNames;
	protected TableColumn<KTableRow, String> nameCol;
	protected TCSession session;
	public static final String PREF_GDT_PROP = "GDT_properties";
	protected static final Registry REG = Registry.getRegistry(TableLoader.class);
	protected Map<TableColumn<KTableRow, String>, PropConfig> htmlColMap = new HashMap<>();
	protected TCComponentBOMLine targetLine;
	protected int defaultSequenceStep = 10;
	protected ObservableList<String> occTypeOptions = FXCollections.observableArrayList();
	protected Map<String, String> occ_real_display_map = new HashMap<>();
	protected Map<String, String> occ_display_real_map = new HashMap<>();

	protected TableLoader(TCSession session) {
		this.session = session;
	}

	public boolean load(TableView<KTableRow> table, String prefName) throws Exception {
		this.table = table;
		clearLocatorData();
		Platform.runLater(() -> {
			table.getColumns().clear();
			table.getItems().clear();
		});
		propConfigs.clear();
		sequenceConfigMap.clear();
		sequenceConfigMapReorder.clear();
		this.htmlColMap.clear();
		if (KUtil.isEmpty(prefName)) {
			return false;
		}
		System.out.println("Load table from pref: " + prefName);
		String[] prefVals = KUtil.getPrefVals(session, prefName);
		int len = KUtil.getLen(prefVals);
		if (len <= 1) {
			return false;
		}
		String itemTypeName = prefVals[0];
		System.out.println(">> Item Type: " + itemTypeName);
		TCComponentBOMLineType boplineType = null;
		itemType = null;
		revType = null;
		precise = 0;
		itemMasterType = null;
		revMasterType = null;
		boplineType = (TCComponentBOMLineType) session.getTypeComponent("BOMLine");
		if (boplineType == null) {
			throw new Exception("Type not found: BOPLine");
		}
		if (!KTableRow.BOMLINE.equalsIgnoreCase(itemTypeName)) {
			itemType = (TCComponentItemType) session.getTypeComponent(itemTypeName);
			if (itemType == null) {
				throw new Exception(String.format(REG.getString("typenotfound.ERROR1"), itemTypeName));
			}
			revType = itemType.getItemRevisionType();
			itemMasterType = session.getTypeComponent(itemTypeName + "Master");
			if (itemMasterType == null) {
				itemMasterType = session.getTypeComponent(itemTypeName + " Master");
			}
			if (itemMasterType == null) {
				throw new Exception(String.format(REG.getString("typenotfound.ERROR1"), itemTypeName + "Master"));
			}
			revMasterType = session.getTypeComponent(revType.getTypeName() + "Master");
			if (revMasterType == null) {
				revMasterType = session.getTypeComponent(revType.getTypeName() + " Master");
			}
			if (revMasterType == null) {
				throw new Exception(String.format(REG.getString("typenotfound.ERROR1"), revType.getTypeName() + "Master"));
			}
		}
		for (int i = 1; i < len; i++) {
			String propName = prefVals[i];
			System.out.println("Load Config: " + propName);
			if (KUtil.isEmpty(propName)) {
				continue;
			}
			if("precise=true".equalsIgnoreCase(propName)) {
				precise = 1;
				continue;
			}
			if("precise=false".equalsIgnoreCase(propName)) {
				precise = -1;
				continue;
			}
			if (propName.toLowerCase().startsWith("sequence_step=")) {
				String numStr = propName.substring("sequence_step=".length()).trim();
				if (!KUtil.isEmpty(numStr)) {
					try {
						this.defaultSequenceStep = Integer.parseInt(numStr);
						System.out.println("Default Sequence Step: " + this.defaultSequenceStep);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				continue;
			}
			String propLoc = null;
			String filterValue = null;
			int width = 0;
			int height = 0;
			int maxCharacter = 0;
			if ('-' == propName.charAt(0)) {
				width = -1;
				propName = propName.substring(1);
			} else {
				while (!KUtil.isEmpty(propName) && Character.isDigit(propName.charAt(0))) {
					width = width * 10 + (propName.charAt(0) - '0');
					propName = propName.substring(1);
				}
				if (propName.startsWith("x")) {
					propName = propName.substring(1);
					while (!KUtil.isEmpty(propName) && Character.isDigit(propName.charAt(0))) {
						height = height * 10 + (propName.charAt(0) - '0');
						propName = propName.substring(1);
					}
				}
				if (propName.startsWith("$")) {
					propName = propName.substring(1);
					while (!KUtil.isEmpty(propName) && Character.isDigit(propName.charAt(0))) {
						maxCharacter = maxCharacter * 10 + (propName.charAt(0) - '0');
						propName = propName.substring(1);
					}
				}
			}
			int ind = propName.indexOf('.');
			if (ind > 0) {
				propLoc = propName.substring(0, ind);
				propName = propName.substring(ind + 1);
			}
			ind = propName.indexOf('=');
			if (ind > 0) {
				filterValue = propName.substring(ind + 1);
				propName = propName.substring(0, ind);
			}
			// System.out.println(propName + "|" + filterValue);
			TCComponentType type = null;
			if (KUtil.isEmpty(propLoc)) {
				type = boplineType;
				propLoc = KTableRow.BOMLINE;
			} else {
				switch (propLoc.toUpperCase()) {
				case KTableRow.BOMLINE:
					type = boplineType;
					break;
				case KTableRow.ITEM:
					type = itemType;
					break;
				case KTableRow.REV:
					type = revType;
					break;
				case KTableRow.ITEMMASTER:
					type = itemMasterType;
					break;
				case KTableRow.REVMASTER:
					type = revMasterType;
					break;
				default:
					System.out.println(">> Prop location error: " + propLoc);
					break;
				}
			}
			if (type != null) {
				boolean isSeq = propName.startsWith("#");
				boolean reorder = false;
				if (isSeq) {
					propName = propName.substring(1);
				}
				reorder = propName.startsWith("#");
				if (reorder) {
					propName = propName.substring(1);
				}
				TCPropertyDescriptor propDesc = type.getPropDesc(propName);
				if (propDesc == null) {
					throw new Exception(String.format(REG.getString("propnotdefinedintype.ERROR2"), propName, type.getDisplayTypeName()));
				}
				PropConfig propConfig;
				this.propConfigs.add(propConfig = new PropConfig(propLoc, propDesc, filterValue, width, height, maxCharacter));
				if (reorder) {
					this.sequenceConfigMapReorder.put(propConfig, 0);
				} else if (isSeq) {
					this.sequenceConfigMap.put(propConfig, 0);
				}
			}
		}
		System.out.println("Load complete");
		System.out.println("Precise: "+precise);
		int size = this.propConfigs.size();
		if (size == 0) {
			return false;
		}
		this.gdtPropNames = KUtil.getPrefVals(session, PREF_GDT_PROP);
		for (int i = 0; i < size; i++) {
			PropConfig config = propConfigs.get(i);
			// if("bl_sequence_no".equals(config.propName)) {
			// this.sequenceConfig1 = config;
			// }else if(KTableRow.REVMASTER.equalsIgnoreCase(config.propLoc) &&
			// "ac5_WorkstepNumber".equals(config.propName)) { // TODO ac5_WorkstepNumber
			// this.sequenceConfig2 = config;
			// }
			addTableCol(table, config);
		}
		// Platform.runLater(()->{
		//
		// KTableRow row = new KTableRow();
		// for (PropConfig string : propConfigs) {
		// row.stringProps.put(string, new
		// SimpleStringProperty(string.lov!=null?"":string.propName));
		// }
		// table.getItems().add(row);
		// });
		// table.setOnMouseClicked(e->{
		// if(e.getButton()!=MouseButton.SECONDARY || e.getClickCount()!=1) {
		// return;
		// }
		// KTableRow row = table.getSelectionModel().getSelectedItem();
		// if(row!=null) {
		// row.switchWebView();
		// table.refresh();
		// }
		// });
		return true;
	}

	public KTableRow getNewRow() {
		KTableRow row = new KTableRow(null, null, null, null, null);
		for (PropConfig propConfig : propConfigs) {
			row.stringProps.put(propConfig, new SimpleStringProperty(""));
		}
		return row;
	}

	public void loadTCData(TCComponentBOMLine targetLine, boolean useLatestRev) throws Exception {
		Platform.runLater(() -> {
			table.getItems().clear();
		});
		if (targetLine == null) {
			return;
		}
		int size = this.propConfigs.size();
		if (size == 0) {
			return;
		}
		removeRows.clear();
		this.targetLine = targetLine;
		// 加载事例类型
		{
			occTypeOptions.clear();
			occ_real_display_map.clear();
			occ_display_real_map.clear();
			TCComponentItemType occItemType = this.itemType;
			TCComponentItem childItem = occItemType == null ? null : new TCComponentItem() {
				@Override
				public TCComponentItemType getTypeComponent() {
					return occItemType;
				}

				@Override
				public TCSession getSession() {
					return session;
				}

				@Override
				public String[] getClassNameHierarchy() throws TCException {
					return occItemType.getClassNameHierarchy();
				}
			};
			Set<DisplayTypeInfo> set = ExcludeOccurrenceTypeUtils.getOccTypesFromPreference(Arrays.asList(new TCComponent[] { this.targetLine }),
					Arrays.asList(childItem == null ? new TCComponent[] {} : new TCComponent[] { childItem }));
			occTypeOptions.add("");
			if (set != null) {
				for (DisplayTypeInfo displayTypeInfo : set) {
					String realValue = displayTypeInfo.getTypeName();
					String disValue = displayTypeInfo.getDisplayTypeName();
					occTypeOptions.add(disValue);
					occ_real_display_map.put(realValue, disValue);
					occ_display_real_map.put(disValue, realValue);
				}
				occTypeOptions.sort(new Comparator<String>() {
					@Override
					public int compare(String o1, String o2) {
						return o1.compareTo(o2);
					}
				});
			}
			System.out.println("getOccTypesFromPreference: " + this.targetLine + "[" + occItemType + "] -> " + occ_real_display_map);
		}
		AIFComponentContext[] cc = targetLine.getChildren();
		System.out.println(Arrays.toString(cc));
		int len = KUtil.getLen(cc);
		for (int i = 0; i < len; i++) {
			TCComponentBOMLine bomine = (TCComponentBOMLine) cc[i].getComponent();
			// String seq = bomine.getStringProperty("bl_sequence_no");
			// if(!KUtil.isEmpty(seq)) {
			// try {
			// int seqNum = Integer.parseInt(seq);
			// seqNum = seqNum/10;
			// if(seqNum>maxSeqNo) {
			// maxSeqNo = seqNum;
			// }
			// } catch (Exception e) {
			// e.printStackTrace();
			// }
			// }
			TCComponentItem item = bomine.getItem();
			if (this.itemType != null && !this.itemType.getName().equals(item.getType())) {
				continue;
			}
			TCComponentItemRevision rev = useLatestRev ? item.getLatestItemRevision() : bomine.getItemRevision();
			TCComponent itemMaster = item.getRelatedComponent("IMAN_master_form");
			TCComponent revMaster = rev.getRelatedComponent("IMAN_master_form_rev");
			KTableRow row = new KTableRow(bomine, item, rev, itemMaster, revMaster);
			boolean checkOK = true;
			for (int j = 0; j < size; j++) {
				PropConfig config = propConfigs.get(j);
				if (!row.parseConfig(config)) {
					checkOK = false;
					break;
				}
			}
			if (checkOK) {
				for (PropConfig sequenceConfig : this.sequenceConfigMap.keySet()) {
					int maxSeqNo = this.sequenceConfigMap.get(sequenceConfig);
					String seq2 = row.getTCValue(sequenceConfig.propLoc, sequenceConfig.propName);
					if (!KUtil.isEmpty(seq2)) {
						try {
							int seqNum = Integer.parseInt(seq2);
							if (seqNum > maxSeqNo) {
								maxSeqNo = seqNum;
								this.sequenceConfigMap.put(sequenceConfig, maxSeqNo);
							}
						} catch (Exception e) {
						}
					}
				}
				for (PropConfig sequenceConfig : this.sequenceConfigMapReorder.keySet()) {
					int maxSeqNo = this.sequenceConfigMapReorder.get(sequenceConfig);
					String seq2 = row.getTCValue(sequenceConfig.propLoc, sequenceConfig.propName);
					if (!KUtil.isEmpty(seq2)) {
						try {
							int seqNum = Integer.parseInt(seq2);
							if (seqNum > maxSeqNo) {
								maxSeqNo = seqNum;
								this.sequenceConfigMapReorder.put(sequenceConfig, maxSeqNo);
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				Platform.runLater(() -> {
					table.getItems().add(row);
				});
			}
		}
		Platform.runLater(() -> {
			table.refresh();
		});
		System.out.println("Max Sequence: " + sequenceConfigMap);
		System.out.println("Max Sequence Reorder: " + sequenceConfigMapReorder);
	}

	public void saveTCData() throws Exception {
		System.out.println("Save: " + targetLine+" Precise: "+precise);
		if (targetLine == null) {
			return;
		}
		while (removeRows.size() > 0) {
			removeRows.get(0).removeLine();
			removeRows.remove(0);
		}
		KTableRow[] rows = table.getItems().toArray(new KTableRow[] {});
		int len = KUtil.getLen(rows);
		for (int i = 0; i < len; i++) {
			KTableRow row = rows[i];
			row.saveTCData(targetLine, itemType, precise, null);
		}
	}

	public void addRow() {
		KTableRow row = getNewRow();
		for (PropConfig sequenceConfig : this.sequenceConfigMapReorder.keySet()) {
			int step = guessSequenceStep(sequenceConfig);
			int maxSeqNo = this.sequenceConfigMapReorder.get(sequenceConfig);
			maxSeqNo += step;
			row.stringProps.get(sequenceConfig).setValue("" + maxSeqNo);
			this.sequenceConfigMapReorder.put(sequenceConfig, maxSeqNo);
		}
		for (PropConfig sequenceConfig : this.sequenceConfigMap.keySet()) {
			int step = guessSequenceStep(sequenceConfig);
			int maxSeqNo = this.sequenceConfigMap.get(sequenceConfig);
			maxSeqNo += step;
			row.stringProps.get(sequenceConfig).setValue("" + maxSeqNo);
			this.sequenceConfigMap.put(sequenceConfig, maxSeqNo);
		}
		table.getItems().add(row);
		table.getSelectionModel().select(row);
		table.scrollTo(row);
	}

	public int guessSequenceStep(PropConfig sequenceConfig) {
		int itemCnt = table.getItems().size();
//		// 查找编号初始化时可能存在其他类型子行，需要重新读取
//		if (itemCnt == 0 && sequenceConfig != null && "bl_sequence_no".equals(sequenceConfig.propName)) {
//			System.out.println("Init bl_sequence_no");
//			try {
//				int maxSeq = -1;
//				AIFComponentContext[] cc = this.targetLine.getChildren();
//				int len = KUtil.getLen(cc);
//				for (int i = 0; i < len; i++) {
//					InterfaceAIFComponent cLine = cc[i].getComponent();
//					String seq = cLine.getProperty("bl_sequence_no");
//					int seqNum = Integer.parseInt(seq);
//					if (seqNum > maxSeq) {
//						maxSeq = seqNum;
//					}
//				}
//				System.out.println("Max Sequence: " + maxSeq);
//				if (maxSeq > 0) {
//					return maxSeq + defaultSequenceStep;
//				}
//			} catch (Exception e) {
//				e.printStackTrace();
//			}
//		}
		if (itemCnt < 2 || sequenceConfig == null) {
			return defaultSequenceStep;
		}
		KTableRow item1 = table.getItems().get(0);
		KTableRow item2 = table.getItems().get(1);
		String item1Seq = item1.stringProps.get(sequenceConfig).getValueSafe();
		String item2Seq = item2.stringProps.get(sequenceConfig).getValueSafe();
		try {
			int n1 = Integer.parseInt(item1Seq);
			int n2 = Integer.parseInt(item2Seq);
			int res = Math.abs(n1 - n2);
			System.out.println("Sequence Step: " + res);
			return res;
		} catch (Exception e) {
			System.out.println("Error Numbers [" + item1Seq + "][" + item2Seq + "]");
		}
		return defaultSequenceStep;
	}

	public void removeRow() {
		int index = table.getSelectionModel().getSelectedIndex();
		KTableRow row = table.getSelectionModel().getSelectedItem();
		System.out.println("Remove: " + index);
		if (row == null) {
			return;
		}
		for (PropConfig sequenceConfig : this.sequenceConfigMapReorder.keySet()) {
			for (int i = table.getItems().size() - 1; i > index; i--) {
				KTableRow topItem = table.getItems().get(i - 1);
				KTableRow bottomItem = table.getItems().get(i);
				bottomItem.stringProps.get(sequenceConfig).setValue(topItem.stringProps.get(sequenceConfig).getValueSafe());
			}
		}
		table.getItems().remove(row);
		removeRows.add(row);
		for (PropConfig sequenceConfig : this.sequenceConfigMap.keySet()) {
			int maxSeqNo = 0;
			for (KTableRow row2 : table.getItems()) {
				String seq2 = row2.stringProps.get(sequenceConfig).getValueSafe();
				if (!KUtil.isEmpty(seq2)) {
					try {
						int seqNum = Integer.parseInt(seq2);
						if (seqNum > maxSeqNo) {
							maxSeqNo = seqNum;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
			sequenceConfigMap.put(sequenceConfig, maxSeqNo);
		}
		for (PropConfig sequenceConfig : this.sequenceConfigMapReorder.keySet()) {
			int maxSeqNo = 0;
			for (KTableRow row2 : table.getItems()) {
				String seq2 = row2.stringProps.get(sequenceConfig).getValueSafe();
				if (!KUtil.isEmpty(seq2)) {
					try {
						int seqNum = Integer.parseInt(seq2);
						if (seqNum > maxSeqNo) {
							maxSeqNo = seqNum;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
			sequenceConfigMapReorder.put(sequenceConfig, maxSeqNo);
		}
		System.out.println("Max Sequence: " + sequenceConfigMap);
		System.out.println("Max Sequence Reorder: " + sequenceConfigMapReorder);
	}

	public void moveUp() {
		int index = table.getSelectionModel().getSelectedIndex();
		System.out.println("Move UP: " + index);
		if (index <= 0) {
			return;
		}
		KTableRow item = table.getItems().get(index);
		KTableRow sideItem = table.getItems().get(index - 1);
		for (PropConfig sequenceConfig : this.sequenceConfigMapReorder.keySet()) {
			String itemSeq = item.stringProps.get(sequenceConfig).getValueSafe();
			String sideItemSeq = sideItem.stringProps.get(sequenceConfig).getValueSafe();
			item.stringProps.get(sequenceConfig).setValue(sideItemSeq);
			sideItem.stringProps.get(sequenceConfig).setValue(itemSeq);
		}
		table.getItems().remove(item);
		table.getItems().add(index - 1, item);
		table.getSelectionModel().clearAndSelect(index - 1);
	}

	public void moveDown() {
		int index = table.getSelectionModel().getSelectedIndex();
		System.out.println("Move Down: " + index);
		if (index >= table.getItems().size() - 1) {
			return;
		}
		KTableRow item = table.getItems().get(index);
		KTableRow sideItem = table.getItems().get(index + 1);
		for (PropConfig sequenceConfig : this.sequenceConfigMapReorder.keySet()) {
			String itemSeq = item.stringProps.get(sequenceConfig).getValueSafe();
			String sideItemSeq = sideItem.stringProps.get(sequenceConfig).getValueSafe();
			item.stringProps.get(sequenceConfig).setValue(sideItemSeq);
			sideItem.stringProps.get(sequenceConfig).setValue(itemSeq);
		}
		table.getItems().remove(item);
		table.getItems().add(index + 1, item);
		table.getSelectionModel().clearAndSelect(index + 1);
	}

	protected void addTableCol(TableView<KTableRow> table, PropConfig config) throws Exception {
		String header = config.propDisplayName;
		TCComponentListOfValues lov = config.lov;
		// gdt properties
		if (!KUtil.isEmpty(config.propName) && KUtil.inArray(config.propName, gdtPropNames) >= 0) {
			TableColumn<KTableRow, String> c = new TableColumn<KTableRow, String>(header);
			c.setCellValueFactory(p -> p.getValue().stringProps.get(config));
			c.setCellFactory(new Callback<TableColumn<KTableRow, String>, TableCell<KTableRow, String>>() {
				@Override
				public TableCell<KTableRow, String> call(TableColumn<KTableRow, String> param) {
					return new KFXEditableStringAreaHtmlTableCell<KTableRow, String>(config, TableLoader.this);
				}
			});
			if (config.width > 0) {
				c.setPrefWidth(config.width);
			} else if (config.width < 0) {
				c.setMinWidth(0);
				c.setMaxWidth(0);
				c.setPrefWidth(0);
			}
			Platform.runLater(() -> {
				c.setSortable(false);
				c.setEditable(config.canEdit);
				table.getColumns().add(c);
			});
			htmlColMap.put(c, config);
			return;
		}
		Map<String, String> real_display_map = new HashMap<>();
		Map<String, String> display_real_map = new HashMap<>();
		ObservableList<String> options = FXCollections.observableArrayList();
		if (lov != null) {
			String[] disVals = lov.getListOfValues().getLOVDisplayValues();
			options.add("");
			for (String string : disVals) {
				options.add(string);
			}
			ListOfValuesInfo info = lov.getListOfValues();
			List<LovValue> v = info == null ? null : info.getValues();
			int size = v == null ? 0 : v.size();
			for (int i = 0; i < size; i++) {
				LovValue vv = v.get(i);
				String realValue = vv.getValue() == null ? "" : vv.getValue().toString();
				String disValue = vv.getDisplayValue();
				real_display_map.put(realValue, disValue);
				display_real_map.put(disValue, realValue);
			}
		}
		if (options.size() == 0 && !"bl_occ_type".equals(config.propName)) {
			TableColumn<KTableRow, String> c = new TableColumn<KTableRow, String>(header);
			if ("object_name".equals(config.propName) && "rev".equalsIgnoreCase(config.propLoc)) { // kk 20210324 倒数第二行写道版本属性object_name
				this.nameCol = c;
			}
			c.setCellValueFactory(p -> p.getValue().stringProps.get(config));
			c.setCellFactory(new Callback<TableColumn<KTableRow, String>, TableCell<KTableRow, String>>() {
				@Override
				public TableCell<KTableRow, String> call(TableColumn<KTableRow, String> param) {
					return new KFXEditableStringAreaTableCell<KTableRow, String>(TableLoader.this) {
						public void commitEdit(String item) {
							if (config.maxCharacterInRow > 0 && item != null) {
								String newValue = item.replace("\r", "");
								String[] split = newValue.split("\n", -1);
								StringBuilder sb = new StringBuilder();
								for (int i = 0; i < split.length; i++) {
									String s = split[i];
									if (i > 0) {
										sb.append("\n");
									}
									List<String> csplit = KChineseSplitter.getDivideVals(s, config.maxCharacterInRow, "\n");
									for (int j = 0; j < csplit.size(); j++) {
										if (j > 0) {
											sb.append("\r");
										}
										sb.append(csplit.get(j));
									}
									// while (s.length() > config.maxCharacterInRow) {
									// sb.append(s.substring(0, config.maxCharacterInRow)).append("\r");
									// s = s.substring(config.maxCharacterInRow);
									// insertPos.add(sb.length() - 1);
									// }
									// sb.append(s);
								}
								item = sb.toString();
								System.out.println("=====================\n" + newValue + "\n>>\n" + item + "\n\"=====================");
							}
							super.commitEdit(item);
						};
					};
				}
			});
			if (config.width > 0) {
				c.setPrefWidth(config.width);
			} else if (config.width < 0) {
				c.setMinWidth(0);
				c.setMaxWidth(0);
				c.setPrefWidth(0);
			}
			Platform.runLater(() -> {
				c.setSortable(false);
				c.setEditable(config.canEdit);
				table.getColumns().add(c);
			});
		} else {
			final boolean isOcc = "bl_occ_type".equals(config.propName);
			TableColumn<KTableRow, String> c = new TableColumn<KTableRow, String>(header);
			c.setCellValueFactory(p -> p.getValue().stringProps.get(config));
			c.setCellFactory(new Callback<TableColumn<KTableRow, String>, TableCell<KTableRow, String>>() {
				@Override
				@SuppressWarnings("unchecked")
				public TableCell<KTableRow, String> call(TableColumn<KTableRow, String> param) {
					return new ComboBoxTableCell<KTableRow, String>(null, isOcc ? occTypeOptions : options) {
						@Override
						public void updateItem(String t, boolean bl) {
							super.updateItem(t, bl);
							if (KUtil.isEmpty(t)) {
								return;
							}
							// System.out.println("Update LOV Item: " + t);
							TableRow<KTableRow> row = getTableRow();
							if (row == null || row.getItem() == null) {
								return;
							}
							KTableRow bean = row.getItem();
							if (bean.stringProps.containsKey(config)) {
								String val = bean.stringProps.get(config).getValueSafe();
								if (!KUtil.isEmpty(val)) {
									try {
										// System.out.println(real_display_map);
										if (isOcc) {
											if (occ_real_display_map.containsKey(val)) {
												val = occ_real_display_map.get(val);
											}
										} else {
											if (real_display_map.containsKey(val)) {
												val = real_display_map.get(val);
											}
										}
										// val = KUtil.getLovDisplayableValue(lov, val);
										// System.out.println(">> display value: " + val);
										super.updateItem(val, bl);
									} catch (Exception e) {
										e.printStackTrace();
										// MessageBox.post(e);
									}
								}
							}
						}

						@Override
						public void commitEdit(String t) {
							super.commitEdit(t);
							System.out.println("Change LOV value: " + t);
							try {
								TableRow<KTableRow> row = getTableRow();
								if (row == null || row.getItem() == null) {
									return;
								}
								KTableRow bean = row.getItem();
								if (bean.stringProps.containsKey(config)) {
									String realVal = isOcc ? occ_display_real_map.get(t) : display_real_map.get(t);
									// String realVal = KUtil.getLovValue(session, lov, t);
									System.out.println(">> real value: " + realVal);
									bean.stringProps.get(config).setValue(realVal);
								}
							} catch (Exception e) {
								e.printStackTrace();
							}
						}

					};
				}
			});
			if (config.width > 0) {
				c.setPrefWidth(config.width);
			}
			Platform.runLater(() -> {
				if (itemType == null && isOcc) {
					c.setEditable(false);
				} else {
					c.setEditable(config.canEdit);
				}
				c.setSortable(false);
				table.getColumns().add(c);
			});
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void insertText(String text) {
		if (KUtil.isEmpty(text)) {
			return;
		}
		Control currentControl = getCurrentControl();
		if (currentControl == null) {
			return;
		}
		if (currentControl instanceof KFXEditableStringAreaTableCell) {
			KFXEditableStringAreaTableCell<KTableRow, String> cell = (KFXEditableStringAreaTableCell<KTableRow, String>) currentControl;
			String oldValue = cell.getItem();
			String newValue;
			System.out.println(currentCaret + ":" + oldValue);
			int temp = currentCaret;
			if (oldValue == null) {
				newValue = text;
			} else if (currentCaret >= 0 && currentCaret <= oldValue.length()) {
				newValue = oldValue.substring(0, currentCaret) + text + oldValue.substring(currentCaret);
				currentCaret += text.length();
				temp = currentCaret;
			} else {
				newValue = oldValue + text;
			}
			System.out.println("-> " + newValue);
			cell.commitEdit(newValue);
			table.edit(cell.getTableRow().getIndex(), cell.getTableColumn());
			final int temp2 = temp;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
					Platform.runLater(() -> {
						cell.startEdit(temp2);
					});
				}
			}).start();
		}
		if (currentControl instanceof KFXEditableStringAreaHtmlTableCell) {
			KFXEditableStringAreaHtmlTableCell<KTableRow, String> cell = (KFXEditableStringAreaHtmlTableCell<KTableRow, String>) currentControl;
			String oldValue = cell.getItem();
			String newValue;
			System.out.println(currentCaret + ":" + oldValue);
			int temp = currentCaret;
			if (oldValue == null) {
				newValue = text;
			} else if (currentCaret >= 0 && currentCaret <= oldValue.length()) {
				newValue = oldValue.substring(0, currentCaret) + text + oldValue.substring(currentCaret);
				currentCaret += text.length();
				temp = currentCaret;
			} else {
				newValue = oldValue + text;
			}
			System.out.println("-> " + newValue);
			cell.commitEdit(newValue);
			table.edit(cell.getTableRow().getIndex(), cell.getTableColumn());
			final int temp2 = temp;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
					Platform.runLater(() -> {
						cell.startEdit(temp2);
					});
				}
			}).start();
		}
	}

	@SuppressWarnings("unchecked")
	public void insertText(String text, String parentText) {
		if (KUtil.isEmpty(text)) {
			return;
		}
		Control currentControl = getCurrentControl();
		if (currentControl == null) {
			return;
		}
		if (currentControl instanceof KFXEditableStringAreaTableCell) {
			KFXEditableStringAreaTableCell<KTableRow, String> cell = (KFXEditableStringAreaTableCell<KTableRow, String>) currentControl;
			try {
				System.out.println("Name: " + parentText);
				if (parentText != null && this.nameCol != null && cell.getTableColumn() != this.nameCol) {
					SimpleStringProperty value = (SimpleStringProperty) this.nameCol.getCellObservableValue((KTableRow) (cell.getTableRow().getItem()));
					System.out.println(value);
					value.setValue(parentText);
				}
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			String oldValue = cell.getItem();
			String newValue;
			System.out.println(currentCaret + ":" + oldValue);
			int temp = currentCaret;
			if (oldValue == null) {
				newValue = text;
			} else if (currentCaret >= 0 && currentCaret <= oldValue.length()) {
				newValue = oldValue.substring(0, currentCaret) + text + oldValue.substring(currentCaret);
				currentCaret += text.length();
				temp = currentCaret;
			} else {
				newValue = oldValue + text;
			}
			System.out.println("-> " + newValue);
			cell.commitEdit(newValue);
			table.edit(cell.getTableRow().getIndex(), cell.getTableColumn());
			final int temp2 = temp;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
					Platform.runLater(() -> {
						cell.startEdit(temp2);
					});
				}
			}).start();
		}
		if (currentControl instanceof KFXEditableStringAreaHtmlTableCell) {
			KFXEditableStringAreaHtmlTableCell<KTableRow, String> cell = (KFXEditableStringAreaHtmlTableCell<KTableRow, String>) currentControl;
			try {
				System.out.println("Name2: " + parentText);
				if (parentText != null && this.nameCol != null && cell.getTableColumn() != this.nameCol) {
					SimpleStringProperty value = (SimpleStringProperty) this.nameCol.getCellObservableValue((KTableRow) (cell.getTableRow().getItem()));
					System.out.println(value);
					value.setValue(parentText);
				}
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			String oldValue = cell.getItem();
			String newValue;
			System.out.println(currentCaret + ":" + oldValue);
			int temp = currentCaret;
			if (oldValue == null) {
				newValue = text;
			} else if (currentCaret >= 0 && currentCaret <= oldValue.length()) {
				newValue = oldValue.substring(0, currentCaret) + text + oldValue.substring(currentCaret);
				currentCaret += text.length();
				temp = currentCaret;
			} else {
				newValue = oldValue + text;
			}
			System.out.println("-> " + newValue);
			cell.commitEdit(newValue);

			TableRow<?> row = cell.getTableRow();
			int rowNum = row.getIndex();
			TableColumn<KTableRow, String> col = cell.getTableColumn();
			KTableRow bean = (KTableRow) table.getItems().get(rowNum);
			bean.showWebView(false);
			cell.updateItem(cell.getItem(), cell.isEmpty());
			table.layout();
			table.edit(rowNum, col);
			final int temp2 = temp;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
					Platform.runLater(() -> {
						cell.startEdit(temp2);
					});
				}
			}).start();
		}
	}

	public void reorder() throws Exception {
		if (sequenceConfigMap == null || sequenceConfigMap.size() == 0) {
			throw new Exception(REG.getString("noseqconfig.ERROR"));
		}
		SwingUtilities.invokeLater(new ReorderDialog(table, sequenceConfigMap));
	}

	@SuppressWarnings("rawtypes")
	public void editSymbol() throws Exception {
		ObservableList<TablePosition> select = table.getSelectionModel().getSelectedCells();
		if (select == null || select.size() == 0) {
			return;
		}
		TablePosition pos = select.get(0);
		int rowNum = pos.getRow();
		int colNum = pos.getColumn();
		System.out.println("Edit symbol at [" + rowNum + ", " + colNum + "]");
		if (rowNum < 0 || colNum < 0) {
			return;
		}
		KTableRow bean = (KTableRow) table.getItems().get(rowNum);
		TableColumn<KTableRow, ?> col = table.getColumns().get(colNum);
		PropConfig configT = null;
		if (htmlColMap.containsKey(col)) {
			configT = this.htmlColMap.get(col);
		} else if (htmlColMap.size() == 1) {
			for (TableColumn<KTableRow, String> k : htmlColMap.keySet()) {
				configT = htmlColMap.get(k);
			}
		}
		if (configT == null) {
			return;
		}
		final PropConfig config = configT;
		String oldValue = bean.stringProps.get(config).getValueSafe();
		System.out.println("Modify: " + oldValue);
		Display localDisplay = Display.getCurrent();
		if (localDisplay == null) {
			localDisplay = Display.getDefault();
		}
		KGdtEditor editor = new KGdtEditor(new JTextArea(oldValue));
		editor.setValue(oldValue);
		localDisplay.asyncExec(new Runnable() {
			public void run() {
				Shell localShell = UIUtilities.getCurrentModalShell();
				GdtDialog gdtDialog = new GdtDialog(localShell, null, editor, true) {
					public boolean close() {
						try {
							String newValue = editor.getValue();
							System.out.println("New Value: " + newValue);
							bean.stringProps.get(config).setValue(newValue);

						} catch (Exception e) {
							e.printStackTrace();
						}
						return super.close();
					}
				};
				editor.setGdtDialog(gdtDialog);
				gdtDialog.open();
			}
		});
	}

	public void doSearch(String prefName) throws Exception {
		int selectRow = table.getSelectionModel().getSelectedIndex();
		System.out.println("Search at [" + selectRow + "] - " + prefName);
		if (selectRow < 0 || KUtil.isEmpty(prefName)) {
			return;
		}
		KTableRow bean = table.getSelectionModel().getSelectedItem();
		SearchBySqlDialog dialog = new SearchBySqlDialog(prefName, bean);
		dialog.run();
	}

}
