// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer 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.
//
// Klipspringer 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 Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

package net.avadeaux.klipspringer;

import net.avadeaux.klipspringer.codec.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;

/** Utility that extracts pictures from a FLAC file. */
public class FlacPictures extends FlacDecoder implements AudioDecoder.PictureTarget {
    private interface Fnamer {
        String fname(File flacFileName, int picNo, String mime, String type, String desc, ByteBuffer data);
    }

    private static Fnamer descriptiveFnamer = new Fnamer() {
            public String fname(File flacFileName, int picNo, String mime, String type, String desc, ByteBuffer data) {
                StringBuilder fnam = new StringBuilder(flacFileName.getName());
                int sufPos = fnam.lastIndexOf(".");
                if (sufPos > 0) { fnam.delete(sufPos, fnam.length()); }
                fnam.append("-"+picNo);
                if (type != null && type.length() > 0) { fnam.append("-").append(type); }
                return fnam.toString();
            }
        };
    private static Fnamer hashFnamer = new Fnamer() {
            public String fname(File flacFileName, int picNo, String mime, String type, String desc, ByteBuffer data) {
                int pos = data.position();
                MessageDigest hash;
                try {
                    hash = MessageDigest.getInstance("SHA-1");
                } catch (NoSuchAlgorithmException ex) { throw new Error(ex); }
                hash.update(data);
                data.position(pos);
                return HexFormat.of().formatHex(hash.digest());
            }
        };

    private final File flacFileName, picDir;
    private final Fnamer fnamer;
    private final PrintStream messages;
    private int picCount = 0;

    public FlacPictures(File flacFileName, File picDir, Fnamer fnamer, PrintStream messages) throws IOException {
        super(flacFileName.toString(), new Target() {
                public boolean write(ByteBuffer data) { return false; }
                public void metadata(PcmFormat format, long totalFrames) { }
                public void close() { }
            });
        this.flacFileName = flacFileName;
        this.picDir = picDir;
        this.fnamer = fnamer;
        this.messages = messages;
    }

    public void picture(String mime, String type, String desc, ByteBuffer data) throws IOException {
        String fbase = fnamer.fname(flacFileName, ++picCount, mime, type, desc, data);
        if (messages != null) {
            messages.println(flacFileName+" "+mime+" picture"
                             +(mime != null && mime.length() > 0 ? ", "+type : "")
                             +(desc != null && desc.length() > 0 ? ", "+desc : "")
                             +" "+fbase);
        }
        if (picDir != null) {
            Files.createDirectories(picDir.toPath());
            if ("-->".equals(mime)) {
                PrintStream pf = new PrintStream(new FileOutputStream(new File(picDir, fbase+".url")));
                pf.println("[InternetShortcut]");
                pf.println("URL="+StandardCharsets.UTF_8.decode(data));
                pf.println("Comment=Extracted from "+flacFileName);
                if (desc != null && desc.length() > 0) { pf.println("Desc="+desc); }
            } else {
                RandomAccessFile f = new RandomAccessFile(new File(picDir, fbase+"."+mime.substring(mime.lastIndexOf('/')+1)), "rw");
                f.setLength(0);
                f.getChannel().write(data);
                f.close();
            }
        }
    }

    public static void main(String[] args) {
        String helpText = "Arguments: [options] flacfile\n\n"
            +"Options:\n"
            +"-o <path>          Directory of output files\n"
            +"-q                 Do not print information of extracted pictures\n"
            +"-l                 Do not output to files\n"
            +"-s                 Use SHA-1 file names (default)\n"
            +"-d                 Use descriptive names\n"
            +"-h or --h          Print this message\n"
            +"--                 No more options, next argument is flac file\n";

        File odir = new File(".");
        PrintStream messages = System.out;
        Fnamer fnamer = hashFnamer;

        int argPos = 0;
        while (argPos < args.length) {
            String o = args[argPos];
            if      (o.charAt(0) != '-')   { break; }
            argPos++;
            if      ("--"      .equals(o)) { break; }
            else if ("-o"      .equals(o)) { odir = new File(args[argPos++]); }
            else if ("-q"      .equals(o)) { messages = null; }
            else if ("-l"      .equals(o)) { odir = null; }
            else if ("-s"      .equals(o)) { fnamer = hashFnamer; }
            else if ("-d"      .equals(o)) { fnamer = descriptiveFnamer; }
            else if (o.startsWith("-h") || o.startsWith("--h")) { System.out.println(helpText); return; }
            else {
                System.err.println("Unregognized option "+o+" (use -help for usage)");
                System.exit(2);
            }
        }
        if (argPos != args.length-1) {
            System.err.print("Invalid arguments (use -help for usage):");
            for (int i = 0; i < args.length; i++) { System.err.print(" "+args[i]); }
            System.err.println();
            System.exit(2);
        }
        try {
            FlacPictures decoder = new FlacPictures(new File(args[argPos]), odir, fnamer, messages);
            decoder.decodeMetadata();
        } catch (Throwable th) {
            System.err.println(th);
            if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
            System.exit(1);
        }
    }
}
