/*
       Licensed to the Apache Software Foundation (ASF) under one
       or more contributor license agreements.  See the NOTICE file
       distributed with this work for additional information
       regarding copyright ownership.  The ASF licenses this file
       to you under the Apache License, Version 2.0 (the
       "License"); you may not use this file except in compliance
       with the License.  You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing,
       software distributed under the License is distributed on an
       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       KIND, either express or implied.  See the License for the
       specific language governing permissions and limitations
       under the License.
 */
package org.apache.cordova.file;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.provider.OpenableColumns;

public class ContentFilesystem extends Filesystem {

	private CordovaInterface cordova;
	private CordovaResourceApi resourceApi;
	
	public ContentFilesystem(String name, CordovaInterface cordova, CordovaWebView webView) {
		this.name = name;
		this.cordova = cordova;
		this.resourceApi = new CordovaResourceApi(webView.getContext(), webView.pluginManager);
	}
	
	@Override
	public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
		// Get the cursor to validate that the file exists
		Cursor cursor = openCursorForURL(inputURL);
		String filePath = null;
		try {
			if (cursor == null || !cursor.moveToFirst()) {
				throw new FileNotFoundException();
			}
			filePath = filesystemPathForCursor(cursor);
		} finally {
			if (cursor != null)
				cursor.close();
		}
		if (filePath == null) {
			filePath = inputURL.URL.toString();
		} else {
			filePath = "file://" + filePath;
		}
		try {
			return makeEntryForPath(inputURL.fullPath, inputURL.filesystemName, false /*fp.isDirectory()*/, filePath);
		} catch (JSONException e) {
			throw new IOException();
		}
	}
	
    @Override
	public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
			String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
        if (options != null) {
            if (options.optBoolean("create")) {
        		throw new IOException("Cannot create content url");
            }
        }
        LocalFilesystemURL requestedURL = new LocalFilesystemURL(Uri.withAppendedPath(inputURL.URL, fileName));
        File fp = new File(this.filesystemPathForURL(requestedURL));
        if (!fp.exists()) {
            throw new FileNotFoundException("path does not exist");
        }
        if (directory) {
            if (fp.isFile()) {
                throw new TypeMismatchException("path doesn't exist or is file");
            }
        } else {
            if (fp.isDirectory()) {
                throw new TypeMismatchException("path doesn't exist or is directory");
            }
        }
        // Return the directory
        return makeEntryForPath(requestedURL.fullPath, requestedURL.filesystemName, directory, Uri.fromFile(fp).toString());

	}

	@Override
	public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
			throws NoModificationAllowedException {

		String filePath = filesystemPathForURL(inputURL);
		File file = new File(filePath);
		try {
			this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
					MediaStore.Images.Media.DATA + " = ?",
					new String[] { filePath });
		} catch (UnsupportedOperationException t) {
			// Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
			// The ContentResolver applies only when the file was registered in the
			// first case, which is generally only the case with images.
		}
		return file.delete();
	}

	@Override
	public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
			throws NoModificationAllowedException {
		throw new NoModificationAllowedException("Cannot remove content url");
	}

	@Override
	public JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL)
			throws FileNotFoundException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
		Integer size = null;
		Integer lastModified = null;
        Cursor cursor = openCursorForURL(inputURL);
        try {
        	if (cursor != null && cursor.moveToFirst()) {
        		size = resourceSizeForCursor(cursor);
        		lastModified = lastModifiedDateForCursor(cursor);
        	} else {
    			throw new FileNotFoundException();
        	}
        } finally {
        	if (cursor != null)
        		cursor.close();
        }

        JSONObject metadata = new JSONObject();
        try {
        	metadata.put("size", size);
        	metadata.put("type", resourceApi.getMimeType(inputURL.URL));
        	metadata.put("name", inputURL.filesystemName);
        	metadata.put("fullPath", inputURL.fullPath);
        	metadata.put("lastModifiedDate", lastModified);
        } catch (JSONException e) {
        	return null;
        }
        return metadata;
	}

	@Override
	public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
			Filesystem srcFs, LocalFilesystemURL srcURL, boolean move)
                    throws IOException, InvalidModificationException, JSONException,
                    NoModificationAllowedException, FileExistsException {
        if (LocalFilesystem.class.isInstance(srcFs)) {
            /* Same FS, we can shortcut with CordovaResourceApi operations */
            // Figure out where we should be copying to
            final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL);

            OutputStream os = resourceApi.openOutputStream(destURL.URL);
            CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcURL.URL);
            if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
                throw new NoModificationAllowedException("Cannot move file at source URL");
            }
            try {
                resourceApi.copyResource(ofrr, os);
            } catch (IOException e) {
                throw new IOException("Cannot read file at source URL");
            }
            if (move) {
                srcFs.removeFileAtLocalURL(srcURL);
            }
            return makeEntryForURL(destinationURL, false, destinationURL.URL.toString());
        } else {
            // Need to copy the hard way
            return super.copyFileToURL(destURL, newName, srcFs, srcURL, move);
		}
	}

    
	@Override
    public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
			ReadFileCallback readFileCallback) throws IOException {
		CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(inputURL.URL);
        if (end < 0) {
            end = ofrr.length;
        }
        long numBytesToRead = end - start;
		try {
			if (start > 0) {
                ofrr.inputStream.skip(start);
			}
            LimitedInputStream inputStream = new LimitedInputStream(ofrr.inputStream, numBytesToRead);
            readFileCallback.handleData(inputStream, ofrr.mimeType);
		} finally {
            ofrr.inputStream.close();
		}
	}

	@Override
	public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
			int offset, boolean isBinary) throws NoModificationAllowedException {
        throw new NoModificationAllowedException("Couldn't write to file given its content URI");
    }
	@Override
	public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
			throws NoModificationAllowedException {
        throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
	}

	protected Cursor openCursorForURL(LocalFilesystemURL url) {
        ContentResolver contentResolver = this.cordova.getActivity().getContentResolver();
        Cursor cursor = contentResolver.query(url.URL, null, null, null, null);
        return cursor;
	}

	protected String filesystemPathForCursor(Cursor cursor) {
        final String[] LOCAL_FILE_PROJECTION = { MediaStore.Images.Media.DATA };
        int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
        if (columnIndex != -1) {
            return cursor.getString(columnIndex);
        }
        return null;
	}

	protected Integer resourceSizeForCursor(Cursor cursor) {
        int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
        if (columnIndex != -1) {
            String sizeStr = cursor.getString(columnIndex);
            if (sizeStr != null) {
            	return Integer.parseInt(sizeStr,10);
            }
        }
        return null;
	}
	
	protected Integer lastModifiedDateForCursor(Cursor cursor) {
        final String[] LOCAL_FILE_PROJECTION = { MediaStore.MediaColumns.DATE_MODIFIED };
        int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
        if (columnIndex != -1) {
            String dateStr = cursor.getString(columnIndex);
            if (dateStr != null) {
            	return Integer.parseInt(dateStr,10);
            }
        }
        return null;
	}

    @Override
    public String filesystemPathForURL(LocalFilesystemURL url) {
        Cursor cursor = openCursorForURL(url);
        try {
        	if (cursor != null && cursor.moveToFirst()) {
        		return filesystemPathForCursor(cursor);
        	}
        } finally {
            if (cursor != null)
            	cursor.close();
        }
        return null;
    }

	@Override
	public LocalFilesystemURL URLforFilesystemPath(String path) {
		// Returns null as we don't support reverse mapping back to content:// URLs
		return null;
	}

	@Override
	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
		String path = filesystemPathForURL(inputURL);
		File file = new File(path);
		return file.exists();
	}

	@Override
	OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL)
			throws IOException {
		OutputStream os = resourceApi.openOutputStream(inputURL.URL);
		return os;
    }
}