package leigh.astro.app;

import leigh.astro.catalog.CatalogObject;
import leigh.astro.catalog.StellarCatalog;
import leigh.astro.fits.AstroSub;
import leigh.astro.fits.AstroSubType;
import leigh.astro.fits.AstroSubUtil;
import leigh.astro.fits.FitsScanner;
import leigh.astro.iraf.Photometry;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.UniversalJob;
import leigh.mecha.util.VelocityWatch;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Bundle a series of fits files together for a particular observation target. This gathers both the lights
 * and calibrations together into a human-friendly package for further processing.
 * <p>
 * Usage: <code>BundlerApp adbPath object</code>
 * <p>
 * Limitations: Calibrations will be gathered via <code>AstroSubUtil.matchCalibrations()</code> for the
 * only one of the light subs. Which sub is undefined. For observation bundles which do not require multiple
 * calibration sets (same equipment, same configuration, same time period) this should be sufficient, but
 * is really not correct.
 *
 * @author Alex Leigh
 */
public class BundlerApp {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(BundlerApp.class);
    private static final boolean qualityArmed = false;

    public static void main(String args[]) throws Exception {
        if (args.length != 2) {
            logger.info("BundlerApp adbPath object");
            System.exit(UniversalJob.RET_BADARGS);
        }

        // Which subs do we have
        FitsScanner scanner = new FitsScanner();
        Path adbPath = Paths.get(args[0]);
        List<AstroSub> allSubs = scanner.scanPath(adbPath);
        logger.info("Located subs. [count: {}]", allSubs.size());

        // Which lights are of NGC6888
        StellarCatalog cat = new StellarCatalog();
        CatalogObject obj = cat.get(args[1]);

        if (obj == null) {
            logger.warn("Could not locate target object in catalog.");
            System.exit(UniversalJob.RET_OK);
        }

        logger.info("Located object in catalog. {}", obj);

        ArrayList<AstroSub> mySubs = new ArrayList<>();

        for (AstroSub sub : allSubs) {
            if (sub.contains(obj)) {
                mySubs.add(sub);
            }
        }

        if (mySubs.size() == 0) {
            logger.warn("No matching lights found. Exiting.");
            System.exit(UniversalJob.RET_OK);
        }

        logger.info("Matched lights. [count: {}]", mySubs.size());

        // FIXME: This should be handled on a per-sub/per-group basis
        AstroSub ex = allSubs.get(0);
        AstroSubUtil bundler = new AstroSubUtil();
        mySubs.addAll(bundler.matchCalibrations(ex, allSubs));

        Path objPath = Paths.get(args[1].toLowerCase());
        Files.createDirectory(objPath);

        ExecutorService svc = Executors.newFixedThreadPool(8);

        VelocityWatch vw = new VelocityWatch(logger);
        for (AstroSub sub : mySubs) {
            if (qualityArmed) {
                // If this is a light, does it meet quality requirements?
                if (sub.getFrameType().equals(AstroSubType.LIGHT)) {
                    Photometry pm = sub.getPhotometry();
                    logger.info("sub [id: {}] [filter: {}] [fwhm: {}] [stars: {}] [sharp: {}] [round: {}]",
                            sub.getId(), sub.getFilter(), pm.averageFWHM(), pm.getCount(), pm.averageSharp(), pm.averageRound());

                    // Not enough stars is a warning sign, and more sharp is more better.
                    if (pm.getCount() < 100 && pm.averageSharp() < 1.2) {
                        vw.event("discard");
                        continue;
                    }
                }
            }

            svc.submit(() -> {
                try {
                    Path src = adbPath.resolve(sub.getId().toString() + ".fits");
                    Path dst = getDstFile(sub, objPath);
                    Files.copy(src, dst);
                    vw.event("copy");
                    logger.info("Copy: {}", dst);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }

        svc.shutdown();
        vw.status();
    }

    /**
     * Given a sub return its bundle path, including filter, in the given distribution directory. For example,
     * given a distribution directory of <code>/tmp</code> the method might return <code>/tmp/H_Alpha/1234.fits</code>.
     */
    private static Path getDstFile(AstroSub sub, Path dst) throws IOException {
        // Everything else must be a calibration
        String dir = sub.getFrameType().toString() + "-" + sub.getFilter();
        Path filterPath = dst.resolve(dir.toLowerCase());
        // Performance of this is poor, but perhaps we do not care in this context.
        if (!Files.exists(filterPath)) {
            Files.createDirectory(filterPath);
        }

        return filterPath.resolve(sub.getId() + ".fits");
    }
}
