/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU Lesser General Public License
 * (version 2.1 or later).  See the COPYING file in this distribution.
 */

public class RawFileFormatDriver : PhotoFileFormatDriver {
    private static RawFileFormatDriver instance = null;
    
    public static void init() {
        instance = new RawFileFormatDriver();
        RawFileFormatProperties.init();
    }
    
    public static RawFileFormatDriver get_instance() {
        return instance;
    }
    
    public override PhotoFileFormatProperties get_properties() {
        return RawFileFormatProperties.get_instance();
    }
    
    public override PhotoFileReader create_reader(string filepath) {
        return new RawReader(filepath);
    }
    
    public override PhotoMetadata create_metadata() {
        return new PhotoMetadata();
    }
    
    public override bool can_write_image() {
        return false;
    }
    
    public override bool can_write_metadata() {
        return false;
    }
    
    public override PhotoFileWriter? create_writer(string filepath) {
        return null;
    }
    
    public override PhotoFileMetadataWriter? create_metadata_writer(string filepath) {
        return null;
    }
    
    public override PhotoFileSniffer create_sniffer(File file, PhotoFileSniffer.Options options) {
        return new RawSniffer(file, options);
    }
}

public class RawFileFormatProperties : PhotoFileFormatProperties {
    private static string[] KNOWN_EXTENSIONS = {
        "3fr", "arw", "srf", "sr2", "bay", "crw", "cr2", "cr3", "cap", "iiq", "eip", "dcs", "dcr", "drf",
        "k25", "kdc", "dng", "erf", "fff", "mef", "mos", "mrw", "nef", "nrw", "orf", "ptx", "pef",
        "pxn", "r3d", "raf", "raw", "rw2", "raw", "rwl", "rwz", "x3f", "srw"
    };

    private static string[] KNOWN_MIME_TYPES = {
        /* a catch-all MIME type for all formats supported by the dcraw command-line
           tool (and hence libraw) */
        "image/x-dcraw",
    
        /* manufacturer blessed MIME types */
        "image/x-canon-cr2",
        "image/x-canon-cr3",
        "image/x-canon-crw",
        "image/x-fuji-raf",
        "image/x-adobe-dng",
        "image/x-panasonic-raw",
        "image/x-raw",
        "image/x-minolta-mrw",
        "image/x-nikon-nef",
        "image/x-olympus-orf",
        "image/x-pentax-pef",
        "image/x-sony-arw",
        "image/x-sony-srf",
        "image/x-sony-sr2",
        "image/x-samsung-raw",

        /* generic MIME types for file extensions*/
        "image/x-3fr",
        "image/x-arw",
        "image/x-srf",
        "image/x-sr2",
        "image/x-bay",
        "image/x-crw",
        "image/x-cr2",
        "image/x-cr3",
        "image/x-cap",
        "image/x-iiq",
        "image/x-eip",
        "image/x-dcs",
        "image/x-dcr",
        "image/x-drf",
        "image/x-k25",
        "image/x-kdc",
        "image/x-dng",
        "image/x-erf",
        "image/x-fff",
        "image/x-mef",
        "image/x-mos",
        "image/x-mrw",
        "image/x-nef",
        "image/x-nrw",
        "image/x-orf",
        "image/x-ptx",
        "image/x-pef",
        "image/x-pxn",
        "image/x-r3d",
        "image/x-raf",
        "image/x-raw",
        "image/x-rw2",
        "image/x-raw",
        "image/x-rwl",
        "image/x-rwz",
        "image/x-x3f",
        "image/x-srw"
    };
    
    private static RawFileFormatProperties instance = null;
    
    public static void init() {
        instance = new RawFileFormatProperties();
    }
    
    public static RawFileFormatProperties get_instance() {
        return instance;
    }
    
    public override PhotoFileFormat get_file_format() {
        return PhotoFileFormat.RAW;
    }

    public override string get_user_visible_name() {
        return _("RAW");
    }

    public override PhotoFileFormatFlags get_flags() {
        return PhotoFileFormatFlags.NONE;
    }
    
    public override string get_default_extension() {
        // Because RAW is a smorgasbord of file formats and exporting to a RAW file is
        // not expected, this function should probably never be called.  However, need to pick
        // one, so here it is.
        return "raw";
    }
    
    public override string[] get_known_extensions() {
        return KNOWN_EXTENSIONS;
    }
    
    public override string get_default_mime_type() {
        return KNOWN_MIME_TYPES[0];
    }
    
    public override string[] get_mime_types() {
        return KNOWN_MIME_TYPES;
    }
}

public class RawSniffer : PhotoFileSniffer {
    public RawSniffer(File file, PhotoFileSniffer.Options options) {
        base (file, options);
    }
    
    public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error {
        // this sniffer doesn't detect corrupted files
        is_corrupted = false;
        
        DetectedPhotoInformation detected = new DetectedPhotoInformation();
        
        GRaw.Processor processor = new GRaw.Processor();
        processor.output_params->user_flip = GRaw.Flip.NONE;
        
        try {
            processor.open_file(file.get_path());
            processor.adjust_sizes_info_only();
        } catch (GRaw.Exception exception) {
            if (exception is GRaw.Exception.UNSUPPORTED_FILE)
                return null;
            
            throw exception;
        }
        
        detected.image_dim = Dimensions(processor.get_sizes().iwidth, processor.get_sizes().iheight);
        detected.colorspace = Gdk.Colorspace.RGB;
        detected.channels = 3;
        detected.bits_per_channel = 8;
        
        RawReader reader = new RawReader(file.get_path());
        try {
            detected.metadata = reader.read_metadata();
        } catch (Error err) {
            // ignored
        }
        
        if (calc_md5 && detected.metadata != null) {
            detected.exif_md5 = detected.metadata.exif_hash();
            detected.thumbnail_md5 = detected.metadata.thumbnail_hash();
        }
        
        if (calc_md5)
            detected.md5 = md5_file(file);
        
        detected.format_name = "raw";
        detected.file_format = PhotoFileFormat.RAW;
        
        return detected;
    }
}

public class RawReader : PhotoFileReader {
    private PhotoMetadata? cached_metadata = null;

    public RawReader(string filepath) {
        base (filepath, PhotoFileFormat.RAW);
    }
    
    public override PhotoMetadata read_metadata() throws Error {
        if (cached_metadata == null) {
            PhotoMetadata metadata = new PhotoMetadata();
            metadata.read_from_file(get_file());
            cached_metadata = metadata;
        }
        return cached_metadata;
    }
    
    public override Gdk.Pixbuf unscaled_read() throws Error {
        GRaw.Processor processor = new GRaw.Processor();
        processor.configure_for_rgb_display(false);
        processor.output_params->user_flip = GRaw.Flip.NONE;
        
        processor.open_file(get_filepath());
        processor.unpack();
        processor.process();
        
        return processor.make_mem_image().get_pixbuf_copy();
    }
    
    public override Gdk.Pixbuf scaled_read(Dimensions full, Dimensions scaled) throws Error {
        // Try to get the embedded thumbnail first
        double width_proportion = (double) scaled.width / (double) full.width;
        double height_proportion = (double) scaled.height / (double) full.height;
        bool half_size = width_proportion < 0.5 && height_proportion < 0.5;
        
        GRaw.Processor processor = new GRaw.Processor();
        processor.configure_for_rgb_display(half_size);
        processor.output_params->user_flip = GRaw.Flip.NONE;
        
        processor.open_file(get_filepath());
        try {
            if (this.get_role () == Role.THUMBNAIL) {
                processor.unpack_thumb();
                var image = processor.make_thumb_image ();
                return resize_pixbuf (image.get_pixbuf_copy (),
                                      scaled,
                                      Gdk.InterpType.BILINEAR);
            }
        } catch (Error error) {
            // Nothing to do, continue with raw developer
        }

        processor.unpack();
        processor.process();
        
        GRaw.ProcessedImage image = processor.make_mem_image();
        
        return resize_pixbuf(image.get_pixbuf_copy(), scaled, Gdk.InterpType.BILINEAR);
    }
}

// Development mode of a RAW photo.
public enum RawDeveloper {
    SHOTWELL = 0,  // Developed internally by Shotwell
    CAMERA,        // JPEG from RAW+JPEG pair (if available)
    EMBEDDED;      // Largest-size
    
    public static RawDeveloper[] as_array() {
        return { SHOTWELL, CAMERA, EMBEDDED };
    }
    
    public string to_string() {
        switch (this) {
            case SHOTWELL:
                return "SHOTWELL";
            case CAMERA:
                return "CAMERA";
            case EMBEDDED:
                return "EMBEDDED";
            default:
                assert_not_reached();
        }
    }
    
    public static RawDeveloper from_string(string value) {
        switch (value) {
            case "SHOTWELL":
                return SHOTWELL;
            case "CAMERA":
                return CAMERA;
            case "EMBEDDED":
                return EMBEDDED;
            default:
                assert_not_reached();
        }
    }
    
    public string get_label() {
        switch (this) {
            case SHOTWELL:
                return _("Shotwell");
            case CAMERA:
            case EMBEDDED:
                return _("Camera");
            default:
                assert_not_reached();
        }
    }
    
    // Determines if two RAW developers are equivalent, treating camera and embedded
    // as the same.
    public bool is_equivalent(RawDeveloper d) {
        if (this == d)
            return true;
        
        if ((this == RawDeveloper.CAMERA && d == RawDeveloper.EMBEDDED) ||
            (this == RawDeveloper.EMBEDDED && d == RawDeveloper.CAMERA))
            return true;
        
        return false;
    }
    
    // Creates a backing JPEG.
    // raw_filepath is the full path of the imported RAW file.
    public BackingPhotoRow create_backing_row_for_development(string raw_filepath,
        string? camera_development_filename = null) throws Error {
        BackingPhotoRow ns = new BackingPhotoRow();
        File master = File.new_for_path(raw_filepath);
        string name, ext;
        disassemble_filename(master.get_basename(), out name, out ext);
        
        string basename;
        
        // If this image is coming in with an existing development, use its existing
        // filename instead.
        if (camera_development_filename == null) {
            basename = name + "_" + ext +
                (this != CAMERA ? ("_" + this.to_string().down()) : "") + ".jpg";
        } else {
            basename = camera_development_filename;
        }
        
        string newbasename = LibraryFiles.convert_basename(basename);

        bool c;
        File? new_back = generate_unique_file(master.get_parent(), newbasename, out c);
        claim_file(new_back);
        ns.file_format = PhotoFileFormat.JFIF;
        ns.filepath = new_back.get_path();
        
        return ns;
    }
}
