package leigh.astro.catalog;

import au.com.bytecode.opencsv.CSVReader;
import leigh.astro.astrometrics.Astrometrics;
import leigh.astro.unit.SphericalCoordinate;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * This class implements a stellar catalog, which maintains an index of stellar objects. All objects in the provided
 * catalogs have their positions noted in J2000 format.
 *
 * @author Alex Leigh
 */
public class StellarCatalog {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(StellarCatalog.class);
    private final List<CatalogObject> objects = new ArrayList<>();

    /**
     * Initialize a new catalog. This will load the stellar data from disk.
     *
     * @throws IOException
     */
    public StellarCatalog() throws IOException {
        load();
        logger.info("Built stellar catalog. [size: {}]", getObjects().size());
    }

    /**
     * Write the entire stellar catalog into the <code>ast_catalog_t</code> table in the astro schema.
     */
    public void writeToSql(Connection con) throws SQLException {
        try (PreparedStatement ps = con.prepareStatement(
                "INSERT INTO ast_catalog_t (nm, ra_deg, dec_deg, catalog_type_id) VALUES (?,?,?,?)")) {
            for (CatalogObject obj : objects) {
                ps.setString(1, obj.getName());
                ps.setDouble(2, obj.getJ2000Coordinate().getX());
                ps.setDouble(3, obj.getJ2000Coordinate().getY());
                ps.setInt(4, getTypeId(con, obj.getType()));
                getTypeId(con, obj.getType());
                ps.execute();
            }
        }
    }

    /**
     * For the given catalog type, return the type ID in the astro schema. This consults the
     * <code>ast_catalog_type_t</code> table. Returns <code>-1</code> if no match is found.
     */
    public int getTypeId(Connection con, String typeName) throws SQLException {
        try (PreparedStatement ps = con.prepareStatement("SELECT id FROM ast_catalog_type_t WHERE nm = ?")) {
            ps.setString(1, typeName);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    logger.info("Found type id: {}", rs.getInt(1));
                    return rs.getInt(1);
                }
            }
        }
        logger.warn("Failed tp find catalog type in dictionary table: {}", typeName);
        return -1;
    }

    /**
     * Load the built-in catalog. This currently consists of a database of the NGC objects. The object names
     * are formated such as <codeNGC1234</code>.
     *
     * @throws IOException
     */
    private void load() throws IOException {
        Class cls = this.getClass();
        InputStream i = cls.getClassLoader().getResourceAsStream("leigh/astro/catalog/ngc.csv");

        CSVReader reader = new CSVReader(new InputStreamReader(new BufferedInputStream(i)), ';');
        String[] record;

        // Header
        reader.readNext();

        int cnt = 0;

        while ((record = reader.readNext()) != null) {
            logger.debug("Processing: {}", record[0]);
            double raDeg = Astrometrics.parseHms(record[2]);
            double decDeg = Astrometrics.parseDms(record[3]);

            logger.debug("Object: [name: {}] [raDeg: {}] [decDeg: {}]", record[0], raDeg, decDeg);

            CatalogObject obj = new CatalogObject(record[0], new SphericalCoordinate(raDeg, decDeg),
                    record[1], cnt);
            objects.add(obj);
            cnt++;
        }
    }

    /**
     * Return a {@link List} containing all the objects in this catalog.
     *
     * @return The list.
     */
    public List<CatalogObject> getObjects() {
        return objects;
    }

    /**
     * Find a particular object by name in the catalog.
     *
     * @param name The name to search for.
     * @return The object or <code>null</code>.
     */
    public CatalogObject get(String name) {
        for (CatalogObject obj : objects) {
            if (obj.getName().equals(name)) return obj;
        }
        return null;
    }

}
