package cn.net.connor.std.processedit.word;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.LibraryLoader;
import com.jacob.com.Variant;
import com.teamcenter.rac.kernel.TCComponentDataset;
import com.teamcenter.rac.kernel.TCSession;
import com.teamcenter.rac.util.Registry;

import cn.net.connor.std.aifrcp.util.KUtil;

public class KWordUtil {

	public static final Registry REG = Registry.getRegistry(KWordUtil.class);
	public static final int WORD_FORMAT_PDF = 17;
	private static Dispatch DOCUMENTS = null;
	public static ActiveXComponent WORDAPP = null;
	public static Dispatch SELECTION = null;

	public static void startWord() throws Exception {
		ComThread.InitSTA();
		LibraryLoader.loadJacobLibrary();
		WORDAPP = new ActiveXComponent("Word.Application");
		WORDAPP.setProperty("Visible", new Variant(false));
		WORDAPP.setProperty("DisplayAlerts", new Variant(false));
		// Dispatch.put(WORDAPP, "DisplayAlerts", new Variant(0));
		// ax.setProperty("AutomationSecurity", new Variant(3));
		DOCUMENTS = WORDAPP.getProperty("Documents").toDispatch();
	}

	public static void closeWord() {
		if (WORDAPP != null) {
			try {
				WORDAPP.invoke("Quit", new Variant[] {});
				WORDAPP = null;
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
				ComThread.Release();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public static class TableText {
		int row;
		int col;
		String text;

		public TableText(int row, int col, String text) {
			this.row = row;
			this.col = col;
			this.text = text;
		}
	}

	public static class PicInfo {
		int width;
		int height;
		String bookMark;
		File picFile;
		String picName;

		public PicInfo(int width, int height, String bookMark, File picFile, String picName) {
			super();
			this.width = width;
			this.height = height;
			this.bookMark = bookMark;
			this.picFile = picFile;
			this.picName = picName;
		}
	}

	public static class ExcelInfo {
		int sheetIndex;
		String bookMark;
		File excelFile;
		String range;

		public ExcelInfo(int sheetIndex, String bookMark, File excelFile, String range) {
			super();
			this.sheetIndex = sheetIndex;
			this.bookMark = bookMark;
			this.excelFile = excelFile;
			this.range = range;
		}

	}

	public static void checkFileExist(File file) throws Exception {
		if (file == null || !file.exists()) {
			throw new Exception(String.format(REG.getString("filenotexist.ERROR1"), file == null ? "null" : file.getAbsolutePath()));
		}
	}

	public static void writeToWord(File fromFile, File saveFile, Map<String, String> textData, List<TableText> tableData, int tableIndex, List<PicInfo> picData, List<ExcelInfo> excelData)
			throws Exception {
		Map<Integer, List<TableText>> tableDataMap = new HashMap<Integer, List<TableText>>();
		if (tableData != null && tableIndex > 0) {
			tableDataMap.put(tableIndex, tableData);
		}
		writeToWord(fromFile, saveFile, textData, tableDataMap, picData, excelData);
	}

	public static void writeToWord(File fromFile, File saveFile, Map<String, String> textData, Map<Integer, List<TableText>> tableDataMap, List<PicInfo> picData, List<ExcelInfo> excelData)
			throws Exception {
		checkFileExist(fromFile);
		System.out.println("Write To Word: " + fromFile.getAbsolutePath());
		Dispatch document = KWordUtil.open(fromFile.getAbsolutePath());
		if (document == null) {
			throw new Exception("Open file failed: " + fromFile.getAbsolutePath());
		}
		// pic name
		if (picData != null && picData.size() > 0) {
			if (textData == null) {
				textData = new HashMap<String, String>();
			}
			for (int i = 0; i < picData.size(); i++) {
				PicInfo data = picData.get(i);
				textData.put(data.bookMark + "_Name", data.picName);
			}
		}
		try {
			KWordUtil.setCustomProperties(document, textData);
			KWordUtil.setBookMarks(document, textData);
			for (Integer tableIndex : tableDataMap.keySet()) {
				KWordUtil.setTableData(document, tableDataMap.get(tableIndex), tableIndex);
			}
			KWordUtil.setPicData(document, picData);
			KWordUtil.setExcelData(document, excelData);
			if (saveFile != null) {
				KWordUtil.saveTo(document, saveFile.getAbsolutePath());
			}
		} finally {
			KWordUtil.close(document, saveFile == null);
		}
	}

	public static void setCustomProperties(Dispatch document, Map<String, String> textData) {
		Dispatch custDocProps = KWordUtil.getDocumentCustomProperties(document);
		if (custDocProps == null) {
			return;
		}
		for (String key : textData.keySet()) {
			String value = textData.get(key);
			KWordUtil.setCustomProperty(custDocProps, key, value);
		}
		KWordUtil.updateFields(document);
	}

	public static String getDocumentProperty(Dispatch document, String key) {
		Dispatch custDocProps = KWordUtil.getDocumentCustomProperties(document);
		if (custDocProps == null) {
			return "";
		}
		Dispatch prop = KWordUtil.getCustomProperty(custDocProps, key);
		if (prop == null) {
			return "";
		}
		return Dispatch.get(prop, "Value").toString();
	}

	public static void setPicData(Dispatch document, List<PicInfo> picData) {
		int cnt = picData == null ? 0 : picData.size();
		for (int i = 0; i < cnt; i++) {
			PicInfo picInfo = picData.get(i);
			System.out.println(String.format("Insert Image [%s][%d, %d]: %s", picInfo.bookMark, picInfo.width, picInfo.height, picInfo.picFile == null ? "null" : picInfo.picFile.getAbsolutePath()));
			if (picInfo.picFile == null || !picInfo.picFile.exists()) {
				System.out.println(">> File Not Exist");
				continue;
			}
			if (selectBookMark(document, picInfo.bookMark)) {
				insertImage(picInfo.picFile.getAbsolutePath(), picInfo.width, picInfo.height);
			} else {
				System.out.println(">> No Bookmark Found: " + picInfo.bookMark);
			}
		}
	}

	public static void setExcelData(Dispatch document, List<ExcelInfo> excelData) {
		int cnt = excelData == null ? 0 : excelData.size();
		for (int i = 0; i < cnt; i++) {
			ExcelInfo picInfo = excelData.get(i);
			System.out.println(String.format("Insert Excel [%s][%d]: %s", picInfo.bookMark, picInfo.sheetIndex, picInfo.excelFile == null ? "null" : picInfo.excelFile.getAbsolutePath()));
			if (picInfo.excelFile == null || !picInfo.excelFile.exists()) {
				System.out.println(">> File Not Exist");
				continue;
			}
			if (selectBookMark(document, picInfo.bookMark)) {
				insertExcel(picInfo.excelFile.getAbsolutePath(), picInfo.sheetIndex, picInfo.range);
			} else {
				System.out.println(">> No Bookmark Found: " + picInfo.bookMark);
			}
		}
	}

	public static void setTableData(Dispatch document, List<TableText> tableData, int tableIndex) {
		int cnt = tableData == null ? 0 : tableData.size();
		// System.out.println(cnt);
		if (cnt == 0) {
			return;
		}
		Dispatch table = getTable(document, tableIndex);
		if (table == null) {
			System.out.println("No Table Found At Index: " + tableIndex);
			return;
		}
		int maxRowNum = 0;
		for (TableText tableText : tableData) {
			if (tableText.row > maxRowNum) {
				maxRowNum = tableText.row;
			}
		}
		checkMaxRow(table, maxRowNum);
		for (int i = 0; i < cnt; i++) {
			setCellValue(table, tableData.get(i));
		}
	}

	public static void setCellValue(Dispatch table, TableText tableText) {
		if (tableText == null) {
			return;
		}
		int col = tableText.col;
		int row = tableText.row;
		String value = tableText.text == null ? "" : tableText.text;
		// System.out.println(String.format("Set Table Value [%d, %d] = [%s]", row, col,
		// value));
		if (col <= 0 || row <= 0) {
			return;
		}
		Dispatch cell = getTableCell(table, row, col);
		if (cell == null) {
			System.out.println(String.format("Cell Not Exist: [%d, %d] = [%s]", row, col, value));
			return;
		}
		try {
			Dispatch.call(cell, "Select");
			Dispatch.put(SELECTION, "Text", value);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void Enter(Dispatch selection) {
		Dispatch.call(selection, "TypeParagraph");
	}

	public static void checkMaxRow(Dispatch table, int maxRowNum) {
		// maxRowNum = 40;
		if(maxRowNum<=0) {
			return;
		}
		try {
			Dispatch rows = Dispatch.call(table, "Rows").toDispatch();
			int rowCnt = Dispatch.call(rows, "Count").getInt();
			System.out.println("check row count: " + rowCnt + " -> " + maxRowNum);
			for (int i = rowCnt + 1; i <= maxRowNum; i++) {
				// Dispatch beforeRow = Dispatch.call(table, "Rows", new Variant(i - 1)).toDispatch();
				Dispatch.call(rows, "Add").toDispatch();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static Dispatch getTableCell(Dispatch table, int row, int col) {
		try {
			return Dispatch.call(table, "Cell", new Variant(row), new Variant(col)).toDispatch();
		} catch (Exception e) {
		}
		return null;
	}

	public static Dispatch getTable(Dispatch document, int tableIndex) {
		try {
			Dispatch tables = Dispatch.call(document, "Tables").toDispatch();
			return Dispatch.call(tables, "Item", new Variant(tableIndex)).toDispatch();
		} catch (Exception e) {
		}
		return null;
	}

	public static Dispatch getCustomProperty(Dispatch custDocProps, String cusPropName) {
		try {
			return Dispatch.call(custDocProps, "Item", cusPropName).toDispatch();
		} catch (Exception e) {
		}
		return null;
	}

	public static void setCustomProperty(Dispatch custDocProp, String value) {
		try {
			// String oldVal = Dispatch.get(custDocProp, "Value").toString();
			// System.out.println(String.format(">> Change [%s] to [%s]", oldVal, value));
			Dispatch.put(custDocProp, "Value", value);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void updateFields(Dispatch document) {
		try {
			Dispatch StoryRanges = Dispatch.call(document, "StoryRanges").toDispatch();
			int rangeCnt = Dispatch.call(StoryRanges, "Count").getInt();
			for (int i = 1; i <= rangeCnt; i++) {
				Dispatch item;
				try {
					item = Dispatch.call(StoryRanges, "Item", i).toDispatch();
				} catch (Exception e) {
					continue;
				}
				Dispatch Fields = Dispatch.call(item, "Fields").toDispatch();
				int fieldCnt = Dispatch.call(Fields, "Count").getInt();
				for (int j = 1; j <= fieldCnt; j++) {
					Dispatch field;
					try {
						field = Dispatch.call(Fields, "Item", j).toDispatch();
					} catch (Exception e) {
						continue;
					}
					Dispatch.call(field, "Update");
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void setCustomProperty(Dispatch custDocProps, String name, String value) {
		System.out.println(String.format("Set Custom Property: [%s] = [%s]", name, value));
		Dispatch prop = getCustomProperty(custDocProps, name);
		if (prop != null) {
			setCustomProperty(prop, value);
		} else {
			System.out.println(">> Property not exist: " + name);
		}
	}

	public static void setBookMarks(Dispatch document, Map<String, String> textMap) {
		Dispatch bookMarks = Dispatch.call(document, "Bookmarks").toDispatch();
		int bCount = Dispatch.get(bookMarks, "Count").getInt();
		for (int i = 1; i <= bCount; i++) {
			Dispatch items = Dispatch.call(bookMarks, "Item", i).toDispatch();
			String bookMarkKey = String.valueOf(Dispatch.get(items, "Name").getString()).replaceAll("null", ""); // 读取书签命名
			if (!textMap.containsKey(bookMarkKey)) {
				continue;
			}
			Dispatch range = Dispatch.get(items, "Range").toDispatch();
			String oldBookMarkValue = String.valueOf(Dispatch.get(range, "Text").getString()).replaceAll("null", ""); // 读取书签文本
			String newBookMarkValue = textMap.get(bookMarkKey);
			Dispatch.put(range, "Text", new Variant(newBookMarkValue));
			System.out.println(String.format("Set Bookmark [%s]: [%s] -> [%s]", bookMarkKey, oldBookMarkValue, newBookMarkValue));
		}
	}

	public static void insertText(String newText) {
		Dispatch.put(SELECTION, "Text", newText);
	}

	public static boolean selectBookMark(Dispatch document, String bookmark) {
		Dispatch bookMarks = Dispatch.call(document, "Bookmarks").toDispatch();
		int bCount = Dispatch.get(bookMarks, "Count").getInt();
		for (int i = 1; i <= bCount; i++) {
			Dispatch items = Dispatch.call(bookMarks, "Item", i).toDispatch();
			String bookMarkKey = String.valueOf(Dispatch.get(items, "Name").getString()).replaceAll("null", ""); // 读取书签命名
			if (bookmark.equals(bookMarkKey)) {
				Dispatch range = Dispatch.get(items, "Range").toDispatch();
				Dispatch.call(range, "Select");
				return true;
			}
		}
		return false;
	}

	public static void insertImage(String imagePath, int w, int h) {
		if (!KUtil.isEmpty(imagePath)) {
			Dispatch picture = Dispatch.call(Dispatch.get(SELECTION, "InLineShapes").toDispatch(), "AddPicture", imagePath).toDispatch();
			Dispatch.call(picture, "Select");
			if (w > 0) {
				Dispatch.put(picture, "Width", new Variant(w));
			}
			if (h > 0) {
				Dispatch.put(picture, "Height", new Variant(h));
			}
		}
	}

	// TODO
	public static void insertExcel(String excelPath, int sheetIndex, String range) {
		if (!KUtil.isEmpty(excelPath)) {
			try {
				KExcelUtil.startExcel(false);
				Dispatch wb = KExcelUtil.open(excelPath);
				Dispatch sheet = KExcelUtil.getSheetByIndex(sheetIndex + 1);
				KExcelUtil.copySheet(sheet, range);
				Dispatch textRange = Dispatch.get(SELECTION, "Range").toDispatch();
				Dispatch.call(textRange, "Paste");
				KExcelUtil.close(wb, false);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				KExcelUtil.closeExcel(false);
			}
		}
	}

	public static void nextPage() {
		// Dispatch.call(SELECTION, "InsertBreak" , new Variant(7) );
		Dispatch.call(SELECTION, "InsertBreak", new Variant(2));// break next page is 2
		// Dispatch.call(WORDAPP, "Run", new
		// Variant("InsertBreakWdSectionBreakNextPage"));
	}

	public static void saveToPDF(Dispatch document, String pdfPath) {
		Dispatch.call(document, "SaveAs", new Object[] { pdfPath, new Variant(WORD_FORMAT_PDF) });
	}

	public static void saveTo(Dispatch document, String newPath) {
		Dispatch.call(document, "SaveAs", newPath);
	}

	public static Dispatch getDocumentCustomProperties(Dispatch document) {
		try {
			return Dispatch.get(document, "CustomDocumentProperties").toDispatch();
		} catch (Exception e) {
		}
		return null;
	}

	public static void close(Dispatch document, boolean save) {
		// if (save) {
		// Dispatch.call(document, "Save");
		// }
		Dispatch.call(document, "Close", new Variant(save));
	}

	public static Dispatch open(String path) {
		try {
			// return Dispatch.invoke(docs, "Open", Dispatch.Method, new Object[] { path,
			// new Variant(false), new Variant(false) }, new int[1]).toDispatch();
			Dispatch doc = Dispatch.call(DOCUMENTS, "Open", path).toDispatch();
			SELECTION = Dispatch.get(WORDAPP, "Selection").toDispatch();
			return doc;
		} catch (Exception e) {
		}
		return null;
	}

	public static String reformatFileName(String fileName) {
		if (fileName == null) {
			return null;
		}
		return fileName.replace("\\", "-").replace("/", "-").replace(":", "-").replace("*", "-").replace("?", "-").replace("\"", "-").replace("<", "[").replace(">", "]").replace("|", "-");
	}

	public static void mergeDocFiles(File newFile, List<File> fileList) throws Exception {
		if (newFile == null || fileList == null || fileList.size() == 0) {
			return;
		}
		int size = fileList.size();
		Dispatch doc = open(fileList.get(0).getAbsolutePath());
		if (doc == null) {
			throw new Exception("Open file failed: " + fileList.get(0).getAbsolutePath());
		}
		try {
			Dispatch.call(SELECTION, "EndKey", new Variant(6));
			Dispatch.call(SELECTION, "TypeParagraph");
			for (int i = 1; i < size; i++) {
				Dispatch.call(SELECTION, "EndKey", new Variant(6));
				Dispatch.call(SELECTION, "Delete");
				// nextPage();
				String path = fileList.get(i).getAbsolutePath();
				System.out.println("Merge: " + path);
				Dispatch.call(SELECTION, "EndKey", new Variant(6));
				Dispatch.invoke(WORDAPP.getProperty("Selection").toDispatch(), "insertFile", Dispatch.Method, new Object[] { path, "", new Variant(false), new Variant(false), new Variant(false) },
						new int[3]);
			}
			saveTo(doc, newFile.getAbsolutePath());
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Merge Documents Failed: " + e.getMessage());
		} finally {
			close(doc, false);
		}
	}

	public static void mergeDocFiles2(File newFile, List<File> fileList, TCSession session, String status) throws Exception {
		mergeDocFiles2(newFile, fileList, session, status, null);
	}

	public static void mergeDocFiles2(File newFile, List<File> fileList, TCSession session, String status, String password) throws Exception {
		if (newFile == null || fileList == null || fileList.size() == 0) {
			return;
		}
		int size = fileList.size();
		File f = fileList.get(0);
		Dispatch mergeDoc = null;
		Dispatch childDoc = null;
		try {
			for (int i = 1; i < size; i++) {
				if (session != null) {
					session.setStatus(status + " ( " + (i + 1) + " / " + size + " )");
				}
				// Dispatch.call(SELECTION, "EndKey", new Variant(6));
				// Dispatch.call(SELECTION, "Delete");
				// nextPage();
				String path = fileList.get(i).getAbsolutePath();
				System.out.println("Merge: " + path);
				childDoc = open(path);
				Dispatch wordContent = Dispatch.get(childDoc, "Content").toDispatch();
				Dispatch.call(wordContent, "Copy");
				mergeDoc = open(f.getAbsolutePath());
				Dispatch.call(SELECTION, "EndKey", new Variant(6));
				nextPage();
				Dispatch.call(SELECTION, "EndKey", new Variant(6));
				Dispatch textRange = Dispatch.get(SELECTION, "Range").toDispatch();
				Dispatch.call(textRange, "Paste");
				// clear copy
				if (find(SELECTION, " ")) {
					Dispatch.call(SELECTION, "Copy");
				}
				close(mergeDoc, true);
				mergeDoc = null;
				close(childDoc, false);
				childDoc = null;
			}
			mergeDoc = open(fileList.get(0).getAbsolutePath());
			if (mergeDoc == null) {
				throw new Exception("Open file failed: " + fileList.get(0).getAbsolutePath());
			}
			saveTo(mergeDoc, newFile.getAbsolutePath());
			close(mergeDoc, false);
			mergeDoc = null;
			if (!KUtil.isEmpty(password)) {
				System.out.println("Add Password");
				Dispatch newDoc = open(newFile.getAbsolutePath());
				if (newDoc == null) {
					throw new Exception("Open file failed: " + fileList.get(0).getAbsolutePath());
				}
				Dispatch.call(newDoc, "Protect", new Variant(1), new Variant(true), password);
				Dispatch.call(newDoc, "Save");
				close(newDoc, true);
				newDoc = null;
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Merge Documents Failed: " + e.getMessage());
		} finally {
			if (mergeDoc != null) {
				close(mergeDoc, false);
			}
			if (childDoc != null) {
				close(childDoc, false);
			}
		}
	}

	public static int getPageCnt(TCComponentDataset wordDataset) throws Exception {
		if (wordDataset == null) {
			return 0;
		}
		File file = KUtil.getDatasetFile(wordDataset);
		Dispatch word = open(file.getAbsolutePath());
		int pageCount = Integer.parseInt(Dispatch.call(SELECTION, "information", 4).toString());
		// String pageCnt = getDocumentProperty(word, KWord.FIELD_PAGECOUNT);
		close(word, false);
		file.delete();
		System.out.println("#PAGECOUNT#: [" + wordDataset + "] = " + pageCount);
		return pageCount >= 0 ? pageCount : 0;
	}

	public static int getPageCntAndFile(TCComponentDataset wordDataset, Map<File, String[]> files, String[] appendConfig, File dir) throws Exception {
		if (wordDataset == null) {
			return 0;
		}
		File file = KUtil.getDatasetFile(dir, wordDataset);
		if (files.containsKey(file)) {
			System.out.println("Duplicate File: " + wordDataset + " - " + file.getAbsolutePath());
			return 0;
		}
		Dispatch word = open(file.getAbsolutePath());
		int pageCount = Integer.parseInt(Dispatch.call(SELECTION, "information", 4).toString());
		// String pageCnt = getDocumentProperty(word, KWord.FIELD_PAGECOUNT);
		close(word, false);
		files.put(file, appendConfig);
		System.out.println("#PAGECOUNT#: [" + wordDataset + "] = " + pageCount);
		return pageCount >= 0 ? pageCount : 0;
	}

	public static int setAppendWordPageNumAndCount(File file, int pageNum, int totalActualPageCnt, int[] tablePos) throws Exception {
		Dispatch word = open(file.getAbsolutePath());
		System.out.println("Set Page Num And Count: " + file.getAbsolutePath() + " | " + pageNum + " | " + Arrays.toString(tablePos));
		int pageCount = Integer.parseInt(Dispatch.call(SELECTION, "information", 4).toString());
		if (tablePos != null && tablePos.length == 4) {
			for (int i = 0; i < pageCount; i++) {
				List<TableText> tableData = new ArrayList<KWordUtil.TableText>();
				tableData.add(new TableText(tablePos[0], tablePos[1], "" + (pageNum + i)));
				tableData.add(new TableText(tablePos[2], tablePos[3], "" + totalActualPageCnt));
				setTableData(word, tableData, i + 1);
			}
		} else {
			String pageNumFormat = REG.getString("word.PAGENUM");
			String pageCountFormat = REG.getString("word.PAGECOUNT");
			replaceAll(SELECTION, String.format(pageCountFormat, ""), String.format(pageCountFormat, "" + totalActualPageCnt));
			moveStart(SELECTION);
			String oldText = String.format(pageNumFormat, "");
			int index = 0;
			while (find(SELECTION, oldText)) {
				replace(SELECTION, String.format(pageNumFormat, "" + (pageNum + index)));
				Dispatch.call(SELECTION, "MoveRight");
				index++;
			}
			close(word, true);
		}

		return pageNum + pageCount;
	}

	public static void moveStart(Dispatch selection) {
		Dispatch.call(selection, "HomeKey", new Variant(6));
	}

	public static void replace(Dispatch selection, String newText) {
		Dispatch.put(selection, "Text", newText);
	}

	public static void replaceAll(Dispatch selection, String oldText, Object replaceObj) {
		moveStart(selection);
		String newText = (String) replaceObj;
		while (find(selection, oldText)) {
			replace(selection, newText);
			Dispatch.call(selection, "MoveRight");
		}
	}

	public static boolean find(Dispatch selection, String toFindText) {
		Dispatch find = Dispatch.call(selection, "Find").toDispatch();
		Dispatch.put(find, "Text", toFindText);
		Dispatch.put(find, "Forward", "True");
		Dispatch.put(find, "format", "True");
		Dispatch.put(find, "MatchCase", "True");
		Dispatch.put(find, "MatchWholeWord", "True");
		return Dispatch.call(find, "Execute").getBoolean();
	}

}