/////////////////////////////////////////////////////////////////
//Copyright (C) 2005 NTT CORPORATION.
//
//LoInfo オブジェクトリスト生成クラス
//
//変更履歴
//2005.01.27  新規作成  森　智志
//
//機能
//
//ScormXMLInfoクラスの継承クラスのオブジェクトを作成するためのfactoryクラスです。
//
/////////////////////////////////////////////////////////////////
package jp.co.ntt.lms.lo.lom.learnableinfo;

import java.io.*;
import java.util.*;

import org.w3c.dom.*;
import jp.co.ntt.lms.lo.scorm.ScormLoEnv;

/**
 * ScormXMLInfoクラスの継承クラスのオブジェクトを作成するためのfactoryクラスです。
 * @author s-mori
 * @version 1.00
 */
public class ScormXMLInfoFactory {
	
	private NodeController ctrl = null;
	private NodeList manifestNodeList = null;
	private File manifestDirectory = null;
	private File metadataDirectory = null;
	
	/**
	 * コンストラクタ。
	 * インスタンスの作成を禁止しています。
	 */
	private ScormXMLInfoFactory(){
	}
	
	/**
	 * コンストラクタ。マニフェストファイルを指定します。
	 * @param manifestFile マニフェストファイル
	 */
	public ScormXMLInfoFactory(File manifestFile) throws Exception{
		
		// scorm.ini 読込
		ScormLoEnv env = new ScormLoEnv();

		// ノードコントローラの取得
		this.ctrl = new NodeController();

		// マニフェストノードを取得する
		this.getManifestNodeList(manifestFile);

		// マニフェストファイルの絶対ディレクトリパスを取得する
		this.manifestDirectory = manifestFile.getParentFile();
		
		// xms2内のメタデータの絶対ディレクトリパスを取得する
		this.metadataDirectory = new File(env.getTempMaterialPath() + File.separator + this.manifestDirectory.getName());
		
					
	}

	/**
	 * manifestノードのリストを、マニフェストファイルから取得します。
	 * @param manifestFile マニフェストファイル
	 * @throws Exception
	 */
	private void getManifestNodeList(File manifestFile) throws Exception{
		if(manifestFile == null){
			throw new NullPointerException("Manifest file is null.");
		}
		
		Node topNode = ctrl.parse(manifestFile);
		
		this.manifestNodeList = ctrl.getNodeList(topNode, "//:manifest");
		
	}
	
	/**
	 * ManifestInfoのリストオブジェクトを作成します。
	 * @return ManifestInfoのコレクション
	 * @throws Exception
	 */
	public Collection makeManifestInfoList() throws Exception{
		Collection manifestInfoList = new ArrayList();
		
		for(int m = 0; m < this.manifestNodeList.getLength(); m++){
			
			Node manifestNode = this.manifestNodeList.item(m);
			
			// manifest の identifier は存在必須。見つからない場合は NullPointerException 
			String maniId = manifestNode.getAttributes().getNamedItem("identifier").getNodeValue();
			
			// manifestInfo を作成
			ManifestInfo maniInfo = new ManifestInfo(maniId);
			maniInfo.setNode(manifestNode);
			maniInfo.setInline(m != 0);

			// manifest直下の meta-data を取得
			LomInfo topLomInfo = this.makeLomInfo(manifestNode);
			if(topLomInfo != null){
				maniInfo.addLomInfo(topLomInfo);
			}
			
			
			// item ノードを探す
			NodeList itemNodeList = ctrl.getNodeList(manifestNode, ".//:organizations/:organization//:item");

			// itemIdentifier を取得し、それをもとに LomInfo を作成し、manifestInfo に追加
			for(int i = 0; i < itemNodeList.getLength(); i++){
				String identifier = itemNodeList.item(i).getAttributes().getNamedItem("identifier").getNodeValue();
				if(identifier != null){
					LomInfo info = this.makeLomInfo(identifier);
					if(info != null){
						maniInfo.addLomInfo(info);
					}
				}
			}
			
			// 完成した manifestInfo をコレクションに追加
			manifestInfoList.add(maniInfo);
		}
		
		return manifestInfoList;
	}
	
	
	private LomInfo makeLomInfo(Node manifestNode) throws Exception{
		return this.makeLomInfo(manifestNode, null);
	}
	private LomInfo makeLomInfo(String itemIdentifier) throws Exception{
		return this.makeLomInfo(null, itemIdentifier);
	}
	
	private LomInfo makeLomInfo(Node manifestNode, String itemIdentifier) throws Exception{
		if(manifestNode == null && (itemIdentifier == null || itemIdentifier.equals(""))){
			throw new NullPointerException("Manifest node or Item identifier is null.");
		}
		
		// manifest 直下の metadata の場合は、manifestNodeが、そうでない場合は、itemIdentifier がわたってくる
		boolean isTopMetadata = (manifestNode != null);
			
		LomInfo lomInfo = null;
		Node metadataNode = null;

		if(isTopMetadata){
			lomInfo = new LomInfo();
			lomInfo.setResourceIdentifier(null);
			metadataNode = ctrl.getNode(manifestNode, "./:metadata");
			
			
		}else{
			Node itemNode = this.getItem(itemIdentifier);
			
			// アイテムが見つからない場合は脱出
			if(itemNode == null){
				return null;
			}
	
			// 該当のノードが見つかったら、itemIdentifier を取得
			NamedNodeMap attrs = itemNode.getAttributes();
			lomInfo = new LomInfo(attrs.getNamedItem("identifier").getNodeValue());
	
	
			// identifierrefがある場合は、そのIDを取得し、リソースを検索
			Node resourceNode = null;
			if(attrs.getNamedItem("identifierref") != null){
				lomInfo.setResourceIdentifier(attrs.getNamedItem("identifierref").getNodeValue());
				resourceNode = this.getResource(attrs.getNamedItem("identifierref").getNodeValue());
			}else{
				// refが無い場合は、その下に直接 meta-data がぶら下がっているものと見なし、
				// itemNode を リソースノードに見立てる
				resourceNode = itemNode;
			}
			
			// リソースが見つからない場合は脱出
			if(resourceNode == null){
				return null;
			}
		
			// 配下の metadata ノードを取得
			metadataNode = ctrl.getNode(resourceNode, ".//:metadata");
		}
		
		// metadata が見つからない場合は脱出
		if(metadataNode == null){
			return null;
		}

		// メタデータの下の lom ノードを探す
		Node lomNode = this.getInlineLomNode(metadataNode);
		
		// 存在しなければ、location ノードを探し、参照先から lom ノードを取得
		if(lomNode == null){
			lomNode = this.getLocatedLomNode(metadataNode);
			
			// それでも存在しない場合は脱出
			if(lomNode == null){
				return null;
			}
			
			lomInfo.setInline(false);
			lomInfo.setLocation(this.getLocationPath(metadataNode));
						
		}else{
			lomInfo.setInline(true);
			lomInfo.setLocation(null);
		}
		
		lomInfo.setNode(lomNode);			
		
		
		return lomInfo;
	}
	

	
	private Node getInlineLomNode(Node metadataNode) throws Exception{
		if(metadataNode == null){
			throw new NullPointerException("Metadata node is null.");
		}
		
		Node lomNode = null;

		NodeList n = metadataNode.getChildNodes();
		for(int i = 0; i < n.getLength(); i++){
			Node x = n.item(i);
			if(x.getNodeName().equals("lom") || x.getNodeName().equals("lom:lom")){
				lomNode = x;
				break;
			}
		}
		
		return lomNode;
	}
	
	
	private Node getLocatedLomNode(Node metadataNode) throws Exception{
		if(metadataNode == null){
			throw new NullPointerException("Metadata node is null.");
		}

		Node lomNode = null;
		
		String locationPath = this.getLocationPath(metadataNode);
		
		lomNode = ctrl.parse(new File(locationPath));
		return lomNode;
		
	}
	
	
	private String getLocationPath(Node metadataNode) throws Exception{
		if(metadataNode == null){
			throw new NullPointerException("Metadata node is null.");
		}
		
		// メタデータの下の location または lom ノードを探す
		Node locationNode = ctrl.getNode(metadataNode, ".//adlcp:location");

		// 見つからなければ脱出
		if(locationNode == null){
			return null;
		}
			
		File locationFile = new File(locationNode.getFirstChild().getNodeValue());
		
		// locationのxmlファイルを開いて展開
		String locationPath = null;
		if(locationFile.isAbsolute()){
			locationPath = locationNode.getFirstChild().getNodeValue();			
		}else{
			locationPath = this.metadataDirectory.getPath() + File.separator + locationNode.getFirstChild().getNodeValue();				
		}
		
		return locationPath;
		
	}
	
	
	private Node getItem(String identifier) throws Exception{
		if(identifier == null || identifier.equals("")){
			throw new NullPointerException("Identifier is null.");
		}
		
		Node itemNode = null;
		
		// item ノードを探す
		for(int m = 0; m < this.manifestNodeList.getLength(); m++){

			Node manifestNode = this.manifestNodeList.item(m);
			
			itemNode = ctrl.getNode(manifestNode, ".//:organizations/:organization//:item[@identifier='" + identifier + "']");

			if(itemNode != null){
				break;
			}			
		}

		return itemNode;
	
	}
	
	
	
	private Node getResource(String identifierRef) throws Exception{
		if(identifierRef == null || identifierRef.equals("")){
			throw new NullPointerException("Identifierref is null.");
		}
				
		Node resNode = null;
		
		// item に対応するリソースを探す
		for(int m = 0; m < this.manifestNodeList.getLength(); m++){

			Node manifestNode = this.manifestNodeList.item(m);
			
			resNode = ctrl.getNode(manifestNode, ".//:resources/:resource[@identifier='" + identifierRef + "']");

			if(resNode != null){
				break;
			}			
		}
		
		return resNode;
	}
	
	

	
	
}
