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

import com.infoteria.asteria.flowengine2.EngineConstant;
import com.infoteria.asteria.flowengine2.execute.ExecuteContext;
import com.infoteria.asteria.flowengine2.flow.InputConnector;
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.stream.StreamDataBinary;
import com.infoteria.asteria.flowlibrary2.stream.StreamDataContainer;
import com.infoteria.asteria.flowlibrary2.stream.StreamDataObject;
import com.infoteria.asteria.flowlibrary2.stream.StreamFactory;
import com.infoteria.asteria.flowlibrary2.stream.StreamType;
import com.infoteria.asteria.flowlibrary2.property.StringProperty;
import com.infoteria.asteria.flowlibrary2.property.BooleanProperty;
import com.infoteria.asteria.util.file.PatternFilenameFilter;
import com.infoteria.asteria.value.Value;

import org.apache.oro.text.regex.MalformedPatternException;

import java.io.IOException;
import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
import java.util.ArrayList;
import java.util.Date;

/**
 * Zipを展開するコンポーネント
 */
public class UnZipComponent extends SimpleComponent {
	
	public static final String COMPONENT_NAME = "UnZip";
	public String getComponentName() { return COMPONENT_NAME;}
	public String getLicenseStr() { return EngineConstant.LICENSE_COREPLUS; }

	private static final String FILE_NOT_FOUND    = "1";
	private static final String JAPANESE_FILENAME = "2";
	private static final String FAIL_UNZIP        = "3";
	
	private static final String PROPERTY_FILESIZE    = "FileSize";
	private static final String PROPERTY_FILEDATE    = "FileDate";
	
	private StringProperty _filePath = new StringProperty("FilePath", false, true);
	private BooleanProperty _loop = new BooleanProperty("LoopProcess", false, false, true);
	
	//PatternFilenameFilterはワイルドカードを処理するための内部クラスです。
	//このクラスの仕様は非公開でありエンドユーザが使用することはできません。
	private PatternFilenameFilter _filter = null;
	private String _filterString = null;
	private ZipInputStream _zis = null;
	
	public UnZipComponent() {
		getInputConnector().setAcceptLinkCount(1);
		getInputConnector().setAcceptType(StreamType.BINARY);
		getOutputConnector().setAcceptType(StreamType.BINARY|
		                                   StreamType.TEXT|
		                                   StreamType.HTML|
		                                   StreamType.CSV|
		                                   StreamType.FIXED_STRING|
		                                   StreamType.MIME|
		                                   StreamType.XML);
		
		registProperty(_filePath);
		registProperty(_loop);
	}
	
	public boolean loopPossibility() {
		return _loop.booleanValue();
	}
	
	public void init(ExecuteContext context) throws FlowException {
		initFilter();
	}
	
	private void initFilter() throws FlowException {
		_filterString = _filePath.strValue();
		if ( _filterString.length() > 0) {
			_filter = new PatternFilenameFilter();
			try {
				_filter.addIncludes(_filterString);
			} catch (MalformedPatternException e) {
				throw new ComponentException(e, FAIL_UNZIP);
			}
		}
	}
	
	public void term(ExecuteContext context) {
		_filter = null;
		if (_zis != null) {
			try {
				_zis.close();
			} catch (IOException e) {
				context.error(e);
			}
			_zis = null;
		}
	}
	
	public boolean execute(ExecuteContext context) throws FlowException {
		if ( !_filterString.equals(_filePath.strValue()))
			initFilter();
		
		ByteArrayInputStream bis = new ByteArrayInputStream(getInputConnector().getStream().byteValue());
		_zis = new ZipInputStream(bis);
		if (_loop.booleanValue()) {
			StreamDataObject os = getNextStream(context);
			if (os == null)
				throw new ComponentException(getMessage(FILE_NOT_FOUND), FILE_NOT_FOUND);
			setOutputStream(os);
			return false;
		} else {
			ArrayList list = new ArrayList(30);
			StreamDataObject os = getNextStream(context);
			if (os == null)
				throw new ComponentException(getMessage(FILE_NOT_FOUND), FILE_NOT_FOUND);
			while (os != null) {
				list.add(os);
				os = getNextStream(context);
			}
			if (list.size() == 1)
				setOutputStream((StreamDataObject)list.get(0));
			else {
				StreamDataContainer container = new StreamDataContainer(StreamType.BINARY);
				for (int i=0; i<list.size(); i++)
					container.addStream((StreamDataObject)list.get(i));
				setOutputStream(container);
			}
			return true;
		}
	}
	
	public int executeLoop(ExecuteContext context) throws FlowException {
		StreamDataObject os = getNextStream(context);
		if (os == null)
			return LOOP_NOTHING;
		setOutputStream(os);
		return LOOP_CONTINUE;
	}
	
	private StreamDataObject getNextStream(ExecuteContext context) throws FlowException {
		try {
			ZipEntry entry = _zis.getNextEntry();
			while (entry != null) {
				if (entry.isDirectory()) {
					entry = _zis.getNextEntry();
					continue;
				}
				
				String filepath = entry.getName();
				if (_filter == null || _filter.accept(filepath)) {
					ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
					int c = _zis.read();
					while (c != -1) {
						bos.write((byte)c);
						c = _zis.read();
					}
					StreamFactory factory = getOutputConnector().getStreamFactory();
					StreamDataObject ret = factory.create(bos.toByteArray());
					ret.setFileName(filepath);	// actually, this set "FilePath"
					ret.putStreamProperty(StreamDataObject.PROPERTY_FILENAME, new Value(new File(filepath).getName()));
					ret.putStreamProperty(PROPERTY_FILEDATE, new Value(new Date(entry.getTime())));
					ret.putStreamProperty(PROPERTY_FILESIZE, new Value(entry.getSize()));
					context.debugInfo(filepath);
					return ret;
				}
				entry = _zis.getNextEntry();
			}
			return null;
		} catch (IOException e) {
			throw new ComponentException(e, FAIL_UNZIP);
		} catch (IllegalArgumentException e) {
			// 日本語ファイル名がある場合のエラーをキャッチ
			ComponentExceptionByMessageCode e2 = new ComponentExceptionByMessageCode(this, JAPANESE_FILENAME);
			e2.setOriginalException(e);
			throw e2;
		}
	}
}

