package mil.nga.geopackage.extension.style;

import java.util.List;

import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.core.contents.Contents;
import mil.nga.geopackage.features.columns.GeometryColumns;
import mil.nga.geopackage.features.user.FeatureRow;
import mil.nga.geopackage.features.user.FeatureTable;
import mil.nga.sf.GeometryType;

/**
 * Feature Table Styles, styles and icons for an individual feature table
 *
 * @author osbornb
 * @since 3.2.0
 */
public class FeatureTableStyles {

    /**
     * Feature Styles
     */
    private final FeatureStyleExtension featureStyleExtension;

    /**
     * Feature Table name
     */
    private final String tableName;

    /**
     * Cached table feature styles
     */
    private final FeatureStyles cachedTableFeatureStyles = new FeatureStyles();

    /**
     * Constructor
     *
     * @param geoPackage   GeoPackage
     * @param featureTable feature table
     */
    public FeatureTableStyles(GeoPackage geoPackage, FeatureTable featureTable) {
        this(geoPackage, featureTable.getTableName());
    }

    /**
     * Constructor
     *
     * @param geoPackage      GeoPackage
     * @param geometryColumns geometry columns
     */
    public FeatureTableStyles(GeoPackage geoPackage,
                              GeometryColumns geometryColumns) {
        this(geoPackage, geometryColumns.getTableName());
    }

    /**
     * Constructor
     *
     * @param geoPackage GeoPackage
     * @param contents   feature contents
     */
    public FeatureTableStyles(GeoPackage geoPackage, Contents contents) {
        this(geoPackage, contents.getTableName());
    }

    /**
     * Constructor
     *
     * @param geoPackage   GeoPackage
     * @param featureTable feature table
     */
    public FeatureTableStyles(GeoPackage geoPackage, String featureTable) {
        featureStyleExtension = new FeatureStyleExtension(geoPackage);
        tableName = featureTable;
        if (!geoPackage.isFeatureTable(featureTable)) {
            throw new GeoPackageException(
                    "Table must be a feature table. Table: " + featureTable
                            + ", Actual Type: "
                            + geoPackage.getTableType(featureTable));
        }
    }

    /**
     * Get the feature style extension
     *
     * @return feature style extension
     */
    public FeatureStyleExtension getFeatureStyleExtension() {
        return featureStyleExtension;
    }

    /**
     * Get the feature table name
     *
     * @return feature table name
     */
    public String getTableName() {
        return tableName;
    }

    /**
     * Determine if the GeoPackage has the extension for the table
     *
     * @return true if has extension
     */
    public boolean has() {
        return featureStyleExtension.has(tableName);
    }

    /**
     * Create style, icon, table style, and table icon relationships for the
     * feature table
     */
    public void createRelationships() {
        featureStyleExtension.createRelationships(tableName);
    }

    /**
     * Check if feature table has a style, icon, table style, or table icon
     * relationships
     *
     * @return true if has a relationship
     */
    public boolean hasRelationship() {
        return featureStyleExtension.hasRelationship(tableName);
    }

    /**
     * Create a style relationship for the feature table
     */
    public void createStyleRelationship() {
        featureStyleExtension.createStyleRelationship(tableName);
    }

    /**
     * Determine if a style relationship exists for the feature table
     *
     * @return true if relationship exists
     */
    public boolean hasStyleRelationship() {
        return featureStyleExtension.hasStyleRelationship(tableName);
    }

    /**
     * Create a feature table style relationship
     */
    public void createTableStyleRelationship() {
        featureStyleExtension.createTableStyleRelationship(tableName);
    }

    /**
     * Determine if feature table style relationship exists
     *
     * @return true if relationship exists
     */
    public boolean hasTableStyleRelationship() {
        return featureStyleExtension.hasTableStyleRelationship(tableName);
    }

    /**
     * Create an icon relationship for the feature table
     */
    public void createIconRelationship() {
        featureStyleExtension.createIconRelationship(tableName);
    }

    /**
     * Determine if an icon relationship exists for the feature table
     *
     * @return true if relationship exists
     */
    public boolean hasIconRelationship() {
        return featureStyleExtension.hasIconRelationship(tableName);
    }

    /**
     * Create a feature table icon relationship
     */
    public void createTableIconRelationship() {
        featureStyleExtension.createTableIconRelationship(tableName);
    }

    /**
     * Determine if feature table icon relationship exists
     *
     * @return true if relationship exists
     */
    public boolean hasTableIconRelationship() {
        return featureStyleExtension.hasTableIconRelationship(tableName);
    }

    /**
     * Delete the style and icon table and row relationships for the feature
     * table
     */
    public void deleteRelationships() {
        featureStyleExtension.deleteRelationships(tableName);
    }

    /**
     * Delete a style relationship for the feature table
     */
    public void deleteStyleRelationship() {
        featureStyleExtension.deleteStyleRelationship(tableName);
    }

    /**
     * Delete a table style relationship for the feature table
     */
    public void deleteTableStyleRelationship() {
        featureStyleExtension.deleteTableStyleRelationship(tableName);
    }

    /**
     * Delete a icon relationship for the feature table
     */
    public void deleteIconRelationship() {
        featureStyleExtension.deleteIconRelationship(tableName);
    }

    /**
     * Delete a table icon relationship for the feature table
     */
    public void deleteTableIconRelationship() {
        featureStyleExtension.deleteTableIconRelationship(tableName);
    }

    /**
     * Get a Style Mapping DAO
     *
     * @return style mapping DAO
     */
    public StyleMappingDao getStyleMappingDao() {
        return featureStyleExtension.getStyleMappingDao(tableName);
    }

    /**
     * Get a Table Style Mapping DAO
     *
     * @return table style mapping DAO
     */
    public StyleMappingDao getTableStyleMappingDao() {
        return featureStyleExtension.getTableStyleMappingDao(tableName);
    }

    /**
     * Get a Icon Mapping DAO
     *
     * @return icon mapping DAO
     */
    public StyleMappingDao getIconMappingDao() {
        return featureStyleExtension.getIconMappingDao(tableName);
    }

    /**
     * Get a Table Icon Mapping DAO
     *
     * @return table icon mapping DAO
     */
    public StyleMappingDao getTableIconMappingDao() {
        return featureStyleExtension.getTableIconMappingDao(tableName);
    }

    /**
     * Get a style DAO
     *
     * @return style DAO
     */
    public StyleDao getStyleDao() {
        return featureStyleExtension.getStyleDao();
    }

    /**
     * Get a icon DAO
     *
     * @return icon DAO
     */
    public IconDao getIconDao() {
        return featureStyleExtension.getIconDao();
    }

    /**
     * Get the table feature styles
     *
     * @return table feature styles or null
     */
    public FeatureStyles getTableFeatureStyles() {
        return featureStyleExtension.getTableFeatureStyles(tableName);
    }

    /**
     * Get the table styles
     *
     * @return table styles or null
     */
    public Styles getTableStyles() {
        return featureStyleExtension.getTableStyles(tableName);
    }

    /**
     * Get the cached table styles, querying and caching if needed
     *
     * @return cached table styles
     */
    public Styles getCachedTableStyles() {

        Styles styles = cachedTableFeatureStyles.getStyles();

        if (styles == null) {
            synchronized (cachedTableFeatureStyles) {
                styles = cachedTableFeatureStyles.getStyles();
                if (styles == null) {
                    styles = getTableStyles();
                    if (styles == null) {
                        styles = new Styles();
                    }
                    cachedTableFeatureStyles.setStyles(styles);
                }
            }
        }

        if (styles.isEmpty()) {
            styles = null;
        }

        return styles;
    }

    /**
     * Get the table style of the geometry type
     *
     * @param geometryType geometry type
     * @return style row
     */
    public StyleRow getTableStyle(GeometryType geometryType) {
        return featureStyleExtension.getTableStyle(tableName, geometryType);
    }

    /**
     * Get the table style default
     *
     * @return style row
     */
    public StyleRow getTableStyleDefault() {
        return featureStyleExtension.getTableStyleDefault(tableName);
    }

    /**
     * Get the table icons
     *
     * @return table icons or null
     */
    public Icons getTableIcons() {
        return featureStyleExtension.getTableIcons(tableName);
    }

    /**
     * Get the cached table icons, querying and caching if needed
     *
     * @return cached table icons
     */
    public Icons getCachedTableIcons() {

        Icons icons = cachedTableFeatureStyles.getIcons();

        if (icons == null) {
            synchronized (cachedTableFeatureStyles) {
                icons = cachedTableFeatureStyles.getIcons();
                if (icons == null) {
                    icons = getTableIcons();
                    if (icons == null) {
                        icons = new Icons();
                    }
                    cachedTableFeatureStyles.setIcons(icons);
                }
            }
        }

        if (icons.isEmpty()) {
            icons = null;
        }

        return icons;
    }

    /**
     * Get the table icon of the geometry type
     *
     * @param geometryType geometry type
     * @return icon row
     */
    public IconRow getTableIcon(GeometryType geometryType) {
        return featureStyleExtension.getTableIcon(tableName, geometryType);
    }

    /**
     * Get the table icon default
     *
     * @return icon row
     */
    public IconRow getTableIconDefault() {
        return featureStyleExtension.getTableIconDefault(tableName);
    }

    /**
     * Get the feature styles for the feature row
     *
     * @param featureRow feature row
     * @return feature styles or null
     */
    public FeatureStyles getFeatureStyles(FeatureRow featureRow) {
        return featureStyleExtension.getFeatureStyles(featureRow);
    }

    /**
     * Get the feature styles for the feature id
     *
     * @param featureId feature id
     * @return feature styles or null
     */
    public FeatureStyles getFeatureStyles(long featureId) {
        return featureStyleExtension.getFeatureStyles(tableName, featureId);
    }

    /**
     * Get the feature style (style and icon) of the feature row, searching in
     * order: feature geometry type style or icon, feature default style or
     * icon, table geometry type style or icon, table default style or icon
     *
     * @param featureRow feature row
     * @return feature style
     */
    public FeatureStyle getFeatureStyle(FeatureRow featureRow) {
        return getFeatureStyle(featureRow, featureRow.getGeometryType());
    }

    /**
     * Get the feature style (style and icon) of the feature row with the
     * provided geometry type, searching in order: feature geometry type style
     * or icon, feature default style or icon, table geometry type style or
     * icon, table default style or icon
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     * @return feature style
     */
    public FeatureStyle getFeatureStyle(FeatureRow featureRow,
                                        GeometryType geometryType) {
        return getFeatureStyle(featureRow.getId(), geometryType);
    }

    /**
     * Get the feature style default (style and icon) of the feature row,
     * searching in order: feature default style or icon, table default style or
     * icon
     *
     * @param featureRow feature row
     * @return feature style
     */
    public FeatureStyle getFeatureStyleDefault(FeatureRow featureRow) {
        return getFeatureStyle(featureRow.getId(), null);
    }

    /**
     * Get the feature style (style and icon) of the feature, searching in
     * order: feature geometry type style or icon, feature default style or
     * icon, table geometry type style or icon, table default style or icon
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     * @return feature style
     */
    public FeatureStyle getFeatureStyle(long featureId,
                                        GeometryType geometryType) {

        FeatureStyle featureStyle = null;

        StyleRow style = getStyle(featureId, geometryType);
        IconRow icon = getIcon(featureId, geometryType);

        if (style != null || icon != null) {
            featureStyle = new FeatureStyle(style, icon);
        }

        return featureStyle;
    }

    /**
     * Get the feature style (style and icon) of the feature, searching in
     * order: feature geometry type style or icon, feature default style or
     * icon, table geometry type style or icon, table default style or icon
     *
     * @param featureId feature id
     * @return feature style
     */
    public FeatureStyle getFeatureStyleDefault(long featureId) {
        return getFeatureStyle(featureId, null);
    }

    /**
     * Get the styles for the feature row
     *
     * @param featureRow feature row
     * @return styles or null
     */
    public Styles getStyles(FeatureRow featureRow) {
        return featureStyleExtension.getStyles(featureRow);
    }

    /**
     * Get the styles for the feature id
     *
     * @param featureId feature id
     * @return styles or null
     */
    public Styles getStyles(long featureId) {
        return featureStyleExtension.getStyles(tableName, featureId);
    }

    /**
     * Get the style of the feature row, searching in order: feature geometry
     * type style, feature default style, table geometry type style, table
     * default style
     *
     * @param featureRow feature row
     * @return style row
     */
    public StyleRow getStyle(FeatureRow featureRow) {
        return getStyle(featureRow, featureRow.getGeometryType());
    }

    /**
     * Get the style of the feature row with the provided geometry type,
     * searching in order: feature geometry type style, feature default style,
     * table geometry type style, table default style
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     * @return style row
     */
    public StyleRow getStyle(FeatureRow featureRow, GeometryType geometryType) {
        return getStyle(featureRow.getId(), geometryType);
    }

    /**
     * Get the default style of the feature row, searching in order: feature
     * default style, table default style
     *
     * @param featureRow feature row
     * @return style row
     */
    public StyleRow getStyleDefault(FeatureRow featureRow) {
        return getStyle(featureRow.getId(), null);
    }

    /**
     * Get the style of the feature, searching in order: feature geometry type
     * style, feature default style, table geometry type style, table default
     * style
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     * @return style row
     */
    public StyleRow getStyle(long featureId, GeometryType geometryType) {

        StyleRow styleRow = featureStyleExtension.getStyle(tableName,
                featureId, geometryType, false);

        if (styleRow == null) {

            // Table Style
            Styles styles = getCachedTableStyles();
            if (styles != null) {
                styleRow = styles.getStyle(geometryType);
            }

        }

        return styleRow;
    }

    /**
     * Get the default style of the feature, searching in order: feature default
     * style, table default style
     *
     * @param featureId feature id
     * @return style row
     */
    public StyleRow getStyleDefault(long featureId) {
        return getStyle(featureId, null);
    }

    /**
     * Get the icons for the feature row
     *
     * @param featureRow feature row
     * @return icons or null
     */
    public Icons getIcons(FeatureRow featureRow) {
        return featureStyleExtension.getIcons(featureRow);
    }

    /**
     * Get the icons for the feature id
     *
     * @param featureId feature id
     * @return icons or null
     */
    public Icons getIcons(long featureId) {
        return featureStyleExtension.getIcons(tableName, featureId);
    }

    /**
     * Get the icon of the feature row, searching in order: feature geometry
     * type icon, feature default icon, table geometry type icon, table default
     * icon
     *
     * @param featureRow feature row
     * @return icon row
     */
    public IconRow getIcon(FeatureRow featureRow) {
        return getIcon(featureRow, featureRow.getGeometryType());
    }

    /**
     * Get the icon of the feature row with the provided geometry type,
     * searching in order: feature geometry type icon, feature default icon,
     * table geometry type icon, table default icon
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     * @return icon row
     */
    public IconRow getIcon(FeatureRow featureRow, GeometryType geometryType) {
        return getIcon(featureRow.getId(), geometryType);
    }

    /**
     * Get the default icon of the feature row, searching in order: feature
     * default icon, table default icon
     *
     * @param featureRow feature row
     * @return icon row
     */
    public IconRow getIconDefault(FeatureRow featureRow) {
        return getIcon(featureRow.getId(), null);
    }

    /**
     * Get the icon of the feature, searching in order: feature geometry type
     * icon, feature default icon, table geometry type icon, table default icon
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     * @return icon row
     */
    public IconRow getIcon(long featureId, GeometryType geometryType) {

        IconRow iconRow = featureStyleExtension.getIcon(tableName, featureId,
                geometryType, false);

        if (iconRow == null) {

            // Table Icon
            Icons icons = getCachedTableIcons();
            if (icons != null) {
                iconRow = icons.getIcon(geometryType);
            }

        }

        return iconRow;
    }

    /**
     * Get the default icon of the feature, searching in order: feature default
     * icon, table default icon
     *
     * @param featureId feature id
     * @return icon row
     */
    public IconRow getIconDefault(long featureId) {
        return getIcon(featureId, null);
    }

    /**
     * Set the feature table default feature styles
     *
     * @param featureStyles default feature styles
     */
    public void setTableFeatureStyles(FeatureStyles featureStyles) {
        featureStyleExtension.setTableFeatureStyles(tableName, featureStyles);
        clearCachedTableFeatureStyles();
    }

    /**
     * Set the feature table default styles
     *
     * @param styles default styles
     */
    public void setTableStyles(Styles styles) {
        featureStyleExtension.setTableStyles(tableName, styles);
        clearCachedTableStyles();
    }

    /**
     * Set the feature table style default
     *
     * @param style style row
     */
    public void setTableStyleDefault(StyleRow style) {
        featureStyleExtension.setTableStyleDefault(tableName, style);
        clearCachedTableStyles();
    }

    /**
     * Set the feature table style for the geometry type
     *
     * @param geometryType geometry type
     * @param style        style row
     */
    public void setTableStyle(GeometryType geometryType, StyleRow style) {
        featureStyleExtension.setTableStyle(tableName, geometryType, style);
        clearCachedTableStyles();
    }

    /**
     * Set the feature table default icons
     *
     * @param icons default icons
     */
    public void setTableIcons(Icons icons) {
        featureStyleExtension.setTableIcons(tableName, icons);
        clearCachedTableIcons();
    }

    /**
     * Set the feature table icon default
     *
     * @param icon icon row
     */
    public void setTableIconDefault(IconRow icon) {
        featureStyleExtension.setTableIconDefault(tableName, icon);
        clearCachedTableIcons();
    }

    /**
     * Set the feature table icon for the geometry type
     *
     * @param geometryType geometry type
     * @param icon         icon row
     */
    public void setTableIcon(GeometryType geometryType, IconRow icon) {
        featureStyleExtension.setTableIcon(tableName, geometryType, icon);
        clearCachedTableIcons();
    }

    /**
     * Set the feature styles for the feature row
     *
     * @param featureRow    feature row
     * @param featureStyles feature styles
     */
    public void setFeatureStyles(FeatureRow featureRow,
                                 FeatureStyles featureStyles) {
        featureStyleExtension.setFeatureStyles(featureRow, featureStyles);
    }

    /**
     * Set the feature styles for the feature table and feature id
     *
     * @param featureId     feature id
     * @param featureStyles feature styles
     */
    public void setFeatureStyles(long featureId, FeatureStyles featureStyles) {
        featureStyleExtension.setFeatureStyles(tableName, featureId,
                featureStyles);
    }

    /**
     * Set the feature style (style and icon) of the feature row
     *
     * @param featureRow   feature row
     * @param featureStyle feature style
     */
    public void setFeatureStyle(FeatureRow featureRow, FeatureStyle featureStyle) {
        featureStyleExtension.setFeatureStyle(featureRow, featureStyle);
    }

    /**
     * Set the feature style (style and icon) of the feature row for the
     * specified geometry type
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     * @param featureStyle feature style
     */
    public void setFeatureStyle(FeatureRow featureRow,
                                GeometryType geometryType, FeatureStyle featureStyle) {
        featureStyleExtension.setFeatureStyle(featureRow, geometryType,
                featureStyle);
    }

    /**
     * Set the feature style default (style and icon) of the feature row
     *
     * @param featureRow   feature row
     * @param featureStyle feature style
     */
    public void setFeatureStyleDefault(FeatureRow featureRow,
                                       FeatureStyle featureStyle) {
        featureStyleExtension.setFeatureStyleDefault(featureRow, featureStyle);
    }

    /**
     * Set the feature style (style and icon) of the feature
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     * @param featureStyle feature style
     */
    public void setFeatureStyle(long featureId, GeometryType geometryType,
                                FeatureStyle featureStyle) {
        featureStyleExtension.setFeatureStyle(tableName, featureId,
                geometryType, featureStyle);
    }

    /**
     * Set the feature style (style and icon) of the feature
     *
     * @param featureId    feature id
     * @param featureStyle feature style
     */
    public void setFeatureStyleDefault(long featureId, FeatureStyle featureStyle) {
        featureStyleExtension.setFeatureStyleDefault(tableName, featureId,
                featureStyle);
    }

    /**
     * Set the styles for the feature row
     *
     * @param featureRow feature row
     * @param styles     styles
     */
    public void setStyles(FeatureRow featureRow, Styles styles) {
        featureStyleExtension.setStyles(featureRow, styles);
    }

    /**
     * Set the styles for the feature table and feature id
     *
     * @param featureId feature id
     * @param styles    styles
     */
    public void setStyles(long featureId, Styles styles) {
        featureStyleExtension.setStyles(tableName, featureId, styles);
    }

    /**
     * Set the style of the feature row
     *
     * @param featureRow feature row
     * @param style      style row
     */
    public void setStyle(FeatureRow featureRow, StyleRow style) {
        featureStyleExtension.setStyle(featureRow, style);
    }

    /**
     * Set the style of the feature row for the specified geometry type
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     * @param style        style row
     */
    public void setStyle(FeatureRow featureRow, GeometryType geometryType,
                         StyleRow style) {
        featureStyleExtension.setStyle(featureRow, geometryType, style);
    }

    /**
     * Set the default style of the feature row
     *
     * @param featureRow feature row
     * @param style      style row
     */
    public void setStyleDefault(FeatureRow featureRow, StyleRow style) {
        featureStyleExtension.setStyleDefault(featureRow, style);
    }

    /**
     * Set the style of the feature
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     * @param style        style row
     */
    public void setStyle(long featureId, GeometryType geometryType,
                         StyleRow style) {
        featureStyleExtension.setStyle(tableName, featureId, geometryType,
                style);
    }

    /**
     * Set the default style of the feature
     *
     * @param featureId feature id
     * @param style     style row
     */
    public void setStyleDefault(long featureId, StyleRow style) {
        featureStyleExtension.setStyleDefault(tableName, featureId, style);
    }

    /**
     * Set the icons for the feature row
     *
     * @param featureRow feature row
     * @param icons      icons
     */
    public void setIcons(FeatureRow featureRow, Icons icons) {
        featureStyleExtension.setIcons(featureRow, icons);
    }

    /**
     * Set the icons for the feature table and feature id
     *
     * @param featureId feature id
     * @param icons     icons
     */
    public void setIcons(long featureId, Icons icons) {
        featureStyleExtension.setIcons(tableName, featureId, icons);
    }

    /**
     * Set the icon of the feature row
     *
     * @param featureRow feature row
     * @param icon       icon row
     */
    public void setIcon(FeatureRow featureRow, IconRow icon) {
        featureStyleExtension.setIcon(featureRow, icon);
    }

    /**
     * Set the icon of the feature row for the specified geometry type
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     * @param icon         icon row
     */
    public void setIcon(FeatureRow featureRow, GeometryType geometryType,
                        IconRow icon) {
        featureStyleExtension.setIcon(featureRow, geometryType, icon);
    }

    /**
     * Set the default icon of the feature row
     *
     * @param featureRow feature row
     * @param icon       icon row
     */
    public void setIconDefault(FeatureRow featureRow, IconRow icon) {
        featureStyleExtension.setIconDefault(featureRow, icon);
    }

    /**
     * Get the icon of the feature, searching in order: feature geometry type
     * icon, feature default icon, table geometry type icon, table default icon
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     * @param icon         icon row
     */
    public void setIcon(long featureId, GeometryType geometryType, IconRow icon) {
        featureStyleExtension.setIcon(tableName, featureId, geometryType, icon);
    }

    /**
     * Set the default icon of the feature
     *
     * @param featureId feature id
     * @param icon      icon row
     */
    public void setIconDefault(long featureId, IconRow icon) {
        featureStyleExtension.setIconDefault(tableName, featureId, icon);
    }

    /**
     * Delete all feature styles including table styles, table icons, style, and
     * icons
     */
    public void deleteAllFeatureStyles() {
        featureStyleExtension.deleteAllFeatureStyles(tableName);
        clearCachedTableFeatureStyles();
    }

    /**
     * Delete all styles including table styles and feature row styles
     */
    public void deleteAllStyles() {
        featureStyleExtension.deleteAllStyles(tableName);
        clearCachedTableStyles();
    }

    /**
     * Delete all icons including table icons and feature row icons
     */
    public void deleteAllIcons() {
        featureStyleExtension.deleteAllIcons(tableName);
        clearCachedTableIcons();
    }

    /**
     * Delete the feature table feature styles
     */
    public void deleteTableFeatureStyles() {
        featureStyleExtension.deleteTableFeatureStyles(tableName);
        clearCachedTableFeatureStyles();
    }

    /**
     * Delete the feature table styles
     */
    public void deleteTableStyles() {
        featureStyleExtension.deleteTableStyles(tableName);
        clearCachedTableStyles();
    }

    /**
     * Delete the feature table default style
     */
    public void deleteTableStyleDefault() {
        featureStyleExtension.deleteTableStyleDefault(tableName);
        clearCachedTableStyles();
    }

    /**
     * Delete the feature table style for the geometry type
     *
     * @param geometryType geometry type
     */
    public void deleteTableStyle(GeometryType geometryType) {
        featureStyleExtension.deleteTableStyle(tableName, geometryType);
        clearCachedTableStyles();
    }

    /**
     * Delete the feature table icons
     */
    public void deleteTableIcons() {
        featureStyleExtension.deleteTableIcons(tableName);
        clearCachedTableIcons();
    }

    /**
     * Delete the feature table default icon
     */
    public void deleteTableIconDefault() {
        featureStyleExtension.deleteTableIconDefault(tableName);
        clearCachedTableIcons();
    }

    /**
     * Delete the feature table icon for the geometry type
     *
     * @param geometryType geometry type
     */
    public void deleteTableIcon(GeometryType geometryType) {
        featureStyleExtension.deleteTableIcon(tableName, geometryType);
        clearCachedTableIcons();
    }

    /**
     * Clear the cached table feature styles
     */
    public void clearCachedTableFeatureStyles() {
        synchronized (cachedTableFeatureStyles) {
            cachedTableFeatureStyles.setStyles(null);
            cachedTableFeatureStyles.setIcons(null);
        }
    }

    /**
     * Clear the cached table styles
     */
    public void clearCachedTableStyles() {
        synchronized (cachedTableFeatureStyles) {
            cachedTableFeatureStyles.setStyles(null);
        }
    }

    /**
     * Clear the cached table icons
     */
    public void clearCachedTableIcons() {
        synchronized (cachedTableFeatureStyles) {
            cachedTableFeatureStyles.setIcons(null);
        }
    }

    /**
     * Delete all feature styles
     */
    public void deleteFeatureStyles() {
        featureStyleExtension.deleteFeatureStyles(tableName);
    }

    /**
     * Delete all styles
     */
    public void deleteStyles() {
        featureStyleExtension.deleteStyles(tableName);
    }

    /**
     * Delete feature row styles
     *
     * @param featureRow feature row
     */
    public void deleteStyles(FeatureRow featureRow) {
        featureStyleExtension.deleteStyles(featureRow);
    }

    /**
     * Delete feature row styles
     *
     * @param featureId feature id
     */
    public void deleteStyles(long featureId) {
        featureStyleExtension.deleteStyles(tableName, featureId);
    }

    /**
     * Delete the feature row default style
     *
     * @param featureRow feature row
     */
    public void deleteStyleDefault(FeatureRow featureRow) {
        featureStyleExtension.deleteStyleDefault(featureRow);
    }

    /**
     * Delete the feature row default style
     *
     * @param featureId feature id
     */
    public void deleteStyleDefault(long featureId) {
        featureStyleExtension.deleteStyleDefault(tableName, featureId);
    }

    /**
     * Delete the feature row style for the feature row geometry type
     *
     * @param featureRow feature row
     */
    public void deleteStyle(FeatureRow featureRow) {
        featureStyleExtension.deleteStyle(featureRow);
    }

    /**
     * Delete the feature row style for the geometry type
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     */
    public void deleteStyle(FeatureRow featureRow, GeometryType geometryType) {
        featureStyleExtension.deleteStyle(featureRow, geometryType);
    }

    /**
     * Delete the feature row style for the geometry type
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     */
    public void deleteStyle(long featureId, GeometryType geometryType) {
        featureStyleExtension.deleteStyle(tableName, featureId, geometryType);
    }

    /**
     * Delete all icons
     */
    public void deleteIcons() {
        featureStyleExtension.deleteIcons(tableName);
    }

    /**
     * Delete feature row icons
     *
     * @param featureRow feature row
     */
    public void deleteIcons(FeatureRow featureRow) {
        featureStyleExtension.deleteIcons(featureRow);
    }

    /**
     * Delete feature row icons
     *
     * @param featureId feature id
     */
    public void deleteIcons(long featureId) {
        featureStyleExtension.deleteIcons(tableName, featureId);
    }

    /**
     * Delete the feature row default icon
     *
     * @param featureRow feature row
     */
    public void deleteIconDefault(FeatureRow featureRow) {
        featureStyleExtension.deleteIconDefault(featureRow);
    }

    /**
     * Delete the feature row default icon
     *
     * @param featureId feature id
     */
    public void deleteIconDefault(long featureId) {
        featureStyleExtension.deleteIconDefault(tableName, featureId);
    }

    /**
     * Delete the feature row icon for the feature row geometry type
     *
     * @param featureRow feature row
     */
    public void deleteIcon(FeatureRow featureRow) {
        featureStyleExtension.deleteIcon(featureRow);
    }

    /**
     * Delete the feature row icon for the geometry type
     *
     * @param featureRow   feature row
     * @param geometryType geometry type
     */
    public void deleteIcon(FeatureRow featureRow, GeometryType geometryType) {
        featureStyleExtension.deleteIcon(featureRow, geometryType);
    }

    /**
     * Delete the feature row icon for the geometry type
     *
     * @param featureId    feature id
     * @param geometryType geometry type
     */
    public void deleteIcon(long featureId, GeometryType geometryType) {
        featureStyleExtension.deleteIcon(tableName, featureId, geometryType);
    }

    /**
     * Get all the unique style row ids the table maps to
     *
     * @return style row ids
     */
    public List<Long> getAllTableStyleIds() {
        return featureStyleExtension.getAllTableStyleIds(tableName);
    }

    /**
     * Get all the unique icon row ids the table maps to
     *
     * @return icon row ids
     */
    public List<Long> getAllTableIconIds() {
        return featureStyleExtension.getAllTableIconIds(tableName);
    }

    /**
     * Get all the unique style row ids the features map to
     *
     * @return style row ids
     */
    public List<Long> getAllStyleIds() {
        return featureStyleExtension.getAllStyleIds(tableName);
    }

    /**
     * Get all the unique icon row ids the features map to
     *
     * @return icon row ids
     */
    public List<Long> getAllIconIds() {
        return featureStyleExtension.getAllIconIds(tableName);
    }

}


package mil.nga.geopackage.tiles.user;

import java.util.List;

import mil.nga.geopackage.db.GeoPackageDataType;
import mil.nga.geopackage.user.UserTableReader;

/**
 * Reads the metadata from an existing tile table
 *
 * @author osbornb
 */
public class TileTableReader extends
        UserTableReader<TileColumn, TileTable, TileRow, TileCursor> {

    /**
     * Constructor
     *
     * @param tableName table name
     */
    public TileTableReader(String tableName) {
        super(tableName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected TileTable createTable(String tableName,
                                    List<TileColumn> columnList) {
        return new TileTable(tableName, columnList);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected TileColumn createColumn(TileCursor cursor, int index, String name,
                                      String type, Long max, boolean notNull, int defaultValueIndex,
                                      boolean primaryKey) {

        GeoPackageDataType dataType = getDataType(type);

        Object defaultValue = cursor.getValue(
                defaultValueIndex, dataType);

        TileColumn column = new TileColumn(index, name, dataType, max, notNull,
                defaultValue, primaryKey);

        return column;
    }

}


package mil.nga.geopackage.extension;

import org.sqlite.database.sqlite.SQLiteDatabase;

import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.db.GeoPackageConnection;
import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.user.custom.UserCustomConnection;
import mil.nga.geopackage.user.custom.UserCustomDao;
import mil.nga.geopackage.user.custom.UserCustomTable;

/**
 * RTree Index Extension
 * TODO User defined functions are not currently supported for Android
 *
 * @author osbornb
 * @since 2.0.1
 */
public class RTreeIndexExtension extends RTreeIndexCoreExtension {

    /**
     * GeoPackage connection
     */
    private GeoPackageConnection connection;

    /**
     * SQLite Android Bindings connection
     */
    private final SQLiteDatabase database;

    /**
     * Constructor
     *
     * @param geoPackage GeoPackage
     */
    public RTreeIndexExtension(GeoPackage geoPackage) {
        super(geoPackage);
        connection = geoPackage.getConnection();
        database = connection.getDb().openOrGetBindingsDb();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public GeoPackage getGeoPackage() {
        return (GeoPackage) super.getGeoPackage();
    }

    /**
     * Get a RTree Index Table DAO for the feature table
     *
     * @param featureTable feature table
     * @return RTree Index Table DAO
     * @since 3.1.0
     */
    public RTreeIndexTableDao getTableDao(String featureTable) {
        return getTableDao(getGeoPackage().getFeatureDao(featureTable));
    }

    /**
     * Get a RTree Index Table DAO for the feature dao
     *
     * @param featureDao feature DAO
     * @return RTree Index Table DAO
     * @since 3.1.0
     */
    public RTreeIndexTableDao getTableDao(FeatureDao featureDao) {

        GeoPackageConnection connection = getGeoPackage().getConnection();
        UserCustomConnection userDb = new UserCustomConnection(connection);
        UserCustomTable userCustomTable = getRTreeTable(featureDao.getTable());
        UserCustomDao userCustomDao = new UserCustomDao(geoPackage.getName(),
                connection, userDb, userCustomTable);

        return new RTreeIndexTableDao(this, userCustomDao, featureDao);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createMinXFunction() {
        createFunction(MIN_X_FUNCTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createMaxXFunction() {
        createFunction(MAX_X_FUNCTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createMinYFunction() {
        createFunction(MIN_Y_FUNCTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createMaxYFunction() {
        createFunction(MAX_Y_FUNCTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createIsEmptyFunction() {
        createFunction(IS_EMPTY_FUNCTION);
    }

    /**
     * Create the function for the connection
     *
     * @param name function name
     */
    private void createFunction(String name) {
        throw new UnsupportedOperationException("User defined SQL functions are not supported. name: " + name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void executeSQL(String sql, boolean trigger) {
        if (trigger) {
            connection.execSQL(sql);
        } else {
            database.execSQL(sql);
        }
    }

}


package mil.nga.geopackage.extension.style;

import android.graphics.BitmapFactory;

import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.extension.related.media.MediaRow;
import mil.nga.geopackage.user.custom.UserCustomColumn;
import mil.nga.geopackage.user.custom.UserCustomRow;

/**
 * Icon Row containing the values from a single result set row
 *
 * @author osbornb
 * @since 3.2.0
 */
public class IconRow extends MediaRow {

    /**
     * Constructor to create an empty row
     */
    public IconRow() {
        this(new IconTable());
    }

    /**
     * Constructor to create an empty row
     *
     * @param table icon table
     */
    public IconRow(IconTable table) {
        super(table);
    }

    /**
     * Constructor
     *
     * @param userCustomRow user custom row
     */
    public IconRow(UserCustomRow userCustomRow) {
        super(userCustomRow);
    }

    /**
     * Copy Constructor
     *
     * @param iconRow icon row to copy
     */
    public IconRow(IconRow iconRow) {
        super(iconRow);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IconTable getTable() {
        return (IconTable) super.getTable();
    }

    /**
     * Get the name column index
     *
     * @return name column index
     */
    public int getNameColumnIndex() {
        return getTable().getNameColumnIndex();
    }

    /**
     * Get the name column
     *
     * @return name column
     */
    public UserCustomColumn getNameColumn() {
        return getTable().getNameColumn();
    }

    /**
     * Get the name
     *
     * @return name
     */
    public String getName() {
        return getValueString(getNameColumnIndex());
    }

    /**
     * Set the name
     *
     * @param name Feature Icon name
     */
    public void setName(String name) {
        setValue(getNameColumnIndex(), name);
    }

    /**
     * Get the description column index
     *
     * @return description column index
     */
    public int getDescriptionColumnIndex() {
        return getTable().getDescriptionColumnIndex();
    }

    /**
     * Get the description column
     *
     * @return description column
     */
    public UserCustomColumn getDescriptionColumn() {
        return getTable().getDescriptionColumn();
    }

    /**
     * Get the description
     *
     * @return description
     */
    public String getDescription() {
        return getValueString(getDescriptionColumnIndex());
    }

    /**
     * Set the description
     *
     * @param description Feature Icon description
     */
    public void setDescription(String description) {
        setValue(getDescriptionColumnIndex(), description);
    }

    /**
     * Get the width column index
     *
     * @return width column index
     */
    public int getWidthColumnIndex() {
        return getTable().getWidthColumnIndex();
    }

    /**
     * Get the width column
     *
     * @return width column
     */
    public UserCustomColumn getWidthColumn() {
        return getTable().getWidthColumn();
    }

    /**
     * Get the width
     *
     * @return width
     */
    public Double getWidth() {
        return (Double) getValue(getWidthColumnIndex());
    }

    /**
     * Set the width
     *
     * @param width Icon display width, when null use actual icon width
     */
    public void setWidth(Double width) {
        if (width != null && width < 0.0) {
            throw new GeoPackageException(
                    "Width must be greater than or equal to 0.0, invalid value: "
                            + width);
        }
        setValue(getWidthColumnIndex(), width);
    }

    /**
     * Get the width or derived width from the icon data and scaled as needed for the height
     *
     * @return derived width
     */
    public double getDerivedWidth() {

        Double width = getWidth();

        if (width == null) {
            width = getDerivedDimensions()[0];
        }

        return width;
    }

    /**
     * Get the height column index
     *
     * @return height column index
     */
    public int getHeightColumnIndex() {
        return getTable().getHeightColumnIndex();
    }

    /**
     * Get the height column
     *
     * @return height column
     */
    public UserCustomColumn getHeightColumn() {
        return getTable().getHeightColumn();
    }

    /**
     * Get the height
     *
     * @return height
     */
    public Double getHeight() {
        return (Double) getValue(getHeightColumnIndex());
    }

    /**
     * Set the height
     *
     * @param height Icon display height, when null use actual icon height
     */
    public void setHeight(Double height) {
        if (height != null && height < 0.0) {
            throw new GeoPackageException(
                    "Height must be greater than or equal to 0.0, invalid value: "
                            + height);
        }
        setValue(getHeightColumnIndex(), height);
    }

    /**
     * Get the height or derived height from the icon data and scaled as needed for the width
     *
     * @return derived height
     */
    public double getDerivedHeight() {

        Double height = getHeight();

        if (height == null) {
            height = getDerivedDimensions()[1];
        }

        return height;
    }

    /**
     * Get the derived width and height from the values and icon data, scaled as
     * needed
     *
     * @return derived dimensions array with two values, width at index 0, height at index 1
     */
    public double[] getDerivedDimensions() {

        Double width = getWidth();
        Double height = getHeight();

        if (width == null || height == null) {

            BitmapFactory.Options options = getDataBounds();
            int dataWidth = options.outWidth;
            int dataHeight = options.outHeight;

            if (width == null) {
                width = (double) dataWidth;

                if (height != null) {
                    width *= (height / dataHeight);
                }
            }

            if (height == null) {
                height = (double) dataHeight;

                if (width != null) {
                    height *= (width / dataWidth);
                }
            }

        }

        return new double[]{width, height};
    }

    /**
     * Get the anchor u column index
     *
     * @return anchor u column index
     */
    public int getAnchorUColumnIndex() {
        return getTable().getAnchorUColumnIndex();
    }

    /**
     * Get the anchor u column
     *
     * @return anchor u column
     */
    public UserCustomColumn getAnchorUColumn() {
        return getTable().getAnchorUColumn();
    }

    /**
     * Get the anchor u
     *
     * @return anchor u
     */
    public Double getAnchorU() {
        return (Double) getValue(getAnchorUColumnIndex());
    }

    /**
     * Set the anchor u
     *
     * @param anchor UV Mapping horizontal anchor distance inclusively between 0.0
     *               and 1.0 from the left edge, when null assume 0.5 (middle of
     *               icon)
     */
    public void setAnchorU(Double anchor) {
        validateAnchor(anchor);
        setValue(getAnchorUColumnIndex(), anchor);
    }

    /**
     * Get the anchor u value or the default value of 0.5
     *
     * @return anchor u value
     */
    public double getAnchorUOrDefault() {
        Double anchorU = getAnchorU();
        if (anchorU == null) {
            anchorU = 0.5;
        }
        return anchorU;
    }

    /**
     * Get the anchor v column index
     *
     * @return anchor v column index
     */
    public int getAnchorVColumnIndex() {
        return getTable().getAnchorVColumnIndex();
    }

    /**
     * Get the anchor v column
     *
     * @return anchor v column
     */
    public UserCustomColumn getAnchorVColumn() {
        return getTable().getAnchorVColumn();
    }

    /**
     * Get the anchor v
     *
     * @return anchor v
     */
    public Double getAnchorV() {
        return (Double) getValue(getAnchorVColumnIndex());
    }

    /**
     * Set the anchor v
     *
     * @param anchor UV Mapping vertical anchor distance inclusively between 0.0
     *               and 1.0 from the top edge, when null assume 1.0 (bottom of
     *               icon)
     */
    public void setAnchorV(Double anchor) {
        validateAnchor(anchor);
        setValue(getAnchorVColumnIndex(), anchor);
    }

    /**
     * Get the anchor v value or the default value of 1.0
     *
     * @return anchor v value
     */
    public double getAnchorVOrDefault() {
        Double anchorV = getAnchorV();
        if (anchorV == null) {
            anchorV = 1.0;
        }
        return anchorV;
    }

    /**
     * Validate the anchor value
     *
     * @param anchor anchor
     */
    private void validateAnchor(Double anchor) {
        if (anchor != null && (anchor < 0.0 || anchor > 1.0)) {
            throw new GeoPackageException(
                    "Anchor must be set inclusively between 0.0 and 1.0, invalid value: "
                            + anchor);
        }
    }

    /**
     * Copy the row
     *
     * @return row copy
     */
    public IconRow copy() {
        return new IconRow(this);
    }

}


package mil.nga.geopackage.tiles;

import android.util.DisplayMetrics;

/**
 * Tile utilities and constants
 *
 * @author osbornb
 * @since 3.2.0
 */
public class TileUtils {

    /**
     * Displayed device-independent pixels
     */
    public static final int TILE_DP = 256;

    /**
     * Tile pixels for default dpi tiles
     */
    public static final int TILE_PIXELS_DEFAULT = TILE_DP;

    /**
     * Tile pixels for high dpi tiles
     */
    public static final int TILE_PIXELS_HIGH = TILE_PIXELS_DEFAULT * 2;

    /**
     * High density scale
     */
    public static final float HIGH_DENSITY = ((float) DisplayMetrics.DENSITY_HIGH) / DisplayMetrics.DENSITY_DEFAULT;

    /**
     * Get the tile side (width and height) dimension based upon the display density scale
     *
     * @param density display density: {@link android.util.DisplayMetrics#density}
     * @return default tile length
     */
    public static int tileLength(float density) {
        int length;
        if (density < HIGH_DENSITY) {
            length = TILE_PIXELS_DEFAULT;
        } else {
            length = TILE_PIXELS_HIGH;
        }
        return length;
    }

    /**
     * Get the tile density based upon the display density scale and tile dimensions
     *
     * @param density    display density: {@link android.util.DisplayMetrics#density}
     * @param tileWidth  tile width
     * @param tileHeight tile height
     * @return tile density
     */
    public static float tileDensity(float density, int tileWidth, int tileHeight) {
        return tileDensity(density, Math.min(tileWidth, tileHeight));
    }

    /**
     * Get the tile density based upon the display density scale and tile length (width or height)
     *
     * @param density    display density: {@link android.util.DisplayMetrics#density}
     * @param tileLength tile length (width or height)
     * @return tile density
     */
    public static float tileDensity(float density, int tileLength) {
        return density * TILE_DP / tileLength;
    }

    /**
     * Get the density based upon the tile dimensions
     *
     * @param tileWidth  tile width
     * @param tileHeight tile height
     * @return density
     */
    public static float density(int tileWidth, int tileHeight) {
        return density(Math.min(tileWidth, tileHeight));
    }

    /**
     * Get the density based upon the tile length (width or height)
     *
     * @param tileLength tile length (width or height)
     * @return density
     */
    public static float density(int tileLength) {
        return ((float) tileLength) / TILE_DP;
    }

}


package mil.nga.geopackage.user;

import android.database.Cursor;
import android.util.Log;

import com.j256.ormlite.misc.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import mil.nga.geopackage.db.CoreSQLUtils;
import mil.nga.geopackage.db.GeoPackageDataType;

/**
 * Abstract User Invalid Cursor for handling failed rows due to large blobs
 *
 * @param <TColumn>  column type
 * @param <TTable>   table type
 * @param <TRow>     row type
 * @param <TCursor>  cursor type
 * @param <TUserDao> user dao type
 * @since 2.0.0
 */
public abstract class UserInvalidCursor<TColumn extends UserColumn, TTable extends UserTable<TColumn>, TRow extends UserRow<TColumn, TTable>, TCursor extends UserCursor<TColumn, TTable, TRow>, TUserDao extends UserDao<TColumn, TTable, TRow, TCursor>>
        implements UserCoreResult<TColumn, TTable, TRow> {

    /**
     * Chunk size to read large blobs. Max supported Android cursor window size is currently 2 mb, using 1 mb to ensure space
     */
    private static final int CHUNK_SIZE = 1048576;

    /**
     * User DAO
     */
    private TUserDao dao;

    /**
     * Requery cursor
     */
    private TCursor cursor;

    /**
     * Invalid positions from initial query cursor
     */
    private List<Integer> invalidPositions;

    /**
     * Blob columns to query in chunks
     */
    private List<TColumn> blobColumns;

    /**
     * Current position within the cursor
     */
    private int currentPosition = -1;

    /**
     * Constructor
     *
     * @param dao              user dao
     * @param cursor           requery cursor
     * @param invalidPositions invalid first cursor positions
     * @param blobColumns      blob columns
     */
    protected UserInvalidCursor(TUserDao dao, TCursor cursor, List<Integer> invalidPositions, List<TColumn> blobColumns) {
        this.dao = dao;
        this.cursor = cursor;
        this.invalidPositions = invalidPositions;
        this.blobColumns = blobColumns;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean moveToNext() {
        boolean hasNext = false;
        currentPosition++;
        if (currentPosition < invalidPositions.size()) {
            int invalidPosition = invalidPositions.get(currentPosition);
            hasNext = cursor.moveToPosition(invalidPosition);
        }
        return hasNext;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TRow getRow() {
        TRow row = cursor.getRow();
        if (row.hasId()) {
            for (UserColumn column : blobColumns) {
                readBlobValue(row, column);
            }
        }
        return row;
    }

    /**
     * Read the blob column value in chunks
     *
     * @param row    user row
     * @param column user blob column
     */
    private void readBlobValue(UserRow row, UserColumn column) {

        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        try {

            byte[] blobChunk = new byte[]{0};
            for (int i = 1; blobChunk.length > 0; i += CHUNK_SIZE) {
                if (i > 1) {
                    byteStream.write(blobChunk);
                }
                blobChunk = new byte[]{};
                String query = "select substr(" +
                        CoreSQLUtils.quoteWrap(column.getName()) + ", " + i + ", " + CHUNK_SIZE + ") from "
                        + CoreSQLUtils.quoteWrap(dao.getTableName()) + " where "
                        + CoreSQLUtils.quoteWrap(row.getPkColumn().getName()) + " = " + row.getId();
                Cursor blobCursor = dao.getDatabaseConnection().getDb().rawQuery(query, null);
                try {
                    if (blobCursor.moveToNext()) {
                        blobChunk = blobCursor.getBlob(0);
                    }
                } finally {
                    blobCursor.close();
                }
            }
            byte[] blob = byteStream.toByteArray();
            row.setValue(column.getIndex(), blob);

        } catch (IOException e) {
            Log.e(UserInvalidCursor.class.getSimpleName(), "Failed to read large blob value. Table: "
                    + dao.getTableName() + ", Column: " + column.getName() + ", Position: " + getPosition(), e);
        } finally {
            IOUtils.closeQuietly(byteStream);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        cursor.close();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getPosition() {
        return cursor.getPosition();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TRow getRow(int[] columnTypes, Object[] values) {
        return cursor.getRow(columnTypes, values);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getValue(TColumn column) {
        return cursor.getValue(column);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getValue(int index, GeoPackageDataType dataType) {
        return cursor.getValue(index, dataType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TTable getTable() {
        return cursor.getTable();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getCount() {
        return invalidPositions.size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean moveToFirst() {
        currentPosition = -1;
        return moveToNext();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean moveToPosition(int position) {
        boolean moved = false;
        if (position < invalidPositions.size()) {
            currentPosition = position;
            int invalidPosition = invalidPositions.get(currentPosition);
            moved = cursor.moveToPosition(invalidPosition);
        }
        return moved;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getValue(int index) {
        return cursor.getValue(index);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getColumnCount() {
        return cursor.getColumnCount();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getType(int columnIndex) {
        return cursor.getType(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getColumnIndex(String columnName) {
        return cursor.getColumnIndex(columnName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getString(int columnIndex) {
        return cursor.getString(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getLong(int columnIndex) {
        return cursor.getLong(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getInt(int columnIndex) {
        return cursor.getInt(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public short getShort(int columnIndex) {
        return cursor.getShort(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double getDouble(int columnIndex) {
        return cursor.getDouble(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float getFloat(int columnIndex) {
        return cursor.getFloat(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public byte[] getBlob(int columnIndex) {
        return cursor.getBlob(columnIndex);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean wasNull() {
        return cursor.wasNull();
    }

}


package mil.nga.geopackage.user.custom;

import java.util.List;

import mil.nga.geopackage.db.GeoPackageConnection;
import mil.nga.geopackage.db.GeoPackageDataType;
import mil.nga.geopackage.user.UserTableReader;

/**
 * Reads the metadata from an existing user custom table
 *
 * @author osbornb
 * @since 3.0.1
 */
public class UserCustomTableReader
        extends
        UserTableReader<UserCustomColumn, UserCustomTable, UserCustomRow, UserCustomCursor> {

    /**
     * Constructor
     *
     * @param tableName table name
     */
    public UserCustomTableReader(String tableName) {
        super(tableName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected UserCustomTable createTable(String tableName,
                                          List<UserCustomColumn> columnList) {
        return new UserCustomTable(tableName, columnList);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected UserCustomColumn createColumn(UserCustomCursor result,
                                            int index, String name, String type, Long max, boolean notNull,
                                            int defaultValueIndex, boolean primaryKey) {

        GeoPackageDataType dataType = getDataType(type);

        Object defaultValue = result.getValue(defaultValueIndex, dataType);

        UserCustomColumn column = new UserCustomColumn(index, name, dataType,
                max, notNull, defaultValue, primaryKey);

        return column;
    }

    /**
     * Read the table
     *
     * @param connection GeoPackage connection
     * @param tableName  table name
     * @return table
     */
    public static UserCustomTable readTable(GeoPackageConnection connection,
                                            String tableName) {
        UserCustomTableReader tableReader = new UserCustomTableReader(tableName);
        UserCustomTable customTable = tableReader.readTable(new UserCustomWrapperConnection(connection));
        return customTable;
    }

}


package mil.nga.geopackage.extension.style;

import java.util.regex.Pattern;

import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.attributes.AttributesColumn;
import mil.nga.geopackage.attributes.AttributesRow;
import mil.nga.geopackage.style.Color;

/**
 * Style Row containing the values from a single result set row
 *
 * @author osbornb
 * @since 3.2.0
 */
public class StyleRow extends AttributesRow {

    /**
     * Color hex pattern
     */
    private Pattern colorPattern = Pattern.compile("^#([0-9a-fA-F]{3}){1,2}$");

    /**
     * Constructor to create an empty row
     */
    public StyleRow() {
        this(new StyleTable());
    }

    /**
     * Constructor to create an empty row
     *
     * @param table style table
     */
    public StyleRow(StyleTable table) {
        super(table);
    }

    /**
     * Constructor
     *
     * @param attributesRow attributes row
     */
    public StyleRow(AttributesRow attributesRow) {
        super(attributesRow);
    }

    /**
     * Copy Constructor
     *
     * @param styleRow style row to copy
     */
    public StyleRow(StyleRow styleRow) {
        super(styleRow);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StyleTable getTable() {
        return (StyleTable) super.getTable();
    }

    /**
     * Get the name column index
     *
     * @return name column index
     */
    public int getNameColumnIndex() {
        return getTable().getNameColumnIndex();
    }

    /**
     * Get the name column
     *
     * @return name column
     */
    public AttributesColumn getNameColumn() {
        return getTable().getNameColumn();
    }

    /**
     * Get the name
     *
     * @return name
     */
    public String getName() {
        return getValueString(getNameColumnIndex());
    }

    /**
     * Set the name
     *
     * @param name Feature Style name
     */
    public void setName(String name) {
        setValue(getNameColumnIndex(), name);
    }

    /**
     * Get the description column index
     *
     * @return description column index
     */
    public int getDescriptionColumnIndex() {
        return getTable().getDescriptionColumnIndex();
    }

    /**
     * Get the description column
     *
     * @return description column
     */
    public AttributesColumn getDescriptionColumn() {
        return getTable().getDescriptionColumn();
    }

    /**
     * Get the description
     *
     * @return description
     */
    public String getDescription() {
        return getValueString(getDescriptionColumnIndex());
    }

    /**
     * Set the description
     *
     * @param description Feature Style description
     */
    public void setDescription(String description) {
        setValue(getDescriptionColumnIndex(), description);
    }

    /**
     * Get the color column index
     *
     * @return color column index
     */
    public int getColorColumnIndex() {
        return getTable().getColorColumnIndex();
    }

    /**
     * Get the color column
     *
     * @return color column
     */
    public AttributesColumn getColorColumn() {
        return getTable().getColorColumn();
    }

    /**
     * Get the style color
     *
     * @return color
     */
    public Color getColor() {
        return createColor(getHexColor(), getOpacity());
    }

    /**
     * Check if the style has a color
     *
     * @return true if has a color
     */
    public boolean hasColor() {
        return hasColor(getHexColor(), getOpacity());
    }

    /**
     * Get the color
     *
     * @return color
     */
    public String getHexColor() {
        return getValueString(getColorColumnIndex());
    }

    /**
     * Set the color
     *
     * @param color color
     */
    public void setColor(Color color) {
        String hex = null;
        Double opacity = null;
        if (color != null) {
            hex = color.getColorHexShorthand();
            opacity = new Double(color.getOpacity());
        }
        setColor(hex);
        setOpacity(opacity);
    }

    /**
     * Set the color
     *
     * @param color Geometry color in hex format #RRGGBB or #RGB
     */
    public void setColor(String color) {
        color = validateColor(color);
        setValue(getColorColumnIndex(), color);
    }

    /**
     * Get the color or default value
     *
     * @return color
     */
    public Color getColorOrDefault() {
        Color color = getColor();
        if (color == null) {
            color = new Color();
        }
        return color;
    }

    /**
     * Get the color or default value
     *
     * @return color
     */
    public String getHexColorOrDefault() {
        String color = getHexColor();
        if (color == null) {
            color = "#000000";
        }
        return color;
    }

    /**
     * Get the opacity column index
     *
     * @return opacity column index
     */
    public int getOpacityColumnIndex() {
        return getTable().getOpacityColumnIndex();
    }

    /**
     * Get the opacity column
     *
     * @return opacity column
     */
    public AttributesColumn getOpacityColumn() {
        return getTable().getOpacityColumn();
    }

    /**
     * Get the opacity
     *
     * @return opacity
     */
    public Double getOpacity() {
        return (Double) getValue(getOpacityColumnIndex());
    }

    /**
     * Set the opacity
     *
     * @param opacity Geometry color opacity inclusively between 0.0 and 1.0
     */
    public void setOpacity(Double opacity) {
        validateOpacity(opacity);
        setValue(getOpacityColumnIndex(), opacity);
    }

    /**
     * Get the opacity or default value
     *
     * @return opacity
     */
    public double getOpacityOrDefault() {
        Double opacity = getOpacity();
        if (opacity == null) {
            opacity = 1.0;
        }
        return opacity;
    }

    /**
     * Get the width column index
     *
     * @return width column index
     */
    public int getWidthColumnIndex() {
        return getTable().getWidthColumnIndex();
    }

    /**
     * Get the width column
     *
     * @return width column
     */
    public AttributesColumn getWidthColumn() {
        return getTable().getWidthColumn();
    }

    /**
     * Get the width
     *
     * @return width
     */
    public Double getWidth() {
        return (Double) getValue(getWidthColumnIndex());
    }

    /**
     * Set the width
     *
     * @param width Geometry line stroke or point width greater than or equal to
     *              0.0
     */
    public void setWidth(Double width) {
        if (width != null && width < 0.0) {
            throw new GeoPackageException(
                    "Width must be greater than or equal to 0.0, invalid value: "
                            + width);
        }
        setValue(getWidthColumnIndex(), width);
    }

    /**
     * Get the width value or default width
     *
     * @return width
     */
    public double getWidthOrDefault() {
        Double width = getWidth();
        if (width == null) {
            width = 1.0;
        }
        return width;
    }

    /**
     * Get the fill color column index
     *
     * @return fill color column index
     */
    public int getFillColorColumnIndex() {
        return getTable().getFillColorColumnIndex();
    }

    /**
     * Get the fill color column
     *
     * @return fill color column
     */
    public AttributesColumn getFillColorColumn() {
        return getTable().getFillColorColumn();
    }

    /**
     * Get the style fill color
     *
     * @return fill color
     */
    public Color getFillColor() {
        return createColor(getFillHexColor(), getFillOpacity());
    }

    /**
     * Check if the style has a fill color
     *
     * @return true if has a fill color
     */
    public boolean hasFillColor() {
        return hasColor(getFillHexColor(), getFillOpacity());
    }

    /**
     * Get the fill color
     *
     * @return fill color
     */
    public String getFillHexColor() {
        return getValueString(getFillColorColumnIndex());
    }

    /**
     * Set the color
     *
     * @param color color
     */
    public void setFillColor(Color color) {
        String hex = null;
        Double opacity = null;
        if (color != null) {
            hex = color.getColorHexShorthand();
            opacity = new Double(color.getOpacity());
        }
        setFillColor(hex);
        setFillOpacity(opacity);
    }

    /**
     * Set the fill color
     *
     * @param fillColor Closed geometry fill color in hex format #RRGGBB or #RGB
     */
    public void setFillColor(String fillColor) {
        fillColor = validateColor(fillColor);
        setValue(getFillColorColumnIndex(), fillColor);
    }

    /**
     * Get the fill opacity column index
     *
     * @return fill opacity column index
     */
    public int getFillOpacityColumnIndex() {
        return getTable().getFillOpacityColumnIndex();
    }

    /**
     * Get the fill opacity column
     *
     * @return fill opacity column
     */
    public AttributesColumn getFillOpacityColumn() {
        return getTable().getFillOpacityColumn();
    }

    /**
     * Get the fill opacity
     *
     * @return fill opacity
     */
    public Double getFillOpacity() {
        return (Double) getValue(getFillOpacityColumnIndex());
    }

    /**
     * Set the fill opacity
     *
     * @param fillOpacity Closed geometry fill color opacity inclusively between 0.0 and
     *                    1.0
     */
    public void setFillOpacity(Double fillOpacity) {
        validateOpacity(fillOpacity);
        setValue(getFillOpacityColumnIndex(), fillOpacity);
    }

    /**
     * Get the fill opacity or default value
     *
     * @return fill opacity
     */
    public double getFillOpacityOrDefault() {
        Double fillOpacity = getFillOpacity();
        if (fillOpacity == null) {
            fillOpacity = 1.0;
        }
        return fillOpacity;
    }

    /**
     * Validate and adjust the color value
     *
     * @param color color
     */
    private String validateColor(String color) {
        String validated = color;
        if (color != null) {
            if (!color.startsWith("#")) {
                validated = "#" + color;
            }
            if (!colorPattern.matcher(validated).matches()) {
                throw new GeoPackageException(
                        "Color must be in hex format #RRGGBB or #RGB, invalid value: "
                                + color);
            }
            validated = validated.toUpperCase();
        }
        return validated;
    }

    /**
     * Validate the opacity value
     *
     * @param opacity opacity
     */
    private void validateOpacity(Double opacity) {
        if (opacity != null && (opacity < 0.0 || opacity > 1.0)) {
            throw new GeoPackageException(
                    "Opacity must be set inclusively between 0.0 and 1.0, invalid value: "
                            + opacity);
        }
    }

    /**
     * Create a color from the hex color and opacity
     *
     * @param hexColor hex color
     * @param opacity  opacity
     * @return color or null
     */
    private Color createColor(String hexColor, Double opacity) {
        Color color = null;
        if (hexColor != null || opacity != null) {
            color = new Color();
            if (hexColor != null) {
                color.setColor(hexColor);
            }
            if (opacity != null) {
                color.setOpacity(opacity.floatValue());
            }
        }
        return color;
    }

    /**
     * Determine if a color exists from the hex color and opacity
     *
     * @param hexColor hex color
     * @param opacity  opacity
     * @return true if has a color
     */
    private boolean hasColor(String hexColor, Double opacity) {
        return hexColor != null || opacity != null;
    }

    /**
     * Copy the row
     *
     * @return row copy
     */
    public StyleRow copy() {
        return new StyleRow(this);
    }

}


package mil.nga.geopackage.factory;

import android.content.Context;

import com.j256.ormlite.logger.LocalLog;

import mil.nga.geopackage.GeoPackageManager;

/**
 * GeoPackage Factory to get a GeoPackage Manager
 *
 * @author osbornb
 */
public class GeoPackageFactory {

    static {
        // Change the ORMLite log level
        System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "INFO");
    }

    /**
     * Get a GeoPackage Manager
     *
     * @param context context
     * @return GeoPackage manager
     */
    public static GeoPackageManager getManager(Context context) {
        Thread.currentThread().setContextClassLoader(GeoPackageManager.class.getClassLoader());
        return new GeoPackageManagerImpl(context);
    }

}