package leigh.astro.fits;

import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.ListUtil;
import leigh.mecha.util.Snavig;
import nom.tam.fits.Fits;
import nom.tam.fits.FitsException;
import nom.tam.fits.Header;
import nom.tam.fits.HeaderCard;
import nom.tam.util.Cursor;
import org.apache.commons.io.FilenameUtils;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * This class scans a directory hierarchy for .fits files, and builds a catalog of
 * {@link AstroSub} objects describing the files it finds.
 *
 * @author Alex Leigh
 */
public class FitsScanner {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(FitsScanner.class);
    private ArrayList<AstroSub> subs = new ArrayList<>();

    public static void debugFits(Fits f) throws FitsException, IOException {
        for (Cursor<String, HeaderCard> it = f.getHDU(0).getHeader().iterator(); it.hasNext(); ) {
            HeaderCard c = it.next();
            logger.info("header: {}", c);
        }
    }

    public void removeDupes(Path path) throws IOException {
        List<AstroSub> subs = scanPath(path);
        List<AstroSub> uniq = new ArrayList<>();
        List<AstroSub> dupes = new ArrayList<>();

        ListUtil.unique(uniq, dupes, subs);

        logger.info("Found subs. [count: {}] [uniq: {}] [dupe: {}]",
                subs.size(), uniq.size(), dupes.size());

        for (AstroSub sub : dupes) {
            logger.info("Removing duplicate sub. [fn: {}]", sub.getPath());
            sub.getPath().toFile().delete();

        }
    }

    public void dump(Path fitsFile) throws FitsException, IOException {
        Fits f = new Fits(fitsFile.toString());
        Header header = f.getHDU(0).getHeader();
        logger.info("Header: {}", header);
    }

    /**
     * Ingest the given files into the database.
     */

    // TODO This should also read the sub into the subs array
    public void ingest(Path dstPath, Path srcPath) throws IOException {
        Files.walkFileTree(srcPath, new SimpleFileVisitor<>() {
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (file.toString().endsWith(".fits") || file.toString().endsWith(".fit")) {
                    try {
                        Fits f = new Fits(file.toAbsolutePath().toString());
                        f.getHDU(0).getHeader();

                        // If we got this far, the file was probably legal.

                        UUID fileId = UUID.randomUUID();
                        Path newFn = dstPath.resolve(fileId + ".fits");

                        logger.info("Moving file: {} {}", file, newFn);

                        Files.move(file, newFn);

                    } catch (FitsException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
    }

    public void scan(Path base) throws IOException {
        // TODO Implement support for incrementally adding / multiple paths.
        subs.clear();

        Files.walkFileTree(base, new SimpleFileVisitor<>() {
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (file.toString().endsWith(".fits") || file.toString().endsWith(".fit")) {
                    try {
                        Fits f = new Fits(file.toAbsolutePath().toString());
                        Header header = f.getHDU(0).getHeader();

                        UUID id = UUID.fromString(FilenameUtils.removeExtension(file.getFileName().toString()));

                        AstroSub sub = new AstroSub(
                                id, header.getStringValue("TELESCOP"),
                                header.getStringValue("INSTRUME"),
                                AstroSubType.valueOf(header.getStringValue("FRAME").toUpperCase()),
                                header.getDoubleValue("EXPTIME"),
                                header.getIntValue("GAIN"),
                                header.getIntValue("OFFSET"),
                                header.getStringValue("FILTER"),
                                header.getDoubleValue("RA"),
                                header.getDoubleValue("DEC"),
                                Snavig.convertToInstant(header.getStringValue("DATE-OBS") + "Z"),
                                file, header.getDoubleValue("CCD-TEMP"),
                                header.getIntValue("NAXIS2"),
                                header.getIntValue("NAXIS1"));
                        subs.add(sub);
                    } catch (FitsException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * Scan the given path recursively for fits-format images, and generate a list of {@link AstroSub}
     * objects. A file will be considered if it ends in <code>.fit</code> or <code>.fits</code> and contains
     * a valid FITS header.
     */
    public List<AstroSub> scanPath(Path base) throws IOException {
        ArrayList<AstroSub> result = new ArrayList<>();

        Files.walkFileTree(base, new SimpleFileVisitor<>() {
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (file.toString().endsWith(".fits") || file.toString().endsWith(".fit")) {
                    try {
                        Fits f = new Fits(file.toAbsolutePath().toString());
                        Header header = f.getHDU(0).getHeader();

                        // debugFits(f);
                        UUID id = UUID.fromString(FilenameUtils.removeExtension(file.getFileName().toString()));

                        AstroSub sub = new AstroSub(
                                id, header.getStringValue("TELESCOP"),
                                header.getStringValue("INSTRUME"),
                                AstroSubType.valueOf(header.getStringValue("FRAME").toUpperCase()),
                                header.getDoubleValue("EXPTIME"),
                                header.getIntValue("GAIN"),
                                header.getIntValue("OFFSET"),
                                header.getStringValue("FILTER"),
                                header.getDoubleValue("RA"),
                                header.getDoubleValue("DEC"),
                                Snavig.convertToInstant(header.getStringValue("DATE-OBS") + "Z"),
                                file, header.getDoubleValue("CCD-TEMP"), header.getIntValue("NAXIS2"),
                                header.getIntValue("NAXIS1"));
                        result.add(sub);
                    } catch (FitsException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return result;
    }

    public ArrayList<AstroSub> getSubs() {
        return subs;
    }
}
