package jp.co.corecorp.s3pdf;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.ObjectInputStream.GetField;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.util.Log;

public class S3LocalStore {
    private static final String STOREFOLDER = "data";
    private static final String STOREACCOUNT = "account";
    private static final String STOREINDEX = "index";
    private static final String STOREDATA = "data";
    private static final String STOREGADGET = "gadget";
    private static final String STOREMETADATA = "meta";
	private S3Context s3ctx;
	private String hostFolder;
	private String userFolder;
	private String user;
	private String name1;
	private String name2;
	
	public S3LocalStore(S3Context ctx) {
		s3ctx = ctx;
		hostFolder	= null;
	}
	
	private File getHostFolder() throws S3Exception {
		if( hostFolder == null ) {
			throw new S3Exception("not connected");
		}
		File fol = new File(hostFolder);
		return fol;
	}
	
	private File getUserFolder() throws S3Exception {
		if( userFolder == null ) {
			throw new S3Exception("not logged in");
		}
		File fol = new File(userFolder);
		return fol;
	}
	
	private File getServiceFolder(S3Application svc) throws S3Exception {
		File home = getUserFolder();
		File svcFile = new File(home, svc.getServiceName());
		return svcFile;
	}
	
	private File getResourceFolder(S3Application svc, S3Resource rsc) throws S3Exception {
		File svcFile =  getServiceFolder(svc);
		File rscFile = new File(svcFile, rsc.getResourceName());
		return rscFile;
	}
	
	private File getIndeFile(S3Application svc, S3Resource rsc) throws S3Exception {
		File rscFile = getResourceFolder(svc, rsc);
		return new File(rscFile, STOREINDEX);
	}
	
	private File getDataFile(S3Application svc, S3Resource rsc) throws S3Exception {
		File rscFile = getResourceFolder(svc, rsc);
		return new File(rscFile, STOREDATA);
	}
	
	private File getMetaFolder(S3Application svc, S3Resource rsc) throws S3Exception {
		File rscFile = getResourceFolder(svc, rsc);
		return new File(rscFile, STOREMETADATA);
	}
	
	private File getGadgetFile(S3Application svc, S3Resource rsc) throws S3Exception {
		File rscFile = getResourceFolder(svc, rsc);
		return new File(rscFile, STOREGADGET);
	}
	/**
	 * コピー元のパス[srcPath]からコピー先のパス[destPath]へファイルのコピー 
	 * を行います。
	 * @param srcPath    コピー元のパス
	 * @param destPath    コピー先のパス
	 * @param bufferSize    データの読み込みバッファサイズ(KB)です。
	 * @throws IOException    何らかの入出力処理例外が発生した場合
	 */
	public static void copyStream(String srcPath, String destPath,
	    int bufferSize) throws IOException {
	    InputStream in = new FileInputStream(srcPath);
	    OutputStream os = new FileOutputStream(destPath);
	    copyStream(in, os, bufferSize);
	}

	public static void copyStream(InputStream is, OutputStream os, int bufferSize) throws IOException {
		int len = -1;
		byte[] buf = new byte[bufferSize * 1024];
		while ((len = is.read(buf, 0, buf.length)) != -1) {
			os.write(buf, 0, len);
		}
		os.flush();
		if (is != null) {
			is.close();
		}
		if (os != null) {
			os.close();
		}
	}

	private synchronized JSONObject loadAccountData() throws S3Exception {
    	File hostdir = getHostFolder();
    	File account = new File(hostdir, STOREACCOUNT);
		if( !account.exists() ) {
			return null;
		}
		try {
			FileInputStream f = new FileInputStream(account);
			InputStreamReader in = new InputStreamReader(f, "UTF-8");
			char[] chunk= new char[f.available()];
			StringBuffer buff = new StringBuffer(f.available());
			while(true) {
				int rs = in.read(chunk);
				if( rs < 0 ) break;
				buff.append(chunk);
			}
			in.close();
			JSONObject js = new JSONObject(buff.toString());
			return js;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (IOException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (JSONException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		}
	}
	private synchronized void saveAccountData(JSONObject acc) throws S3Exception {
	   	File hostdir = getHostFolder();
    	File account = new File(hostdir, STOREACCOUNT);
    	try {
			String js = acc.toString();
			FileOutputStream fo = new FileOutputStream(account);
			OutputStreamWriter out = new OutputStreamWriter(fo);
			out.write(js);
			out.write("\r\n");
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (IOException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		}
	}
	
	private synchronized JSONObject findAccount(String user, String pass) throws S3Exception {
		JSONObject acc = loadAccountData();
		return findAccount(acc, user, pass);
	}
	private synchronized JSONObject findAccount(JSONObject acc, String user, String pass) throws S3Exception {
		try {
			JSONArray accounts = acc.getJSONArray("accounts");
			for(int i = 0; i < accounts.length(); i++ ) {
				JSONObject def = accounts.getJSONObject(i);
				String acc_user = def.getString("user");
				if( acc_user.equals(user) ) {
					return def;
				}
			}
		} catch (JSONException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		}
		return null;
	}
	
	private synchronized void createAccount(String user, String pass, String name1, String name2) throws S3Exception {
    	JSONObject acc = loadAccountData();
    	createAccount(acc, user, pass, name1, name2);
	}
	private synchronized void createAccount(JSONObject acc, String user, String pass, String name1, String name2) throws S3Exception {
    	try {
			if( acc == null ) {
				acc = new JSONObject("{accounts:[]}");
			}
			JSONObject duser = new JSONObject();
			duser.put("user", user);
			duser.put("passwd", pass);
			if( name1 != null && !name1.isEmpty() )
				duser.put("name1", name1);
			if( name2 != null && !name2.isEmpty() )
				duser.put("name2", name2);
			JSONArray arr = acc.getJSONArray("accounts");
			int len=arr.length();
			arr.put(len, duser);
		} catch (JSONException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		}
    	saveAccountData(acc);
	}
	
	public synchronized void connect(String host, int port) throws S3Exception {
		Log.d("s3store","do connect");
		String tag = String.format("%s-%d", host, port);
		File metadir = s3ctx.getMetadtaDir();
		File hostdir = new File(metadir, tag);
		if( hostdir.exists() ) {
			if( !hostdir.isDirectory() ) {
				if( !hostdir.delete() ) {
					Log.d("s3store","host directory is not directory and can't delete");
					throw new S3Exception(s3ctx.getContext().getString(R.string.localServerError));
				}
			}
		}
		if( !hostdir.exists() ) {
			if( !hostdir.mkdir() ) {
				Log.d("s3store","host directory can't create");
				throw new S3Exception(s3ctx.getContext().getString(R.string.localServerError));
			}
		}
		hostFolder = hostdir.toString();
		Log.d("s3store","connect complete");
	}

    public synchronized void login(String user, String pass) throws S3Exception {
		Log.d("s3store","do login");
    	JSONObject acc = null;
    	try {
    		acc = findAccount(user, pass);
	    	if( acc == null ) {
	    		Log.d("s3store","account not found");
	    		throw new S3Exception(s3ctx.getContext().getString(R.string.loginError));
	    	}
    	}
    	catch(S3Exception e) {
    		Log.d("s3store",e.toString());
    		throw new S3Exception(s3ctx.getContext().getString(R.string.loginError));
    	}
    	
    	File hostdir = getHostFolder();
    	File userdir = new File(hostdir, user);
    	if( !userdir.exists() ) {
    		Log.d("s3store","userdir is not exist");
    		throw new S3Exception(s3ctx.getContext().getString(R.string.loginError));
    	}
    	if( !userdir.isDirectory() ) {
    		Log.d("s3store","userdir is not directory");
    		throw new S3Exception(s3ctx.getContext().getString(R.string.loginError));
    	}
    	this.user		= user;
    	this.userFolder	= userdir.toString();
    	try {
			this.name1	= acc.getString("name1");
		} catch (JSONException e) {
			this.name1	= null;
		}
    	try {
			this.name2		= acc.getString("name2");
		} catch (JSONException e) {
			this.name2	= null;
		}
		Log.d("s3store","login complete");
    }
    
    public synchronized void register(String user, String pass, String name1, String name2) throws S3Exception {
    	JSONObject acc = loadAccountData();
    	if( acc != null ) {
	    	if( findAccount(acc, user, pass) != null ) {
	    		throw new S3Exception(s3ctx.getContext().getString(R.string.registerError));
	    	}
    	}
    	
    	createAccount(acc, user, pass, name1, name2);
    	File hostdir = getHostFolder();
    	File userdir = new File(hostdir, user);
    	if( userdir.exists() ) {
    		userdir.delete();
    	}
    	userdir.mkdir();
    	this.user		= user;
    	this.userFolder	= userdir.toString();
		this.name1		= name1;
		this.name2		= name2;
    }

	public synchronized void disconnect() {
		this.hostFolder		= null;
		this.userFolder		= null;
		this.user			= null;
	}
	
	public synchronized void registerService(S3Application svc) throws S3Exception {
		if( userFolder == null ) {
			throw new S3Exception("not logged in");
		}
		File svcFile = getServiceFolder(svc);
		if( !svcFile.isDirectory() ) {
			if( svcFile.delete() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.localServerError));
			}
		}
		if( !svcFile.exists() ) {
			if( !svcFile.mkdir() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.localServerError));
			}
		}
	}
	
	public List<String> getResourceList(S3Application svc) throws S3Exception {
		File svcFile = getServiceFolder(svc);
		FilenameFilter filter = new FilenameFilter() {
			public boolean accept(File dir, String filename) {
				if( dir.isDirectory() ) {
					return true;
				}
				return false;
			}
		};
		List<String> list = new ArrayList<String>();
		File[] files = svcFile.listFiles(filter);
		int i;
		for( i = 0; i < files.length; i++) {
			File f = files[i];
			list.add(f.getName());
		}
		return list;
	}
	
	public JSONObject loadResourceInfo(S3Application svc, S3Resource rsc) throws S3Exception {
		File idxFile = getIndeFile(svc, rsc);
		if( !idxFile.exists() ) {
			return null;
		}
		try {
			BufferedInputStream f = new BufferedInputStream(new FileInputStream(idxFile));
			InputStreamReader in = new InputStreamReader(f, "UTF-8");
			char[] chunk= new char[f.available()];
			StringBuffer buff = new StringBuffer(f.available());
			while(true) {
				int rs = in.read(chunk);
				if( rs < 0 ) break;
				buff.append(chunk);
			}
			in.close();
			JSONObject js = new JSONObject(buff.toString());
			return js;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (IOException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (JSONException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		}
	}
	public void saveResourceInfo(S3Application svc, S3Resource rsc, JSONObject info) throws S3Exception {
		File idxFile = getIndeFile(svc, rsc);
		if( idxFile.exists() ) {
			idxFile.delete();
		}
		try {
			String s = info.toString();
			InputStream is = new ByteArrayInputStream(s.getBytes()); 
			BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(idxFile));
			
			copyStream(is, os, 4096);
			is.close();
			os.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(e.toString());
		} catch (IOException e) {
			e.printStackTrace();
			throw new S3Exception(e.toString());
		}
	}
	
	public long getSize(S3Application svc, S3Resource rsc) throws S3Exception {
		File dataFile = getDataFile(svc, rsc);
		if( !dataFile.exists() ) {
			return 0;
		}
		return dataFile.length();
	}
	public String getDataPath(S3Application svc, S3Resource rsc) throws S3Exception {
		File dataFile = getDataFile(svc, rsc);
		return dataFile.getPath();
	}
	public InputStream openData(S3Application svc, S3Resource rsc) throws S3Exception {
		File dataFile = getDataFile(svc, rsc);
		if( !dataFile.exists() ) {
			return null;
		}
		FileInputStream in;
		try {
			in = new FileInputStream(dataFile);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileNotFoundError));
		}
		return in;
	}
	
	public InputStream openGadget(S3Application svc, S3Resource rsc) throws S3Exception {
		File gadFile = getGadgetFile(svc, rsc);
		if( !gadFile.exists() ) {
			return null;
		}
		FileInputStream in;
		try {
			in = new FileInputStream(gadFile);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileNotFoundError));
		}
		return in;
	}
	
	public synchronized void saveGadget(S3Application svc, S3Resource rsc, InputStream in) throws S3Exception {
		File gadFile = getGadgetFile(svc, rsc);
		if( gadFile.exists() ) {
			if( !gadFile.delete() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
			}
		}
		try {
			BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(gadFile));
			//Context ctx = s3ctx.getContext();
			//String fn = gadFile.toString();
			//os = ctx.openFileOutput(fn, Context.MODE_WORLD_READABLE);//|Context.MODE_WORLD_WRITEABLE);
			copyStream(in, os, 4096);
			os.close();
			gadFile.setReadable(true, false);
			gadFile.setWritable(true, false);
		} catch (FileNotFoundException e) {
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
		} catch (IOException e) {
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
		}
	}
	
	public synchronized void removeGadget(S3Application svc, S3Resource rsc) throws S3Exception {
		File gadFile = getGadgetFile(svc, rsc);
		if( gadFile.exists() ) {
			if( !gadFile.delete() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
			}
		}
	}

	public synchronized InputStream openMetaData(S3Application svc, S3Resource rsc, String tag) throws S3Exception {
		File metaFolder = getMetaFolder(svc, rsc);
		if( !metaFolder.exists() )
			return null;
		if( !metaFolder.isDirectory() )
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
		File metaFile = new File(metaFolder, tag);
		if( !metaFile.exists() )
			return null;
		if( !metaFile.isFile() )
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
		FileInputStream in;
		try {
			in = new FileInputStream(metaFile);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileNotFoundError));
		}
		return in;
	}
	public synchronized void removeMetaData(S3Application svc, S3Resource rsc, String tag) throws S3Exception {
		File metaFolder = getMetaFolder(svc, rsc);
		if( !metaFolder.exists() ) {
			return;
		}
		File metaFile = new File(metaFolder, tag);
		if( metaFile.exists() ) {
			if( !metaFile.delete() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
			}
			String[] list = metaFolder.list();
			if( list.length == 0 ) {
				metaFolder.delete();
			}
		}
	}
	public synchronized File saveMetaData(S3Application svc, S3Resource rsc, String tag, InputStream in) throws S3Exception {
		File metaFolder = getMetaFolder(svc, rsc);
		if( metaFolder.exists() ) {
			if( !metaFolder.isDirectory() ) {
				if( !metaFolder.delete() ) {
					throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
				}
			}
		}
		if( !metaFolder.exists() ) {
			if( !metaFolder.mkdir() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
			}
		}
		File metaFile = new File(metaFolder, tag);
		if( metaFile.exists() ) {
			if( !metaFile.delete() ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
			}
		}
		try {
			FileOutputStream os = new FileOutputStream(metaFile);
			copyStream(in, os, 4096);
			os.close();
			metaFile.setReadable(true, false);
			metaFile.setWritable(true, false);
		} catch (FileNotFoundException e) {
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
		} catch (IOException e) {
			throw new S3Exception(s3ctx.getContext().getString(R.string.fileIoError));
		}
		return metaFile;
	}
}
