/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed 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 com.android.cts;

import com.android.ddmlib.RawImage;

import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferUShort;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Formatter;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Utilities for CTS host.
 *
 */
public class HostUtils {
    
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy",
            Locale.ENGLISH);
    
    /**
     * Check if the given file exists
     *
     * @param name the file name to be checked
     * @return if the file exists, return true;
     *         else, return false
     */
    public static boolean isFileExist(final String name) {
        return new File(name).exists();
    }

    /**
     * Convert a 16bpp RawImage into a BufferedImage.
     *
     * @param rawImage the image to convert.
     * @return the BufferedImage.
     */
    public static BufferedImage convertRawImageToBufferedImage(RawImage rawImage) {
        assert rawImage.bpp == 16;

        BufferedImage im = new BufferedImage(rawImage.width,
                rawImage.height, BufferedImage.TYPE_USHORT_565_RGB);
        SampleModel sampleModel = new SinglePixelPackedSampleModel(DataBuffer.TYPE_USHORT,
                rawImage.width,
                rawImage.height,
                // RGB565
                new int[] { 0xf800, 0x07e0, 0x001f });

        // It would be more efficient to just subclass DataBuffer and provide a
        // TYPE_USHORT interface to the byte array.  But Raster.createRaster at
        // some point uses instanceof(DataBufferUShort) to verify that the DataBuffer
        // is of the right type (instead of just checking DataBuffer.getDataType).
        // And since DataBufferUShort is final, it can't be subclassed to get around
        // the check either.  So copy the data into a short[] instead to work around the problem.
        short shortData[] = new short[rawImage.size / 2];
        for (int x = 0; x < shortData.length; x++) {
            int rawImageOffset = x * 2;
            int a = 0xff & rawImage.data[rawImageOffset];
            int b = 0xff & rawImage.data[rawImageOffset + 1];
            shortData[x] = (short)((b << 8) | a);
        }
        DataBuffer db = new DataBufferUShort(shortData, shortData.length);
        Raster raster = Raster.createRaster(sampleModel,
                db, null);
        im.setData(raster);
        return im;
    }

    /**
     * Interface used with visitAllFilesUnder
     */
    public interface FileVisitor {
        /**
         * Gets called on every file visited.
         * @param f the File for the file being visited.
         */
        void visitFile(File f);
    }

    /**
     * Recursively visit all files under a given path.
     *
     * @param root the path to start at.
     * @param filter the file filter to match.  null means to visit all files.
     * @param visitor the visitor to visit with.
     */
    public static void visitAllFilesUnder(File root, FilenameFilter filter, FileVisitor visitor) {
        File[] files = root.listFiles(filter);
        // A null file may indicate not having enough permissions to view that directory
        if (files != null) {
            for (File f : files) {
                visitor.visitFile(f);

                if (f.isDirectory()) {
                    visitAllFilesUnder(f, filter, visitor);
                }
            }
        }
    }

    /**
     * Recursively visit all files under a given path.
     *
     * @param path the path to start at.
     * @param filter the file filter to match.  null means to visit all files.
     * @param visitor the visitor to visit with.
     */
    public static void visitAllFilesUnder(String path, FilenameFilter filter, FileVisitor visitor) {
        visitAllFilesUnder(new File(path), filter, visitor);
    }

    // Private class to help zipUpDirectory
    private static class ZipFileVisitor implements FileVisitor {
        private final ZipOutputStream zipOutputStream;
        private boolean ok = true;
        private IOException caughtException;
        private final ZipFilenameTransformer transformer;

        public ZipFileVisitor(ZipOutputStream zipOutputStream,
                ZipFilenameTransformer transformer) {
            this.zipOutputStream = zipOutputStream;
            this.transformer = transformer;
        }

        public void visitFile(File f) {
            String path = f.getPath();
            if (transformer != null) {
                path = transformer.transform(path);
            }
            ZipEntry ze = new ZipEntry(path);
            try {
                zipOutputStream.putNextEntry(ze);
                InputStream is = new BufferedInputStream(new FileInputStream(f));
                byte[] buffer = new byte[4096];
                int bytesRead = is.read(buffer);
                while (bytesRead > 0) {
                    zipOutputStream.write(buffer, 0, bytesRead);
                    bytesRead = is.read(buffer);
                }
                zipOutputStream.closeEntry();
            } catch (IOException e) {
                ok = false;
                caughtException = e;
            }
        }

        /**
         * Indicates that the visitor ran without errors
         * @return true if everything ran OK.
         */
        boolean isOk() {
            return ok;
        }

        /**
         * If an IOException was thrown while zipping, it gets kept here.
         *
         * @return the IOException that was caught, or null if none was.
         */
        IOException getCaughtException() {
            return caughtException;
        }

    }

    /**
     * Indicates some issue with zipping up the file.
     */
    static class ZipFileException extends Exception {
        ZipFileException(IOException ioException) {
            super("Caught wrapped exception", ioException);
        }
    }

    /**
     * Interface provided to rename files before they get zipped.
     */
    public interface ZipFilenameTransformer {
        /**
         * Transform a local filesystem filename into a zipfile filename.
         *
         * @param filename the input filename
         * @return the filename to be saved to the zipfile as.
         */
        String transform(String filename);
    }

    /**
     * Recursively zip up a directory into a zip file.
     *
     * @param sourceDir the directory to zip up
     * @param outputFilePath the zipfile to create.
     * @param transformer filepath transformer.  can be null.
     * @throws IOException if there were issues writing the zipfile.
     */
    public static void zipUpDirectory(String sourceDir,
            String outputFilePath,
            ZipFilenameTransformer transformer)
    throws IOException, ZipFileException {
        // I <3 abstractions
        FileOutputStream fileOut = new FileOutputStream(outputFilePath);
        BufferedOutputStream bufOut = new BufferedOutputStream(fileOut);
        final ZipOutputStream zipOutputStream = new ZipOutputStream(bufOut);

        ZipFileVisitor zfv = new ZipFileVisitor(zipOutputStream, transformer);
        visitAllFilesUnder(sourceDir, null, zfv);
        zipOutputStream.close();
        if (!zfv.isOk()) {
            throw new ZipFileException(zfv.getCaughtException());
        }
    }

    /**
     * Get the formatted time string.
     *
     * @param milliSec The time in milliseconds.
     * @param separator The separator between the date information and time information.
     * @param dateSeparator The date separator separating the date information nibbles.
     * @param timeSeparator The time separator separating the time information nibbles.
     * @return The formated time string.
     */
    public static String getFormattedTimeString(long milliSec, String separator,
            String dateSeparator, String timeSeparator) {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(milliSec);
        int year  = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int date  = cal.get(Calendar.DATE);
        int hour  = cal.get(Calendar.HOUR_OF_DAY);
        int min   = cal.get(Calendar.MINUTE);
        int sec   = cal.get(Calendar.SECOND);

        Formatter fmt = new Formatter();
        if ((separator == null) || (separator.length() == 0)) {
            separator = "_";
        }

        if ((dateSeparator == null) || (dateSeparator.length() == 0)) {
            dateSeparator = ".";
        }

        if ((timeSeparator == null) || (timeSeparator.length() == 0)) {
            timeSeparator = ".";
        }

        final String formatStr = "%4d" + dateSeparator + "%02d" + dateSeparator + "%02d"
                         + separator + "%02d" + timeSeparator + "%02d" + timeSeparator + "%02d";
        fmt.format(formatStr, year, month, date, hour, min, sec);

        return fmt.toString();
    }
    
    /**
     * Convert the given byte array into a lowercase hex string.
     * 
     * @param arr The array to convert.
     * @return The hex encoded string.
     */
    public static String toHexString(byte[] arr) {
        StringBuffer buf = new StringBuffer(arr.length * 2);
        for (byte b : arr) {
            buf.append(String.format("%02x", b & 0xFF));
        }
        return buf.toString();
    }
    
    /**
     * Strip control characters from the given string.
     */
    public static String replaceControlChars(String s) {
        // Replace any character < 0x20, except for tab, lf and cr
        return s.replaceAll("[\\x00-\\x1f&&[^\t\n\r]]", "?");
    }

    public static Date dateFromString(String s) throws ParseException {
        return dateFormat.parse(s);
    }
    
    public static String dateToString(Date d) {
        return dateFormat.format(d);
    }
}
