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

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

import railo.commons.io.ModeUtil;
import railo.commons.io.res.ContentType;
import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourceProvider;
import railo.commons.io.res.util.ResourceSupport;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;


/**
 * a ram resource
 */
public final class RamResource extends ResourceSupport {
	
	private final RamResourceProviderOld provider;
	
	private final String parent;
	private final String name;
	private RamResourceCore _core;

	RamResource(RamResourceProviderOld provider, String path) {
		this.provider=provider;
		if(path.equals("/") || StringUtil.isEmpty(path)) {
		//if(path.equals("/")) {
			this.parent=null;
			this.name="";
		}
		else {
			String[] pn = ResourceUtil.translatePathName(path);
			this.parent=pn[0];
			this.name=pn[1];
		}
		
	}
	
	private RamResource(RamResourceProviderOld provider, String parent,String name) {
		this.provider=provider;
		this.parent=parent ;
		this.name=name;
	}
	
	RamResourceCore getCore() {
		if(_core==null || _core.getType()==0) {
			_core=provider.getCore(getInnerPath());
		}
		return _core;
	}
	

	void removeCore() {
		if(_core==null)return;
		_core.remove();
		_core=null;
	}
	
	private RamResourceCore createCore(int type) throws IOException {
		return _core=provider.createCore(getInnerPath(),type);
	}
	

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

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

	@Override
	public String getParent() {
		if(isRoot()) return null;
		return provider.getScheme().concat("://").concat(ResourceUtil.translatePath(parent, true, false));
	}

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

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

	@Override
	public void remove(boolean force) throws IOException {
		if(isRoot()) 
			throw new IOException("can't remove root resource ["+getPath()+"]");

		provider.read(this);
		RamResourceCore core = getCore();
		if(core==null)
			throw new IOException("can't remove resource ["+getPath()+"],resource does not exist");
		
		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);
			}
		}
		removeCore();
	}

	@Override
	public boolean exists() {
		try {
			provider.read(this);
		} catch (IOException e) {
			return true;
		}
		return getCore()!=null;
	}

	@Override
	public Resource getParentResource() {
		return getParentRamResource();
	}


	private RamResource getParentRamResource() {
		if(isRoot()) return null;
		return new RamResource(provider,parent);
	}

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

		return new RamResource(provider,realpath);
	}

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

	@Override
	public boolean isDirectory() {
		return exists() && getCore().getType()==RamResourceCore.TYPE_DIRECTORY;
	}

	@Override
	public boolean isFile() {
		return exists() && getCore().getType()==RamResourceCore.TYPE_FILE;
	}

	@Override
	public long lastModified() {
		if(!exists()) return 0;
		return getCore().getLastModified();
	}

	@Override
	public long length() {
		if(!exists()) return 0;
		byte[] data= getCore().getData();
		if(data==null) return 0;
		return data.length;
	}

	@Override
	public String[] list() {
		if(!exists()) return null;
		RamResourceCore core = getCore();
		if(core.getType()!=RamResourceCore.TYPE_DIRECTORY)
			return null;
		
		return core.getChildNames();
		/*List list = core.getChildren();
		if(list==null && list.size()==0) return new String[0];
		
		Iterator it = list.iterator();
		String[] children=new String[list.size()];
		RamResourceCore cc;
		int count=0;
		while(it.hasNext()) {
			cc=(RamResourceCore) it.next();
			children[count++]=cc.getName();
		}
		return children;*/
	}

	@Override
	public Resource[] listResources() {
		String[] list = list();
		if(list==null)return null;
		
		Resource[] children=new Resource[list.length];
		String p=getInnerPath();
		if(!isRoot())p=p.concat("/");
		for(int i=0;i<children.length;i++) {
			children[i]=new RamResource(provider,p,list[i]);
		}
		return children;
	}

	@Override
	public boolean setLastModified(long time) {
		if(!exists()) return false;
		getCore().setLastModified(time);
		return true;
	}

	@Override
	public boolean setReadOnly() {
		return setWritable(false);
	}

	@Override
	public void createFile(boolean createParentWhenNotExists) throws IOException {
		ResourceUtil.checkCreateFileOK(this,createParentWhenNotExists);
		provider.lock(this);
		try {
			createCore(RamResourceCore.TYPE_FILE);
		}
		finally {
			provider.unlock(this);
		}
	}


	@Override
	public void createDirectory(boolean createParentWhenNotExists) throws IOException {
		ResourceUtil.checkCreateDirectoryOK(this,createParentWhenNotExists);
		provider.lock(this);
		try {
			createCore(RamResourceCore.TYPE_DIRECTORY);
		}
		finally {
			provider.unlock(this);
		}
		
	}

	@Override
	public InputStream getInputStream() throws IOException {
		ResourceUtil.checkGetInputStreamOK(this);

		provider.lock(this);
		RamResourceCore core = getCore();
		
		byte[] data = core.getData();
		if(data==null)data=new byte[0];
		provider.unlock(this);
		return new ByteArrayInputStream(data);
	}

	public OutputStream getOutputStream(boolean append) throws IOException {
		ResourceUtil.checkGetOutputStreamOK(this);
		provider.lock(this);
		return new RamOutputStream(this,append);
	}

	public ContentType getContentType() {
		return ResourceUtil.getContentType(this);
	}

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

	/**
	 * This is useed by the MemoryResource too write back data to, that are written to outputstream
	 */
	class RamOutputStream extends ByteArrayOutputStream {

		private RamResource res;
		private boolean append;

		/**
		 * Constructor of the class
		 * @param res
		 */
		public RamOutputStream(RamResource res, boolean append) {
			this.append=append;
			this.res=res;
		}

		@Override
		public void close() throws IOException {
			try {
				super.close();
				RamResourceCore core = res.getCore();
				if(core==null)core=res.createCore(RamResourceCore.TYPE_FILE);
				
				core.setData(this.toByteArray(),append);
			}
			finally {
				res.getResourceProvider().unlock(res);
			}
		}
	}

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

	private boolean isRoot() {
		return parent==null;
	}
	
	public int getMode() {
		if(!exists())return 0;
		return getCore().getMode();
	}
	
	public void setMode(int mode) throws IOException {
		if(!exists())throw new IOException("can't set mode on resource ["+this+"], resource does not exist");
		getCore().setMode(mode);
	}
	@Override
	public boolean getAttribute(short attribute) {
		if(!exists())return false;
		return (getCore().getAttributes()&attribute)>0;
	}
	@Override
	public void setAttribute(short attribute, boolean value) throws IOException {
		if(!exists())throw new IOException("can't get attributes on resource ["+this+"], resource does not exist");
		int attr = getCore().getAttributes();
		if(value) {
			if((attr&attribute)==0) attr+=attribute;
		}
		else {
			if((attr&attribute)>0) attr-=attribute;
		}
		getCore().setAttributes(attr);
	}
	
}
