/*
 * "Peko" Visual Novel System
 *
 * All Rights Reserved.
 * Copyright (c) 1999-2003 Tsukuba Bunko.
 *
 * $Id: SessionManager.java,v 1.1.2.1 2003/12/11 10:12:19 ppoi Exp $
 */
package tsukuba_bunko.peko.session;

import	java.io.File;
import	java.io.FileInputStream;
import	java.io.FileOutputStream;
import	java.io.ObjectInputStream;
import	java.io.ObjectOutputStream;

import	java.text.DecimalFormat;

import	java.util.Date;
import	java.util.HashSet;

import	javax.swing.JOptionPane;

import	tsukuba_bunko.peko.Logger;
import	tsukuba_bunko.peko.PekoSystem;

import	tsukuba_bunko.peko.resource.ResourceManager;


/**
 * セーブデータの管理を行います。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.1 $
 */
public class SessionManager	{

	/**
	 * セーブデータ ID のフォーマッタ
	 */
	protected static final DecimalFormat	FORMAT = new DecimalFormat( "000" );


	/**
	 * 前回選択したインデックス
	 */
	protected int	_lastIndex = -1;

	/**
	 * システムセーブデータ
	 */
	protected SystemSaveData	_systemSaveData = null;

	/**
	 * 現在のセッション
	 */
	protected Session	_session = null;


	/**
	 * <code>SessionManager</code> のインスタンスを生成します。
	 */
	public SessionManager()
	{
		super();

		try	{
			_systemSaveData = loadSystemSaveData();
		}
		catch( Exception e )	{
			Logger.debug( "[session.manager] fail to load SystemSaveData.", e );
		}

		_session = new Session();
		_session.setSessionFlagSet( new HashSet(89) );
		if( _systemSaveData != null )	{
			_session.setSystemFlagSet( _systemSaveData.getSystemFlagSet(), _systemSaveData.getSaveDataInfo().getTimestamp() );
		}
		else	{
			_systemSaveData = new SystemSaveData();
			SaveDataInfo	info = new SaveDataInfo();
			info.setID( -1 );
			info.setTitle( "PVNS System Save Data" );
			_systemSaveData.setSaveDataInfo( info );

			_session.setSystemFlagSet( new HashSet(89), null );
		}
	}


	/**
	 * 現在のセッションを初期化します。
	 */
	public void initializeSession()
	{
		_session.setSessionFlagSet( new HashSet(89) );
		_session.setSceneContext( null );
	}

	/**
	 * 現在のセッションを取得します。
	 * @return	現在のセッション
	 */
	public Session getSession()
	{
		return _session;
	}

	/**
	 * システムセーブデータを取得します。
	 * @return	システムセーブデータ
	 */
	public SystemSaveData getSystemSaveData()
	{
		return _systemSaveData;
	}


	/**
	 * 現在のセッションを保存します。
	 */
	public void saveCurrentSession()
		throws SaveFailureException
	{
		SaveData	saveData = new SaveData();
		SaveDataInfo	saveDataInfo = new SaveDataInfo();
		saveDataInfo.setTitle( _session.getSceneContext().getSceneTitle() );

		saveData.setSaveDataInfo( saveDataInfo );
		saveData.setSession( _session );

		save( saveData );
	}


	/**
	 * セーブデータ一覧を取得します。
	 * @param	beginIndex	開始インデックス
	 * @param	size	取得サイズ
	 * @return	セーブデータ一覧
	 */
	protected SaveDataInfo[] getSaveDataInfoList( int beginIndex, int size )
	{
		SaveDataInfo[]	list = new SaveDataInfo[ size ];
		File	saveFile = null;
		SaveData	saveData = null;
		SaveDataInfo	saveDataInfo = null;
		Object	data = null;
		FileInputStream	fis = null;
		ObjectInputStream	ois = null;
		for( int i = 0; i < list.length; ++i )	{
			saveFile = getSaveFile( i + beginIndex );
			if( saveFile.isFile() )	{
				try	{
					fis = new FileInputStream( saveFile );
					ois = new ObjectInputStream( fis );
					data = ois.readObject();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0008W, new Object[]{String.valueOf(i + beginIndex)}, e );
				}
				finally	{
					if( ois != null )	{
						try	{
							ois.close();
						}
						catch( Exception e )	{
							Logger.warn( MessageIDs.SAV0004W, e );
						}
					}
					else if( fis != null )	{
						try	{
							fis.close();
						}
						catch( Exception e )	{
							Logger.warn( MessageIDs.SAV0004W, e );
						}
					}
					ois = null;
					fis = null;
				}

				if( data instanceof SaveData )	{
					saveData = (SaveData)data;
					if( saveData != null )	{
						saveDataInfo = saveData.getSaveDataInfo();
						if( (saveDataInfo != null) && (saveDataInfo.getTitle() != null) && (saveDataInfo.getTimestamp() != null) )	{
							list[i] = saveDataInfo;
						}
					}
					else	{
						Logger.warn( MessageIDs.SAV0022W, new Object[]{String.valueOf(i)} );
					}
				}
			}
		}

		return list;
	}

	/**
	 * セーブデータを保存します。
	 * @param	data	保存するセーブデータ
	 * @throws	SaveFailureException
	 */
	protected void save( SaveData data )
		throws SaveFailureException
	{
		SaveDataInfo[]	list = getSaveDataInfoList( 0, 20 );
		int	index = SaveDataDialog.showDialog( list, getInitialSelectedIndexForSave(list), true );
		Logger.debug( "index: " + index );
		if( index == -1 )	{
			return;
		}
		else	{
			data.getSaveDataInfo().setID( index );
		}
		Date	timestamp = new Date();
		data.getSaveDataInfo().setTimestamp( timestamp );

		FileOutputStream	fos = null;
		ObjectOutputStream	oos = null;
		try	{
			File	path = getSaveFile( data.getSaveDataInfo().getID() );
			Logger.debug( "save to " + path.getAbsolutePath() );

			fos = new FileOutputStream( path );

			oos = new ObjectOutputStream( fos );
			oos.writeObject( data );
			oos.flush();
			_lastIndex = index;
		}
		catch( Exception e )	{
			Logger.error( MessageIDs.SAV0001E, new Object[]{String.valueOf(data.getSaveDataInfo().getID())}, e );
			SaveFailureException	sfe = new SaveFailureException( "fail to save.", e );
			throw sfe;
		}
		finally	{
			if( oos != null )	{
				try	{
					oos.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0002W, e );
				}
			}
			else if( fos != null )	{
				try	{
					fos.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0002W, e );
				}
			}
		}
	}



	/**
	 * セーブデータを読み込みます。
	 * @param	id	読み込むセーブデータのセーブデータ ID
	 * @return	読み込んだセーブデータ
	 * @throws	LoadFailureException	ロードに失敗した場合
	 */
	public boolean load()
		throws LoadFailureException
	{
		SaveDataInfo[]	list = getSaveDataInfoList( 0, 20 );
		boolean	nodata = false;
		if( list != null )	{
			nodata = true;
			for( int i = 0; i < list.length; ++i )	{
				if( list[i] != null )	{
					nodata = false;
				}
			}
		}
		else	{
			nodata = true;
		}

		if( nodata )	{
			ResourceManager	resources = ResourceManager.getInstance();
			String	title = (String)resources.getResource( ResourceIDs.DIALOG_NOTIFY_NODATA_TITLE );
			if( title == null )	{
				Logger.warn( MessageIDs.SAV0020W );
			}
			String	message = (String)resources.getResource( ResourceIDs.DIALOG_NOTIFY_NODATA_MESSAGE );
			if( message == null )	{
				message = "No save data for loading.";
				Logger.warn( MessageIDs.SAV0021W, new Object[]{"\"" + message + "\""} );
			}
			JOptionPane.showMessageDialog( PekoSystem.getInstance().getMainWindow(), message, title, JOptionPane.INFORMATION_MESSAGE );
			return false;
		}

		int	index = SaveDataDialog.showDialog( list, getInitialSelectedIndexForLoad(list), false );
		if( index == -1 )	{
			return false;
		}

		File	path = getSaveFile( index );
		if( !path.exists() )	{
			Logger.error( MessageIDs.SAV0005E, new Object[]{String.valueOf(index)} );
			LoadFailureException	lfe = new LoadFailureException( "no such save file" );
			throw lfe;
		}

		FileInputStream	fis = null;
		ObjectInputStream	ois = null;
		try	{
			fis = new FileInputStream( path );
			ois = new ObjectInputStream( fis );

			SaveData	data = (SaveData)ois.readObject();
			Session	session = data.getSession();
			session.setSystemFlagSet( _session.getSystemFlagSet(), _session.getTimestamp() );
			_session = session;

			_lastIndex = index;
			return true;
		}
		catch( Exception e )	{
			Logger.error( MessageIDs.SAV0003E, new Object[]{String.valueOf(String.valueOf(index))}, e );
			LoadFailureException	lfe = new LoadFailureException( "fail to load.", e );
			throw lfe;
		}
		finally	{
			if( ois != null )	{
				try	{
					ois.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0004W, e );
				}
			}
			else if( fis != null )	{
				try	{
					fis.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0004W, e );
				}
			}
		}
	}

	/**
	 * システムセーブデータを保存します。
	 * @param	data	保存するセーブデータ
	 */
	public void saveSystemSaveData()
		throws SaveFailureException
	{
		Logger.debug( "save SystemSaveData." );
		SystemSaveData	data = _systemSaveData;
		data.setSystemFlagSet( _session.getSystemFlagSet() );
		FileOutputStream	fos = null;
		ObjectOutputStream	oos = null;
		try	{
			data.getSaveDataInfo().setTimestamp( new Date() );

			File	path = getSystemSaveFile();

			fos = new FileOutputStream( path );

			oos = new ObjectOutputStream( fos );
			oos.writeObject( data );
			oos.flush();
		}
		catch( Exception e )	{
			Logger.error( MessageIDs.SAV0006F, e );
			SaveFailureException	sfe = new SaveFailureException( "fail to save system save data.", e );
			throw sfe;
		}
		finally	{
			if( oos != null )	{
				try	{
					oos.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0002W, e );
				}
			}
			else if( fos != null )	{
				try	{
					fos.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0002W, e );
				}
			}
		}
	}

	/**
	 * システムセーブデータを読み込みます。
	 * @return	システムセーブデータ
	 * @throws	LoadFailureException	読み込みに失敗した場合
	 */
	public SystemSaveData loadSystemSaveData()
		throws LoadFailureException
	{
		SystemSaveData	saveData = null;

		File	path = getSystemSaveFile();
		if( !path.exists() )	{
			return null;
		}

		FileInputStream	fis = null;
		ObjectInputStream	ois = null;
		try	{
			fis = new FileInputStream( path );
			ois = new ObjectInputStream( fis );

			saveData = (SystemSaveData)ois.readObject();
		}
		catch( Exception e )	{
			Logger.fatal( MessageIDs.SAV0006F, e );
			LoadFailureException	lfe = new LoadFailureException( "fail to load.", e );
			throw lfe;
		}
		finally	{
			if( ois != null )	{
				try	{
					ois.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0004W, e );
				}
			}
			else if( fis != null )	{
				try	{
					fis.close();
				}
				catch( Exception e )	{
					Logger.warn( MessageIDs.SAV0004W, e );
				}
			}
		}

		return saveData;
	}

	/**
	 * <code>id</code> で識別されるセーブデータを格納するセーブファイルを取得します。
	 * @param	id	セーブデータ ID
	 * @return	セーブファイル
	 */
	protected File getSaveFile( int id )
	{
		ResourceManager	resources = ResourceManager.getInstance();
		return new File( resources.getLocationResources().getSaveDirectory(), "save" + FORMAT.format(new Integer(id)) + ".dat" );
	}

	/**
	 * システムの状態を保存するセーブデータを格納するセーブファイルを取得します。
	 * @return	セーブファイル
	 */
	protected File getSystemSaveFile()
	{
		ResourceManager	resources = ResourceManager.getInstance();
		return new File( resources.getLocationResources().getSaveDirectory(), "config.dat" );
	}

	/**
	 * セーブ時に最初に選択されているインデックスを取得します。
	 * @param	list	一覧
	 * @return	セーブ時に最初に選択されているインデックス
	 */
	protected int getInitialSelectedIndexForSave( SaveDataInfo[] list )
	{
		if( (list == null) || (list.length == 0) )	{
			throw new IllegalArgumentException( "void list is specified." );
		}

		int	selected = 0;
		SaveDataInfo	info = null;
		for( int i = 0; i < list.length; ++i )	{
			info = list[i];
			if( info == null )	{
				selected = i;
				break;
			}
			else	{
				if( (list[selected] == null) || info.getTimestamp().before(list[selected].getTimestamp()) )	{
					selected = i;
				}
			}
		}
		return selected;
	}

	/**
	 * ロード時に最初に選択されているインデックスを取得します。
	 * @param	list	一覧
	 * @return	ロード時に最初に選択されているインデックス
	 */
	protected int getInitialSelectedIndexForLoad( SaveDataInfo[] list )
	{
		if( (list == null) || (list.length == 0) )	{
			throw new IllegalArgumentException( "void list is specified." );
		}

		if( _lastIndex != -1 )	{
			return _lastIndex;
		}

		int	selected = 0;
		SaveDataInfo	info = null;
		for( int i = 0; i < list.length; ++i )	{
			info = list[i];
			if( info != null )	{
				if( (list[selected] == null) || info.getTimestamp().after(list[selected].getTimestamp()) )	{
					selected = i;
				}
			}
		}
		return selected;
	}
}