package com.infoteria.asteria.flowlibrary2.component.storage;

import com.infoteria.asteria.flowengine2.execute.ExecuteContext;
import com.infoteria.asteria.flowengine2.execute.Transaction;
import com.infoteria.asteria.flowengine2.flow.InputConnector;
import com.infoteria.asteria.flowengine2.resource.ExecuteInfo;
import com.infoteria.asteria.flowlibrary2.component.ComponentException;
import com.infoteria.asteria.flowlibrary2.component.ComponentExceptionByMessageCode;
import com.infoteria.asteria.flowlibrary2.component.SimpleComponent;
import com.infoteria.asteria.flowlibrary2.FlowException;
import com.infoteria.asteria.flowlibrary2.property.BooleanProperty;
import com.infoteria.asteria.flowlibrary2.property.StringProperty;
import com.infoteria.asteria.flowlibrary2.property.PathResolverProperty;
import com.infoteria.asteria.flowlibrary2.property.EncodingProperty;
import com.infoteria.asteria.flowlibrary2.property.ExceptionProperty;
import com.infoteria.asteria.flowlibrary2.stream.StreamDataContainer;
import com.infoteria.asteria.flowlibrary2.stream.StreamDataObject;
import com.infoteria.asteria.flowlibrary2.stream.StreamException;
import com.infoteria.asteria.flowlibrary2.stream.StreamFactory;
import com.infoteria.asteria.flowlibrary2.stream.StreamFactoryCSV;
import com.infoteria.asteria.flowlibrary2.stream.StreamFactoryText;
import com.infoteria.asteria.flowlibrary2.stream.StreamType;
import org.apache.tools.ant.DirectoryScanner;
import com.infoteria.asteria.util.file.FileUtil;
import com.infoteria.asteria.value.Value;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * ファイルを取得するコンポーネント
 */
public class FileSystemGetComponent extends SimpleComponent implements Transaction {
	
	public static final String COMPONENT_NAME = "FileSystem(Get)";
	public String getComponentName() { return COMPONENT_NAME;}
	
	private static final String INVALID_FILE_PATH    = "1";
	private static final String FIND_FILE_COUNT      = "2";
	private static final String FILE_NOT_FOUND       = "3";
	private static final String FILE_READONLY        = "4";
	private static final String FAIL_DELETE_FILE     = "5";
	private static final String DELETE_FILE          = "6";
	private static final String READ_FILE            = "7";
	
	private static final String PROPERTY_FILESIZE    = "FileSize";
	private static final String PROPERTY_FILEDATE    = "FileDate";
	
	private static final int STATE_FILE_NOT_FOUND = 1;
	
	private StringProperty _filePath = new StringProperty("FilePath", true, true);
	private BooleanProperty _fileDelete = new BooleanProperty("FileDelete", false, false, false);
	private BooleanProperty _loop = new BooleanProperty("LoopProcess", false, false, false);
	private EncodingProperty _fileEncoding = new EncodingProperty("FileEncoding", true, true, "(AutoDetect)");
	private PathResolverProperty _homeMode = new PathResolverProperty("HomeDirectory", PathResolverProperty.STR_RELATIVE);
	private ExceptionProperty _fileNotFound = new ExceptionProperty("FileNotFoundException", STATE_FILE_NOT_FOUND);
	private BooleanProperty _allowAbsolute = new BooleanProperty("AllowAbsolute", false, false, true);
	private BooleanProperty _allowUpDir = new BooleanProperty("AllowUpDir", false, false, true);
	
	private LinkedList _loopList = null;
	private ArrayList _deleteList = null;
	
	public FileSystemGetComponent() {
		getInputConnector().setAcceptType(StreamType.ALL);
		getInputConnector().setAcceptLinkCount(InputConnector.LINK_UNBOUNDED);
		getOutputConnector().setAcceptType(StreamType.BINARY|
		                                   StreamType.TEXT|
		                                   StreamType.HTML|
		                                   StreamType.CSV|
		                                   StreamType.FIXED_STRING|
		                                   StreamType.MIME|
		                                   StreamType.XML);
		
		registProperty(_filePath);
		registProperty(_fileDelete);
		registProperty(_loop);
		registProperty(_fileEncoding);
		registProperty(_homeMode);
		registProperty(_fileNotFound);
		registProperty(_allowAbsolute);
		registProperty(_allowUpDir);
	}
	
	public boolean loopPossibility() {
		return _loop.booleanValue();
	}
	
	public void init(ExecuteContext context) throws FlowException {
		if (_fileDelete.booleanValue())
			_deleteList = new ArrayList();
		_homeMode.setAllowAbsolute(_allowAbsolute.booleanValue());
		_homeMode.setAllowUpDir(_allowUpDir.booleanValue());
	}
	
	public void term(ExecuteContext context) {
		_loopList = null;
	}
	
	public boolean execute(ExecuteContext context) throws FlowException {
		if (_fileDelete.booleanValue())
			context.addTransaction(this);
		File path = _homeMode.getFile(context, _filePath.strValue());
		File basedir = path.getParentFile();
		if (basedir == null)
			throw new ComponentExceptionByMessageCode(this, INVALID_FILE_PATH, _filePath.strValue());
		
		List result = getFileList(path);
		if (result.size() == 0)
			throw new ComponentExceptionByMessageCode(this, FILE_NOT_FOUND, path, STATE_FILE_NOT_FOUND);
		else if (result.size() > 1) {
			context.debugInfo(getMessage(FIND_FILE_COUNT, Integer.toString(result.size())));
			result = new ArrayList(result);
			Collections.sort(result);
		}
		
		if (_loop.booleanValue() && result.size() > 1) {
			//ループが発生する場合
			_loopList = new LinkedList(result);
			File file = (File)_loopList.removeFirst();
			StreamDataObject stream = createStream(file, context);
			getOutputConnector().setStream(stream);
			return false;
		} else if (result.size() > 1) {
			//コンテナ化する場合
			StreamFactory factory = getOutputConnector().getStreamFactory();
			StreamDataContainer container = new StreamDataContainer(factory.getType());
			for (int i=0; i < result.size(); i++) {
				File file = (File)result.get(i);
				StreamDataObject stream = createStream(file, context);
				container.addStream(stream);
			}
			getOutputConnector().setStream(container);
			return true;
		} else {//result.size == 1
			//1ファイルのみがマッチした場合
			File file = (File)result.get(0);
			StreamDataObject stream = createStream(file, context);
			getOutputConnector().setStream(stream);
			return true;
		}
	}
	
	public int executeLoop(ExecuteContext context) throws FlowException {
		if (_fileDelete.booleanValue())
			context.addTransaction(this);
		File file = (File)_loopList.removeFirst();
		StreamDataObject stream = createStream(file, context);
		getOutputConnector().setStream(stream);
		return (_loopList.size() == 0 ? LOOP_END : LOOP_CONTINUE);
	}
	
	public void commit(ExecuteContext context) throws FlowException {
		if ( !_fileDelete.booleanValue())
			return;
		
		try {
			for (int i=0; i < _deleteList.size(); i++) {
				File file = (File)_deleteList.get(i);
				if (!file.exists())
					continue;
				
				if ( !file.canWrite()) {
					context.error(getMessage(FILE_READONLY, file.getName()));
					continue;
				}
				if ( !file.delete())
					throw new ComponentExceptionByMessageCode(this, FAIL_DELETE_FILE, file.getName());
				context.debugInfo(getMessage(DELETE_FILE, file.getName()));
			}
		} finally {
			_deleteList.clear();
		}
	}
	
	public void rollback(ExecuteContext context) {
		if (_fileDelete.booleanValue())
			_deleteList.clear();
	}
	
	private static String getCanonicalPath(File file) {
		try {
			return file.getCanonicalPath();
		} catch (IOException e) {
			//not occur
			e.printStackTrace();
			return null;
		}
	}
	
	private StreamDataObject createStream(File file, ExecuteContext context) throws StreamException {
		StreamFactory factory = getOutputConnector().getStreamFactory();
		StreamDataObject stream = null;
		int type = factory.getType();
		if (type == StreamType.XML)
			stream =  factory.create(file);
		else if (type == StreamType.CSV) {
			String enc = _fileEncoding.getEncoding();
			if (_fileEncoding.isAutoDetect())
				enc = null;
			stream = ((StreamFactoryCSV)factory).create(file, enc);
		}
		
		if (stream == null) {
			byte[] data = null;
			try {
				data = FileUtil.readFile(file);
			} catch (IOException e) {
				throw new StreamException(ExecuteInfo.STREAM_IO_EXCEPTION, e);
			}
			switch (factory.getType()) {
				case StreamType.BINARY:
				case StreamType.FIXED_STRING:
				case StreamType.MIME:
				//case StreamType.XML:
					stream = factory.create(data);
					break;
				case StreamType.TEXT:
				case StreamType.HTML:
				//case StreamType.CSV:
					//(AutoDetect)の場合はJISAutoDetectではなくストリームのコンストラクタの
					//AutoDetect機構を使用する
					String enc = _fileEncoding.getEncoding();
					if (_fileEncoding.isAutoDetect())
						enc = null;
					stream = ((StreamFactoryText)factory).create(data, enc);
					break;
				default:
					throw new IllegalStateException();
			}
		}
		String path = getCanonicalPath(file);
		stream.putStreamProperty(StreamDataObject.PROPERTY_FILENAME, new Value(file.getName()));
		stream.putStreamProperty(StreamDataObject.PROPERTY_FILEPATH, new Value(path));
		stream.putStreamProperty(PROPERTY_FILEDATE, new Value(new Date(file.lastModified())));
		stream.putStreamProperty(PROPERTY_FILESIZE, new Value(file.length()));
		
		context.debugInfo(getMessage(READ_FILE, path));
		if (_fileDelete.booleanValue() && !_deleteList.contains(file))
			_deleteList.add(file);
		return stream;
	}
	
	public void setExceptionParam(FlowException e) {
		e.addParam(_filePath.getName(), _filePath.getValue());
	}
	
	//CSVGetComponentでもこのメソッドを使用している
	public static List getFileList(File path) throws ComponentException {
		ArrayList list = new ArrayList();
		String s = path.toString();
		try {
			s = path.getCanonicalPath();
		} catch (IOException e) {
		}
		
		s = s.replace(File.separatorChar == '\\' ? '/' : '\\', File.separatorChar);
		int idx = getWildcardIndex(s);
		if (idx >= 0) {
			idx = s.lastIndexOf(File.separatorChar, idx);
			if (idx == -1)
				return list;
			
			File basedir = new File(s.substring(0, idx+1));
			String pattern = s.substring(idx+1);
			
			DirectoryScanner ds = new DirectoryScanner();
			ds.setBasedir(basedir);
			ds.setCaseSensitive(File.separatorChar == '/');
			ds.setIncludes(new String[] { pattern });
			
			try {
				ds.scan();
			} catch (Exception e) {
				throw new ComponentException(e);
			}
			String[] files = ds.getIncludedFiles();
			for (int i=0; i<files.length; i++) {
				File f = new File(basedir, files[i]);
				list.add(f);
			}
		} else {
			if (path.exists() && path.isFile())
				list.add(path);
		}
		return list;
	}
	
	/**
	 * @deprecated getFileList(File path)を使用
	 */
	public static List getFileList(File basedir, String filename) throws ComponentException {
		return getFileList(new File(basedir, filename));
	}
	
	private static boolean isUseWildcard(String str) {
		return getWildcardIndex(str) != -1;
	}
	
	private static int getWildcardIndex(String str) {
		int len = str.length();
		for (int i=0; i<len; i++) {
			char c = str.charAt(i);
			if (c == '*' || c == '?')
				return i;
		}
		return -1;
	}
}
