/*
 * This file is part of LSPosed.
 *
 * LSPosed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LSPosed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2020 EdXposed Contributors
 * Copyright (C) 2021 LSPosed Contributors
 */

package de.robv.android.xposed.services;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @hide
 */
public final class DirectAccessService extends BaseService {
    @Override
    public boolean hasDirectFileAccess() {
        return true;
    }

    @SuppressWarnings("RedundantIfStatement")
    @Override
    public boolean checkFileAccess(String filename, int mode) {
        File file = new File(filename);
        if (mode == F_OK && !file.exists()) return false;
        if ((mode & R_OK) != 0 && !file.canRead()) return false;
        if ((mode & W_OK) != 0 && !file.canWrite()) return false;
        if ((mode & X_OK) != 0 && !file.canExecute()) return false;
        return true;
    }

    @Override
    public boolean checkFileExists(String filename) {
        return new File(filename).exists();
    }

    @Override
    public FileResult statFile(String filename) throws IOException {
        File file = new File(filename);
        return new FileResult(file.length(), file.lastModified());
    }

    @Override
    public byte[] readFile(String filename) throws IOException {
        File file = new File(filename);
        byte content[] = new byte[(int) file.length()];
        FileInputStream fis = new FileInputStream(file);
        fis.read(content);
        fis.close();
        return content;
    }

    @Override
    public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
        File file = new File(filename);
        long size = file.length();
        long time = file.lastModified();
        if (previousSize == size && previousTime == time)
            return new FileResult(size, time);
        return new FileResult(readFile(filename), size, time);
    }

    @Override
    public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
        File file = new File(filename);
        long size = file.length();
        long time = file.lastModified();
        if (previousSize == size && previousTime == time)
            return new FileResult(size, time);

        // Shortcut for the simple case
        if (offset <= 0 && length <= 0)
            return new FileResult(readFile(filename), size, time);

        // Check range
        if (offset > 0 && offset >= size) {
            throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
        } else if (offset < 0) {
            offset = 0;
        }

        if (length > 0 && (offset + length) > size) {
            throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
        } else if (length <= 0) {
            length = (int) (size - offset);
        }

        byte content[] = new byte[length];
        FileInputStream fis = new FileInputStream(file);
        fis.skip(offset);
        fis.read(content);
        fis.close();
        return new FileResult(content, size, time);
    }

    /**
     * {@inheritDoc}
     * <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
     */
    @Override
    public InputStream getFileInputStream(String filename) throws IOException {
        return new BufferedInputStream(new FileInputStream(filename), 16 * 1024);
    }

    /**
     * {@inheritDoc}
     * <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
     */
    @Override
    public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
        File file = new File(filename);
        long size = file.length();
        long time = file.lastModified();
        if (previousSize == size && previousTime == time)
            return new FileResult(size, time);
        return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16 * 1024), size, time);
    }
}
