package railo.runtime.text.pdf;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.pdfbox.exceptions.CryptographyException;
import org.pdfbox.exceptions.InvalidPasswordException;
import org.pdfbox.pdmodel.PDDocument;

import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.type.file.FileResource;
import railo.commons.lang.StringUtil;
import railo.runtime.PageContext;
import railo.runtime.dump.DumpData;
import railo.runtime.dump.DumpProperties;
import railo.runtime.dump.DumpTable;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.PageRuntimeException;
import railo.runtime.op.Caster;
import railo.runtime.type.Collection;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.dt.DateTime;
import railo.runtime.type.util.StructSupport;

import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;

public class PDFDocument extends StructSupport implements Struct {

	private byte[] barr;
	private String password;
	private Resource resource;
	private Set<Integer> pages;

	public PDFDocument(byte[] barr, String password) {
		this.barr=barr;
		this.password=password;
	}

	public PDFDocument(Resource resource, String password) {
		this.resource=resource;
		this.password=password;
	}

	public PDFDocument(byte[] barr, Resource resource, String password) {
		this.resource=resource;
		this.barr=barr;
		this.password=password;
	}
	

	@Override
	public void clear() {
		getInfo().clear();
	}


	@Override
	public boolean containsKey(Key key) {
		return getInfo().containsKey(key);
	}

	@Override
	public Collection duplicate(boolean deepCopy) {
		PDFDocument duplicate=new PDFDocument(barr,resource,password);
		return duplicate;
	}
	

	@Override
	public Object get(Key key) throws PageException {
		return getInfo().get(key);
	}

	@Override
	public Object get(Key key, Object defaultValue) {
		return getInfo().get(key, defaultValue);
	}

	@Override
	public Key[] keys() {
		return getInfo().keys();
	}

	@Override
	public Object remove(Key key) throws PageException {
		return getInfo().remove(key);
	}

	@Override
	public Object removeEL(Key key) {
		return getInfo().removeEL(key);
	}

	@Override
	public Object set(Key key, Object value) throws PageException {
		return getInfo().set(key, value);
	}

	@Override
	public Object setEL(Key key, Object value) {
		return getInfo().setEL(key, value);
	}

	@Override
	public int size() {
		return getInfo().size();
	}

	@Override
	public DumpData toDumpData(PageContext pageContext, int maxlevel,DumpProperties properties) {
		
		DumpData dd = getInfo().toDumpData(pageContext, maxlevel,properties);
		if(dd instanceof DumpTable)((DumpTable)dd).setTitle("Struct (PDFDocument)");
		return dd;
	}

	@Override
	public Iterator<Collection.Key> keyIterator() {
		return getInfo().keyIterator();
	}
    
    @Override
	public Iterator<String> keysAsStringIterator() {
    	return getInfo().keysAsStringIterator();
    }
	
	@Override
	public Iterator<Entry<Key, Object>> entryIterator() {
		return getInfo().entryIterator();
	}
	
	@Override
	public Iterator<Object> valueIterator() {
		return getInfo().valueIterator();
	}

	@Override
	public boolean castToBooleanValue() throws PageException {
		return getInfo().castToBooleanValue();
	}
    
    @Override
    public Boolean castToBoolean(Boolean defaultValue) {
        return getInfo().castToBoolean(defaultValue);
    }

	@Override
	public DateTime castToDateTime() throws PageException {
		return getInfo().castToDateTime();
	}
    
    @Override
    public DateTime castToDateTime(DateTime defaultValue) {
        return getInfo().castToDateTime(defaultValue);
    }

	@Override
	public double castToDoubleValue() throws PageException {
		return getInfo().castToDoubleValue();
	}
    
    @Override
    public double castToDoubleValue(double defaultValue) {
        return getInfo().castToDoubleValue(defaultValue);
    }

	@Override
	public String castToString() throws PageException {
		return getInfo().castToString();
	}
	@Override
	public String castToString(String defaultValue) {
		return getInfo().castToString(defaultValue);
	}

	@Override
	public int compareTo(String str) throws PageException {
		return getInfo().compareTo(str);
	}

	@Override
	public int compareTo(boolean b) throws PageException {
		return getInfo().compareTo(b);
	}

	@Override
	public int compareTo(double d) throws PageException {
		return getInfo().compareTo(d);
	}

	@Override
	public int compareTo(DateTime dt) throws PageException {
		return getInfo().compareTo(dt);
	}
///////////////////////////////////////////////
	
	public PdfReader getPdfReader() throws ApplicationException {
		try {
			if(barr!=null) {
				if(password!=null)return new PdfReader(barr,password.getBytes());
				return new PdfReader(barr);
			}
			if(password!=null)return new PdfReader(IOUtil.toBytes(resource),password.getBytes());
			return new PdfReader(IOUtil.toBytes(resource));
		}
		catch(IOException ioe) {
			throw new ApplicationException("can not load file ["+resource+"]",ioe.getMessage());
		}
	}
	
	private String getFilePath() {
		if(resource==null) return "";
		return resource.getAbsolutePath();
	}

	public Struct getInfo()  {

		PdfReader pr=null;
		try {
			pr=getPdfReader();
			//PdfDictionary catalog = pr.getCatalog();
			int permissions = pr.getPermissions();
			boolean encrypted=pr.isEncrypted();
			
			Struct info=new StructImpl();
			info.setEL("FilePath", getFilePath());
			
			// access
			info.setEL("ChangingDocument", allowed(encrypted,permissions,PdfWriter.ALLOW_MODIFY_CONTENTS));
			info.setEL("Commenting", allowed(encrypted,permissions,PdfWriter.ALLOW_MODIFY_ANNOTATIONS));
			info.setEL("ContentExtraction", allowed(encrypted,permissions,PdfWriter.ALLOW_SCREENREADERS));
			info.setEL("CopyContent", allowed(encrypted,permissions,PdfWriter.ALLOW_COPY));
			info.setEL("DocumentAssembly", allowed(encrypted,permissions,PdfWriter.ALLOW_ASSEMBLY+PdfWriter.ALLOW_MODIFY_CONTENTS));
			info.setEL("FillingForm", allowed(encrypted,permissions,PdfWriter.ALLOW_FILL_IN+PdfWriter.ALLOW_MODIFY_ANNOTATIONS));
			info.setEL("Printing", allowed(encrypted,permissions,PdfWriter.ALLOW_PRINTING));
			info.setEL("Secure", "");
			info.setEL("Signing", allowed(encrypted,permissions,PdfWriter.ALLOW_MODIFY_ANNOTATIONS+PdfWriter.ALLOW_MODIFY_CONTENTS+PdfWriter.ALLOW_FILL_IN));
			
			info.setEL("Encryption", encrypted?"Password Security":"No Security");// MUST
			info.setEL("TotalPages", Caster.toDouble(pr.getNumberOfPages()));
			info.setEL("Version", "1."+pr.getPdfVersion());
			info.setEL("permissions", ""+permissions);
			info.setEL("permiss", ""+PdfWriter.ALLOW_FILL_IN);
			
			info.setEL("Application", "");
			info.setEL("Author", "");
			info.setEL("CenterWindowOnScreen", "");
			info.setEL("Created", "");
			info.setEL("FitToWindow", "");
			info.setEL("HideMenubar", "");
			info.setEL("HideToolbar", "");
			info.setEL("HideWindowUI", "");
			info.setEL("Keywords", "");
			info.setEL("Language", "");
			info.setEL("Modified", "");
			info.setEL("PageLayout", "");
			info.setEL("Producer", "");
			info.setEL("Properties", "");
			info.setEL("ShowDocumentsOption", "");
			info.setEL("ShowWindowsOption", "");
			info.setEL("Subject", "");
			info.setEL("Title", "");
			info.setEL("Trapped", "");
	
			// info
			HashMap imap = pr.getInfo();
			Iterator it = imap.entrySet().iterator();
			Map.Entry entry;
			while(it.hasNext()) {
				entry=(Entry) it.next();
				info.setEL(Caster.toString(entry.getKey(),null), entry.getValue());
			}
			return info;
		}
		catch(PageException pe) {
			throw new PageRuntimeException(pe);
		}
		finally {
			if(pr!=null)pr.close();
		}
	}
	

	

	private static Object allowed(boolean encrypted, int permissions, int permission) {
		return (!encrypted || (permissions&permission)>0)?"Allowed":"Not Allowed";
	}



	public void setPages(String strPages) throws PageException {
		if(StringUtil.isEmpty(strPages))return;
		if(pages==null)
			pages=new HashSet<Integer>();
		PDFUtil.parsePageDefinition(pages,strPages);
	}

	public Set<Integer> getPages() {
		//if(pages==null)pages=new HashSet();
		return pages;
	}

	public Resource getResource() {
		return resource;
	}
	public byte[] getRaw() throws IOException {
		if(barr!=null)return barr;
		return IOUtil.toBytes(resource);
	}

	@Override
	public boolean containsValue(Object value) {
		return getInfo().containsValue(value);
	}

	@Override
	public java.util.Collection values() {
		return getInfo().values();
	}
	
	public PDDocument toPDDocument() throws CryptographyException, InvalidPasswordException, IOException {
		PDDocument doc;
		if(barr!=null) 
			doc= PDDocument.load(new ByteArrayInputStream(barr,0,barr.length));
		else if(resource instanceof FileResource)
			doc= PDDocument.load((File)resource);
		else 
			doc= PDDocument.load(new ByteArrayInputStream(IOUtil.toBytes(resource),0,barr.length));
		
		if(password!=null)doc.decrypt(password);
		
		
		return doc;
		
	}

}
