package railo.commons.io.res.type.datasource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import railo.commons.io.IOUtil;
import railo.commons.io.ModeUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourceProvider;
import railo.commons.io.res.type.datasource.DatasourceResourceProvider.ConnectionData;
import railo.commons.io.res.util.ResourceSupport;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.PageException;
import railo.runtime.exp.PageRuntimeException;
import railo.runtime.type.util.ArrayUtil;

public final class DatasourceResource extends ResourceSupport {

	

	private DatasourceResourceProvider provider;
	private String parent;
	private String name;
	private ConnectionData data;
	private int fullPathHash;
	private int pathHash;
	
	/**
	 * Constructor of the class
	 * @param provider
	 * @param data
	 * @param path
	 */
	DatasourceResource(DatasourceResourceProvider provider, ConnectionData data,String path) {
		this.provider=provider;
		this.data=data;
		if("/".equals(path)) {
			this.parent=null;
			this.name="";
		}
		else {
			String[] pn = ResourceUtil.translatePathName(path);
			this.parent=pn[0];
			this.name=pn[1];
		}
	}


	private int fullPathHash() {
		if(fullPathHash==0) fullPathHash=getInnerPath().hashCode();
		return fullPathHash;
	}
	
	private int pathHash() {
		if(pathHash==0 && parent!=null) pathHash=parent.hashCode();
		return pathHash;
	}

	private Attr attr() {
		return provider.getAttr(data,fullPathHash(),parent,name);
	}


	private boolean isRoot() {
		return parent==null;
	}
	
	@Override
	public void createDirectory(boolean createParentWhenNotExists) throws IOException {
		ResourceUtil.checkCreateDirectoryOK(this,createParentWhenNotExists);
		provider.create(data,fullPathHash(),pathHash(),parent,name,Attr.TYPE_DIRECTORY);
		
	}

	@Override
	public void createFile(boolean createParentWhenNotExists) throws IOException {
		ResourceUtil.checkCreateFileOK(this,createParentWhenNotExists);
		provider.create(data,fullPathHash(),pathHash(),parent,name,Attr.TYPE_FILE);
	}
	
	@Override
	public void remove(boolean force) throws IOException {
		ResourceUtil.checkRemoveOK(this);
		if(isRoot()) 
			throw new IOException("can't remove root resource ["+getPath()+"]");

		
		Resource[] children = listResources();
		if(children!=null && children.length>0) {
			if(!force) {
				throw new IOException("can't delete directory ["+getPath()+"], directory is not empty");
			}
			for(int i=0;i<children.length;i++) {
				children[i].remove(true);
			}
		}
		provider.delete(data,fullPathHash(),parent,name);
	}

	@Override
	public boolean exists() {
		return attr().exists();
	}

	@Override
	public InputStream getInputStream() throws IOException {
		ResourceUtil.checkGetInputStreamOK(this);
		return provider.getInputStream(data,fullPathHash(),parent,name);
	}

	@Override
	public int getMode() {
		return attr().getMode();
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public OutputStream getOutputStream(boolean append) throws IOException {
		ResourceUtil.checkGetOutputStreamOK(this);
		byte[] barr=null;
		
		if(append && !provider.concatSupported(data) && isFile()){
			try{
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				IOUtil.copy(getInputStream(), baos,true,true);
				barr = baos.toByteArray();
			}
			catch(Throwable t){
				
			}
		}
		
		OutputStream os = provider.getOutputStream(data,fullPathHash(),pathHash(),parent,name,append);
		if(!ArrayUtil.isEmpty(barr))IOUtil.copy(new ByteArrayInputStream(barr), os,true,false);
		return os;
	}

	@Override
	public String getParent() {
		if(isRoot()) return null;
		String p = (StringUtil.isEmpty(parent))?"/":parent;
		return provider.getScheme().concat("://").concat(data.key()).concat(ResourceUtil.translatePath(p, true, false));
		
	}

	@Override
	public Resource getParentResource() {
		return getParentDatasourceResource();
	}
	private DatasourceResource getParentDatasourceResource() {
		if(isRoot()) return null;
		return new DatasourceResource(provider,data,parent);
	}

	@Override
	public String getPath() {
		return provider.getScheme().concat("://").concat(data.key()).concat(getInnerPath());
	}
	private String getInnerPath() {
		if(parent==null) return "/";
		return parent.concat(name);
	}

	@Override
	public Resource getRealResource(String realpath) {
		realpath=ResourceUtil.merge(getInnerPath(), realpath);
		if(realpath.startsWith("../"))return null;

		return new DatasourceResource(provider,data,realpath);
	}

	@Override
	public ResourceProvider getResourceProvider() {
		return provider;
	}

	@Override
	public boolean isAbsolute() {
		return true;
	}

	@Override
	public boolean isDirectory() {
		return attr().isDirectory();
	}
	
	@Override
	public boolean isFile() {
		return attr().isFile();
	}

	@Override
	public boolean isReadable() {
		return ModeUtil.isReadable(getMode());
	}

	@Override
	public boolean isWriteable() {
		return ModeUtil.isWritable(getMode());
	}

	@Override
	public long lastModified() {
		return attr().getLastModified();
	}

	@Override
	public long length() {
		return attr().size();
	}

	@Override
	public Resource[] listResources() {
		if(!attr().isDirectory())return null;
		
		String path;
		if(parent==null) path= "/";
		else path=parent.concat(name).concat("/");
		
		
		Attr[] children=null;
		try {
			children = provider.getAttrs(data,path.hashCode(),path);
		} catch (PageException e) {
			throw new PageRuntimeException(e);
		}
		if(children==null) return new Resource[0];
		Resource[] attrs = new Resource[children.length];
		for(int i=0;i<children.length;i++) {
			// TODO optimieren, alle attr mitgeben
			attrs[i]=new DatasourceResource(provider,data,path+children[i].getName());
		}
		return attrs;
	}

	@Override
	public boolean setLastModified(long time) {
		if(!exists()) return false;
		return provider.setLastModified(data,fullPathHash(),parent,name,time);
	}

	@Override
	public void setMode(int mode) throws IOException {
		if(!exists())throw new IOException("can't set mode on resource ["+this+"], resource does not exist");
		provider.setMode(data,fullPathHash(),parent,name,mode);
	}


	@Override
	public void moveTo(Resource dest) throws IOException {
		super.moveTo(dest);// TODO
	}
	
	
	@Override
	public boolean setReadable(boolean readable) {
		if(!exists())return false;
		try {
			setMode(ModeUtil.setReadable(getMode(), readable));
			return true;
		} catch (IOException e) {
			return false;
		}
	}

	@Override
	public boolean setWritable(boolean writable) {
		if(!exists())return false;
		try {
			setMode(ModeUtil.setWritable(getMode(), writable));
			return true;
		} catch (IOException e) {
			return false;
		}
	}
	
	@Override
	public String toString() {
		return getPath();
	}

}
