/* Copyright 2010-2015 Yorba Foundation
 *
 * This software is licensed under the GNU Lesser General Public License
 * (version 2.1 or later).  See the COPYING file in this distribution.
 */

public abstract class GdkReader : PhotoFileReader {
    public GdkReader(string filepath, PhotoFileFormat file_format) {
        base (filepath, file_format);
    }
    
    public override PhotoMetadata read_metadata() throws Error {
        PhotoMetadata metadata = new PhotoMetadata();
        metadata.read_from_file(get_file());
        
        return metadata;
    }
    
    public override Gdk.Pixbuf unscaled_read() throws Error {
        return new Gdk.Pixbuf.from_file(get_filepath());
    }
    
    public override Gdk.Pixbuf scaled_read(Dimensions full, Dimensions scaled) throws Error {
        return new Gdk.Pixbuf.from_file_at_scale(get_filepath(), scaled.width, scaled.height, false);
    }
}

public abstract class GdkSniffer : PhotoFileSniffer {
    private DetectedPhotoInformation detected = null;
    private bool size_ready = false;
    private bool area_prepared = false;
    
    public GdkSniffer(File file, PhotoFileSniffer.Options options) {
        base (file, options);
    }
    
    public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error {
        detected = new DetectedPhotoInformation();
        
        Gdk.PixbufLoader pixbuf_loader = new Gdk.PixbufLoader();
        pixbuf_loader.size_prepared.connect(on_size_prepared);
        pixbuf_loader.area_prepared.connect(on_area_prepared);
        
        // valac chokes on the ternary operator here
        Checksum? md5_checksum = null;
        if (calc_md5)
            md5_checksum = new Checksum(ChecksumType.MD5);
        
        detected.metadata = new PhotoMetadata();
        try {
            detected.metadata.read_from_file(file);
        } catch (Error err) {
            // no metadata detected
            detected.metadata = null;
        }
        
        if (calc_md5 && detected.metadata != null) {
            uint8[]? flattened_sans_thumbnail = detected.metadata.flatten_exif(false);
            if (flattened_sans_thumbnail != null && flattened_sans_thumbnail.length > 0)
                detected.exif_md5 = md5_binary(flattened_sans_thumbnail, flattened_sans_thumbnail.length);
            
            uint8[]? flattened_thumbnail = detected.metadata.flatten_exif_preview();
            if (flattened_thumbnail != null && flattened_thumbnail.length > 0)
                detected.thumbnail_md5 = md5_binary(flattened_thumbnail, flattened_thumbnail.length);
        }
        
        // if no MD5, don't read as much, as the needed info will probably be gleaned
        // in the first 8K to 16K
        uint8[] buffer = calc_md5 ? new uint8[64 * 1024] : new uint8[8 * 1024];
        size_t count = 0;
        
        // loop through until all conditions we're searching for are met
        FileInputStream fins = file.read(null);
        for (;;) {
            size_t bytes_read = fins.read(buffer, null);
            if (bytes_read <= 0)
                break;
            
            count += bytes_read;
            
            if (calc_md5)
                md5_checksum.update(buffer, bytes_read);
            
            // keep parsing the image until the size is discovered
            if (!size_ready || !area_prepared)
                pixbuf_loader.write(buffer[0:bytes_read]);
            
            // if not searching for anything else, exit
            if (!calc_md5 && size_ready && area_prepared)
                break;
        }
        
        // PixbufLoader throws an error if you close it with an incomplete image, so trap this
        try {
            pixbuf_loader.close();
        } catch (Error err) {
        }
        
        if (fins != null)
            fins.close(null);
        
        if (calc_md5)
            detected.md5 = md5_checksum.get_string();
        
        // if size and area are not ready, treat as corrupted file (entire file was read)
        is_corrupted = !size_ready || !area_prepared;
        
        return detected;
    }
    
    private void on_size_prepared(Gdk.PixbufLoader loader, int width, int height) {
        detected.image_dim = Dimensions(width, height);
        size_ready = true;
    }
    
    private void on_area_prepared(Gdk.PixbufLoader pixbuf_loader) {
        Gdk.Pixbuf? pixbuf = pixbuf_loader.get_pixbuf();
        if (pixbuf == null)
            return;
        
        detected.colorspace = pixbuf.get_colorspace();
        detected.channels = pixbuf.get_n_channels();
        detected.bits_per_channel = pixbuf.get_bits_per_sample();
        
        unowned Gdk.PixbufFormat format = pixbuf_loader.get_format();
        detected.format_name = format.get_name();
        detected.file_format = PhotoFileFormat.from_pixbuf_name(detected.format_name);
        
        area_prepared = true;
    }
}

