package charactermanaj.model.io;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import charactermanaj.model.CharacterData;
import charactermanaj.model.IndependentPartsSetInfo;
import charactermanaj.model.IndependentWorkingSet;
import charactermanaj.model.PartsColorInfo;
import charactermanaj.model.PartsIdentifier;
import charactermanaj.model.PartsSet;
import charactermanaj.model.WorkingSet;
import charactermanaj.util.FileUtilities;
import charactermanaj.util.UserData;
import charactermanaj.util.UserDataFactory;

/**
 * ワーキングセットの保存と復元.<br>
 *
 * @author seraphy
 */
public class WorkingSetPersist {

	/**
	 * ロガー
	 */
	private static final Logger logger = Logger
			.getLogger(WorkingSetPersist.class.getName());

	/**
	 * ワーキングセットのサフィックス.
	 */
	private static final String WORKINGSET_FILE_SUFFIX = "workingset.xml";

	private static final WorkingSetPersist singletion = new WorkingSetPersist();

	private final UserDataFactory userDataFactory = UserDataFactory.getLocalInstance();

	public static WorkingSetPersist getInstance() {
		return singletion;
	}

	/**
	 * すべてのワーキングセットをクリアする.<br>
	 */
	public void removeAllWorkingSet() {
		File dir = userDataFactory.getSpecialDataDir("foo-" + WORKINGSET_FILE_SUFFIX);
		if (dir.exists() && dir.isDirectory()) {
			File[] files = dir.listFiles(new FileFilter() {
				public boolean accept(File pathname) {
					return pathname.isFile() && pathname.getName().endsWith(WORKINGSET_FILE_SUFFIX);
				}
			});
			if (files == null) {
				logger.log(Level.WARNING, "dir access failed. " + dir);
				return;
			}
			for (File file : files) {
				logger.log(Level.INFO, "remove file: " + file);
				try {
					FileUtilities.delete(file);
				} catch (Exception ex) {
					logger.log(Level.WARNING, "failed to remove file: " + file, ex);
				}
			}
		}
	}

	/**
	 * ワーキングセットを削除する.
	 *
	 * @param cd
	 *            対象のキャラクターデータ
	 */
	public void removeWorkingSet(CharacterData cd) {
		UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(
				cd.getDocBase(), WORKINGSET_FILE_SUFFIX);
		if (workingSetXmlData != null && workingSetXmlData.exists()) {
			logger.log(Level.INFO, "remove file: " + workingSetXmlData);
			workingSetXmlData.delete();
		}
	}

	/**
	 * ワーキングセットを保存する.<br>
	 * ワーキングセットインスタンスには、あらかじめ全て設定しておく必要がある.<br>
	 *
	 * @param workingSet
	 *            ワーキングセット
	 * @throws IOException
	 *             失敗
	 */
	public void saveWorkingSet(WorkingSet workingSet) throws IOException {
		if (workingSet == null) {
			throw new IllegalArgumentException();
		}
		CharacterData characterData = workingSet.getCharacterData();
		if (characterData == null) {
			throw new IllegalArgumentException("character-data must be set.");
		}

		// XML形式でのワーキングセットの保存
		UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(
				characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);
		OutputStream outstm = workingSetXmlData.getOutputStream();
		try {
			WorkingSetXMLWriter workingSetXmlWriter = new WorkingSetXMLWriter();
			workingSetXmlWriter.writeWorkingSet(workingSet, outstm);
		} finally {
			outstm.close();
		}
	}

	/**
	 * ワーキングセットを取得する.<br>
	 *
	 * @param characterData
	 *            対象のキャラクターデータ
	 * @return ワーキングセット、なければnull
	 * @throws IOException
	 *             読み込みに失敗した場合
	 */
	public WorkingSet loadWorkingSet(CharacterData characterData)
			throws IOException {
		if (characterData == null) {
			throw new IllegalArgumentException();
		}
		// XML形式でのワーキングセットの復元
		UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(
				characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);
		if (workingSetXmlData == null || !workingSetXmlData.exists()) {
			// 保存されていない場合
			return null;
		}

		// キャラクターデータと関連づけられていないワーキングセットデータを取得する
		IndependentWorkingSet workingSet2;

		InputStream is = workingSetXmlData.openStream();
		try {
			WorkingSetXMLReader WorkingSetXMLReader = new WorkingSetXMLReader();
			workingSet2 = WorkingSetXMLReader.loadWorkingSet(is);

		} finally {
			is.close();
		}

		// 現在のキャラクターデータと照合する。
		URI docBase = characterData.getDocBase();
		if (docBase != null
				&& !docBase.equals(workingSet2.getCharacterDocBase())) {
			// docBaseが一致せず
			logger.log(Level.INFO, "docBase missmatch");
			return null;
		}
		String sig = characterData.toSignatureString();
		if (!sig.equals(workingSet2.getCharacterDataSig())) {
			// 構造が一致せず.
			logger.log(Level.INFO, "character data structure missmatch");
			return null;
		}

		// 現在のキャラクターデータに関連づけられているワーキングセットとして転記する

		WorkingSet ws = new WorkingSet();
		ws.setCharacterData(characterData);
		ws.setCharacterDataRev(characterData.getRev());
		ws.setCharacterDocBase(docBase);

		ws.setLastUsedExportDir(workingSet2.getLastUsedExportDir());
		ws.setLastUsedSaveDir(workingSet2.getLastUsedSaveDir());

		// パーツの色情報を復元する.
		Map<PartsIdentifier, PartsColorInfo> partsColorInfoMap = characterData
				.getPartsColorManager().getPartsColorInfoMap();
		workingSet2.createCompatible(characterData, partsColorInfoMap);
		ws.setPartsColorInfoMap(partsColorInfoMap);

		// 選択されているパーツの復元
		IndependentPartsSetInfo partsSetInfo = workingSet2.getCurrentPartsSet();
		if (partsSetInfo != null) {
			PartsSet partsSet = IndependentPartsSetInfo.convertPartsSet(
					partsSetInfo, characterData, false);
			ws.setCurrentPartsSet(partsSet);

			// 最後に選択したお気に入り情報の復元
			IndependentPartsSetInfo lastUsePresetPartsInfo = workingSet2
					.getLastUsePresetParts();
			if (lastUsePresetPartsInfo != null
					&& lastUsePresetPartsInfo.getId() != null
					&& lastUsePresetPartsInfo.getId().trim().length() > 0) {
				PartsSet lastUsePresetParts = IndependentPartsSetInfo
						.convertPartsSet(lastUsePresetPartsInfo,
								characterData, false);
				ws.setLastUsePresetParts(lastUsePresetParts);
			}
		}

		ws.setWallpaperInfo(workingSet2.getWallpaperInfo());

		ws.setZoomFactor(workingSet2.getZoomFactor());
		ws.setViewPosition(workingSet2.getViewPosition());
		ws.setWindowRect(workingSet2.getWindowRect());
		
		ws.setNoNeedDataDownload(workingSet2.isNoNeedDataDownload());

		return ws;
	}

	/**
	 * 古いワーキングセットについて、すでに本体データが削除されている場合はワーキングセットも削除する。
	 * まだ存在するものは削除されない。
	 * 生きている場合は、ワーキングセットの検査日時を表すために更新日時を現在日時に設定しなおす。
	 * @param expireDate 判定対象となる日時、それ以前のもののみ判定を行う。
	 */
	public void purge(final long expireDate) {
		final String XML_SUFFIX = "-" + WORKINGSET_FILE_SUFFIX;

		UserDataFactory userDataFactory = UserDataFactory.getLocalInstance();
		File dir = userDataFactory.getSpecialDataDir(XML_SUFFIX);

		File[] xmls = dir.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return pathname.isFile() && pathname.getName().endsWith(XML_SUFFIX)
						&& pathname.lastModified() < expireDate && pathname.length() > 0;
			}
		});
		if (xmls == null) {
			logger.log(Level.WARNING, "workingset-dir access failed.");
			return;
		}

		for (File xmlFile : xmls) {
			try {
				WorkingSetXMLReader reader = new WorkingSetXMLReader();
				IndependentWorkingSet ws;
				InputStream is = new BufferedInputStream(new FileInputStream(xmlFile));
				try {
					ws = reader.loadWorkingSet(is);
				} finally {
					is.close();
				}

				URI docBase = ws.getCharacterDocBase();
				if (docBase.getScheme().equals("file")) {
					File characterXml = new File(docBase);
					if (!characterXml.exists()) {
						// キャラクター定義XMLが存在しない = 削除されたキャラクターデータ
						logger.log(Level.INFO, "remove amandone workingset: " + xmlFile + ", docBase=" + docBase);
						xmlFile.delete();
					} else {
						// チェック済みであることを示すためにXMLの更新日時を現在時刻にする
						xmlFile.setLastModified(System.currentTimeMillis());
					}
				}

			} catch (Exception ex) {
				logger.log(Level.WARNING, "file access failed. " + xmlFile, ex);
			}
		}
	}
}
