package leigh.astro.app;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import leigh.astro.db.AstroSql;
import leigh.astro.fits.AstroSub;
import leigh.astro.fits.AstroSubType;
import leigh.astro.fits.FitsScanner;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.UniversalJob;

import java.beans.PropertyVetoException;
import java.io.IOException;
import java.nio.file.Path;
import java.sql.*;
import java.util.List;
import java.util.UUID;

/**
 * This application processes FITS subs (light, dark, flat, bias) and loads their meta-information into a SQL
 * database using the astro schema. The app will attempt to process all FITS files not already present in the
 * database (based on the ID of the FITS file). The filenames for the FITS files are expected to be in the form
 * <code>id.fits</code> where <code>id</code> is a valid UUID.
 *
 * @author Alex Leigh
 */
public class SubLoaderApp {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(SubLoaderApp.class);
    private final int FILTER_RED_ID = 1;
    private final int FILTER_GREEN_ID = 2;
    private final int FILTER_BLUE_ID = 3;
    private final int FILTER_HA_ID = 4;
    private final int FILTER_SII_ID = 5;
    private final int FILTER_OIII_ID = 6;
    private final int FILTER_LUM_ID = 7;

    public static void main(String[] args) throws IOException, PropertyVetoException, SQLException {
        if (args.length != 1) {
            logger.info("Usage: SubLoaderApp path");
            System.exit(UniversalJob.RET_BADARGS);
        }

        SubLoaderApp loader = new SubLoaderApp();
        loader.process(Path.of(args[0]));
    }

    public void process(Path path) throws SQLException, IOException, PropertyVetoException {
        ComboPooledDataSource cpds = AstroSql.getDataSource();

        try (Connection con = cpds.getConnection()) {
            FitsScanner fs = new FitsScanner();
            List<AstroSub> allSubs = fs.scanPath(path);
            logger.info("Located subs: {}", allSubs.size());

            PreparedStatement insert = con.prepareStatement(
                    "INSERT INTO ast_sub_t (bin_id,capture_ts,sub_type_id,filter_id,length_sec,gain,off," +
                            "width_px,height_px,ccd_temp,center_ra_deg, center_dec_deg, train_id) VALUES " +
                            "(UUID_TO_BIN(?), ?,?,?,?,?,?,?,?,?,?,?,?)");

            for (AstroSub sub : allSubs) {
                if (!isLoaded(con, sub.getId())) {
                    logger.info("Loading metadata for sub: {}", sub);
                    insert.setString(1, sub.getId().toString());
                    insert.setTimestamp(2, Timestamp.from(sub.getTime()));
                    insert.setInt(3, getSubTypeId(sub.getFrameType()));
                    insert.setInt(4, getFilterId(sub.getFilter()));
                    insert.setDouble(5, sub.getLengthSeconds());
                    insert.setInt(6, sub.getGain());
                    insert.setInt(7, sub.getOffset());
                    insert.setInt(8, sub.getWidthPx());
                    insert.setInt(9, sub.getHeightPx());
                    insert.setDouble(10, sub.getCcdTemp());
                    insert.setDouble(11, sub.getObj().getX());
                    insert.setDouble(12, sub.getObj().getY());
                    // The current image train is id:2
                    insert.setInt(13, 2);
                    insert.execute();
                } else {
                    logger.info("Skipping already-loaded sub: {}", sub);
                }
            }
        }
    }

    /**
     * Returns <code>true</code> if the <code>ast_sub_t</code> table already contains a record for the given
     * id.
     */
    public boolean isLoaded(Connection con, UUID id) throws SQLException {
        try (PreparedStatement ps = con.prepareStatement(
                "SELECT COUNT(*) FROM ast_sub_t WHERE bin_id=UUID_TO_BIN(?)")) {
            ps.setString(1, id.toString());
            try (ResultSet rs = ps.executeQuery()) {
                // We assume this worked given the query. Anything else is an exception.
                rs.next();
                int x = rs.getInt(1);
                if (x > 0) return true;
            }
        }
        return false;
    }

    public int getSubTypeId(AstroSubType type) {
        switch (type) {
            case LIGHT:
                return 1;
            case DARK:
                return 2;
            case FLAT:
                return 3;
            case BIAS:
                return 4;
        }
        throw new IllegalArgumentException();
    }

    /**
     * Returns the SQL filter ID for the given FITS name. The FITS name is that which is stored in the FITS
     * file which is being processed. This very simplistic implementation is hard-coded with the known SQL
     * ids. Note that this is the <code>sub_filter_t(id)</code>, not <code>sub_filter_type(id)</code>.
     */
    public int getFilterId(String fitsName) {
        switch (fitsName) {
            case "Red":
                return FILTER_RED_ID;
            case "Green":
                return FILTER_GREEN_ID;
            case "Blue":
                return FILTER_BLUE_ID;
            case "H_Alpha":
                return FILTER_HA_ID;
            case "SII":
                return FILTER_SII_ID;
            case "OIII":
                return FILTER_OIII_ID;
            case "LPR":
                return FILTER_LUM_ID;
        }

        throw new IllegalArgumentException();
    }
}
