package jp.hasc.hasctool.ui.commands;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.List;

import jp.hasc.hasctool.core.runtime.AbstractTask;
import jp.hasc.hasctool.core.runtime.FileStreamProvider;
import jp.hasc.hasctool.core.runtime.RuntimeContext;
import jp.hasc.hasctool.core.util.CoreUtil;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;

/**
 * Resourceに読み書きするIOStreamを提供します
 * @author iwasaki
 */
public class ResourceStreamProvider implements
		FileStreamProvider {
	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(ResourceStreamProvider.class);
	
	public static final String KEYWORD_PROJECT_ROOT="$(projectRoot)";
	public static final String KEYWORD_WORKSPACE_ROOT="$(workspaceRoot)";

	private final IContainer baseContainer_;
	private final RuntimeContext runtimeContext_;

	public ResourceStreamProvider(IContainer baseContainer,
			RuntimeContext runtimeContext) {
		baseContainer_ = baseContainer;
		runtimeContext_ = runtimeContext;
	}
	
	public static final String SCHEME_UNBUFFERED_RESOURCE="unbufferedresource:";
	public static final String SCHEME_BUFFERED_RESOURCE="bufferedresource:";
	
	private static boolean isUsingMemoryOutputStream(String path) {
		if (path.startsWith(SCHEME_UNBUFFERED_RESOURCE)) return false;
		//if (path.startsWith(SCHEME_BUFFERED_RESOURCE)) return true;
		return true;
	}
	
	private String deleteScheme(String path) {
		if (path.startsWith(SCHEME_UNBUFFERED_RESOURCE)) return path.substring(SCHEME_UNBUFFERED_RESOURCE.length());
		if (path.startsWith(SCHEME_BUFFERED_RESOURCE)) return path.substring(SCHEME_BUFFERED_RESOURCE.length());
		return path;
	}
	
	@Override
	public OutputStream openOutputStream(String path){
		if (path==null) throw new NullPointerException("path is null");
		boolean usingMemoryOutputStream=isUsingMemoryOutputStream(path);
		path=deleteScheme(path);
		final String fpath = path;
		if (usingMemoryOutputStream) {
			//　全てを一旦メモリ上へ
			ByteArrayOutputStream baout=new ByteArrayOutputStream() {
				@Override
				public void close() throws IOException {
					super.close();
					try {
						ByteArrayInputStream bain=new ByteArrayInputStream(toByteArray());
						IFile outf = getFile(fpath);
						try{
							mkdirs(outf.getParent());
						}catch(Exception ex) {
							LOG.warn("Exception",ex);
						}
						ProgressMonitorForBlocking blk = new ProgressMonitorForBlocking();
						if (outf.exists()) {
							outf.setContents(bain, true, false, blk);
						}else{
							outf.create(bain, true, blk);
						}
						blk.awaitDone();
						bain.close();
						LOG.info("wrote file: "+outf.getFullPath());
					} catch (Exception ex) {
						CoreUtil.throwAsRuntimeException(ex);
					}
				}
			};
			return baout;
		}else{
			
			// Taskが止まらないことがある？
			try {
				final PipedInputStream pis = new PipedInputStream();
				final PipedOutputStream pos = new PipedOutputStream(pis);
				AbstractTask task = new AbstractTask(){
					@Override
					protected void run() throws InterruptedException {
						try {
							IFile outf = getFile(fpath);
							try{
								mkdirs(outf.getParent());
							}catch(Exception ex) {
								LOG.warn("Exception on mkdirs: "+ex.toString());
							}
							ProgressMonitorForBlocking blk = new ProgressMonitorForBlocking();
							if (outf.exists()) {
								outf.setContents(pis, true, false, blk);
							}else{
								outf.create(pis, true, blk);
							}
							blk.awaitDone();
							pis.close();
							LOG.info("wrote file: "+outf.getFullPath());
						} catch (Exception ex) {
							CoreUtil.throwAsRuntimeException(ex);
						}
					}
				};
				task.setup(runtimeContext_);
				task.start();
				return pos;
			} catch (IOException ex) {
				CoreUtil.throwAsRuntimeException(ex);
				return null;
			}
		}
	}

	@Override
	public InputStream openInputStream(String path){
		if (path==null) throw new NullPointerException("path is null");
		path=deleteScheme(path);
		try {
			return getFile(path).getContents();
		} catch (CoreException ex) {
			CoreUtil.throwAsRuntimeException(ex);
			return null;
		}
	}

	public RuntimeContext getRuntimeContext() {
		return runtimeContext_;
	}
	
	public IFile getFile(String path) throws CoreException {
		path=deleteScheme(path);
		if (path.startsWith(KEYWORD_PROJECT_ROOT)) {
			// プロジェクトパス
			return baseContainer_.getProject().getFile(path.substring(KEYWORD_PROJECT_ROOT.length()));
			//fileName=baseContainer_.getProject().getFullPath().toString()+fileName.substring(KEYWORD_PROJECT_ROOT.length());
		}
		
		if (path.startsWith(KEYWORD_WORKSPACE_ROOT)) {
			// Workspaceパス
			return baseContainer_.getWorkspace().getRoot().getFile(new Path(path.substring(KEYWORD_WORKSPACE_ROOT.length())));
		}
		
		if (path.startsWith("/")) {
			// 絶対パス
			return baseContainer_.getWorkspace().getRoot().getFile(new Path(path));
		}
		
		// 相対パス
		return baseContainer_.getFile(new Path(path));
	}
	
	public IFolder getFolder(String path) throws CoreException {
		path=deleteScheme(path);
		if (path.startsWith(KEYWORD_PROJECT_ROOT)) {
			// プロジェクトパス
			return baseContainer_.getProject().getFolder(path.substring(KEYWORD_PROJECT_ROOT.length()));
			//fileName=baseContainer_.getProject().getFullPath().toString()+fileName.substring(KEYWORD_PROJECT_ROOT.length());
		}
		
		if (path.startsWith(KEYWORD_WORKSPACE_ROOT)) {
			// Workspaceパス
			return baseContainer_.getWorkspace().getRoot().getFolder(new Path(path.substring(KEYWORD_WORKSPACE_ROOT.length())));
		}
		
		if (path.startsWith("/")) {
			// 絶対パス
			return baseContainer_.getWorkspace().getRoot().getFolder(new Path(path));
		}
		
		// 相対パス
		return baseContainer_.getFolder(new Path(path));
	}
	
	private void mkdirs(IContainer container) throws CoreException, InterruptedException {
		if (!container.exists() && container instanceof IFolder) {
			mkdirs(container.getParent());
			ProgressMonitorForBlocking blk = new ProgressMonitorForBlocking();
			IFolder folder = (IFolder)container;
			folder.create(true, true, blk);
			blk.awaitDone();
		}
	}

	@Override
	public void listFileNames(String folderPath, List<String> out) {
		folderPath=deleteScheme(folderPath);
		try{
			IFolder folder = getFolder(folderPath);
			for(IResource r:folder.members()) {
				if (r.getType()!=IResource.FILE) continue;
				String cname = r.getName();
				if (isFileNameToIgnore(cname)) continue;
				out.add(cname);
			}
		}catch(Exception ex) {
			CoreUtil.throwAsRuntimeException(ex);
		}
	}

	static boolean isFileNameToIgnore(String cname) {
		return cname.equals(".cvsignore") || cname.equals(".gitignore");
	}

	@Override
	public void listSubFolderNames(String folderPath, List<String> out) {
		folderPath=deleteScheme(folderPath);
		try{
			IFolder folder = getFolder(folderPath);
			for(IResource r:folder.members()) {
				if (r.getType()!=IResource.FOLDER) continue;
				String cname = r.getName();
				if (isFolderNameToIgnore(cname)) continue;
				out.add(cname);
			}
		}catch(Exception ex) {
			CoreUtil.throwAsRuntimeException(ex);
		}
	}

	static boolean isFolderNameToIgnore(String cname) {
		return cname.equals(".svn") || cname.equals(".git") || cname.equals(".") || cname.equals("..");
	}
}
