MoneyWiseSecurityPrice.java

/*
 * MoneyWise: Finance Application
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.moneywise.data.basic;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDateFormatter;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity.MoneyWiseSecurityList;
import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInstanceMap;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataMapItem;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedDataItem;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedFieldSet;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedPair;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedValues;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;

import java.util.ArrayList;
import java.util.Currency;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/**
 * SecurityPrice data type.
 *
 * @author Tony Washer
 */
public class MoneyWiseSecurityPrice
        extends PrometheusEncryptedDataItem {
    /**
     * Object name.
     */
    public static final String OBJECT_NAME = MoneyWiseBasicDataType.SECURITYPRICE.getItemName();

    /**
     * List name.
     */
    public static final String LIST_NAME = MoneyWiseBasicDataType.SECURITYPRICE.getListName();

    /**
     * Report fields.
     */
    private static final PrometheusEncryptedFieldSet<MoneyWiseSecurityPrice> FIELD_DEFS = PrometheusEncryptedFieldSet.newEncryptedFieldSet(MoneyWiseSecurityPrice.class);

    /*
     * FieldIds.
     */
    static {
        FIELD_DEFS.declareLinkField(MoneyWiseBasicDataType.SECURITY);
        FIELD_DEFS.declareDateField(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
        FIELD_DEFS.declareEncryptedPriceField(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE);
    }

    /**
     * Invalid currency error.
     */
    public static final String ERROR_CURRENCY = MoneyWiseBasicResource.MONEYWISEDATA_ERROR_CURRENCY.getValue();

    /**
     * Copy Constructor.
     *
     * @param pList  the list
     * @param pPrice The Price
     */
    protected MoneyWiseSecurityPrice(final MoneyWiseSecurityPriceBaseList<? extends MoneyWiseSecurityPrice> pList,
                                     final MoneyWiseSecurityPrice pPrice) {
        /* Set standard values */
        super(pList, pPrice);
    }

    /**
     * Edit Constructor.
     *
     * @param pList the list
     */
    public MoneyWiseSecurityPrice(final MoneyWiseSecurityPriceBaseList<? extends MoneyWiseSecurityPrice> pList) {
        super(pList, 0);
        setNextDataKeySet();
    }

    /**
     * Values constructor.
     *
     * @param pList   the List to add to
     * @param pValues the values constructor
     * @throws OceanusException on error
     */
    private MoneyWiseSecurityPrice(final MoneyWiseSecurityPriceList pList,
                                   final PrometheusDataValues pValues) throws OceanusException {
        /* Initialise the item */
        super(pList, pValues);

        /* Access formatter */
        final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();

        /* Protect against exceptions */
        try {
            /* Store the Date */
            Object myValue = pValues.getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
            if (myValue instanceof OceanusDate d) {
                setValueDate(d);
            } else if (myValue instanceof String s) {
                final OceanusDateFormatter myParser = myFormatter.getDateFormatter();
                setValueDate(myParser.parseDate(s));
            }

            /* Store the Security */
            myValue = pValues.getValue(MoneyWiseBasicDataType.SECURITY);
            if (myValue instanceof Integer i) {
                setValueSecurity(i);
            } else if (myValue instanceof String s) {
                setValueSecurity(s);
            }

            /* Store the Price */
            myValue = pValues.getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE);
            if (myValue instanceof OceanusPrice p) {
                setValuePrice(p);
            } else if (myValue instanceof byte[] ba) {
                setValuePrice(ba);
            } else if (myValue instanceof String myString) {
                setValuePrice(myString);
                setValuePrice(myFormatter.parseValue(myString, OceanusPrice.class));
            }

            /* Catch Exceptions */
        } catch (IllegalArgumentException
                 | OceanusException e) {
            /* Pass on exception */
            throw new MoneyWiseDataException(this, ERROR_CREATEITEM, e);
        }
    }

    @Override
    public MetisFieldSetDef getDataFieldSet() {
        return FIELD_DEFS;
    }

    @Override
    public boolean includeXmlField(final MetisDataFieldId pField) {
        /* Determine whether fields should be included */
        if (MoneyWiseBasicDataType.SECURITY.equals(pField)) {
            return true;
        }
        if (MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE.equals(pField)) {
            return true;
        }
        if (MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE.equals(pField)) {
            return true;
        }

        /* Pass call on */
        return super.includeXmlField(pField);
    }

    @Override
    public String toString() {
        /* Access Key Values */
        final PrometheusEncryptedValues myValues = getValues();
        final Object mySecurity = myValues.getValue(MoneyWiseBasicDataType.SECURITY);
        final Object myDate = myValues.getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
        final Object myPrice = myValues.getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE, OceanusPrice.class);

        /* Access formatter */
        final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();

        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();
        myBuilder.append(myFormatter.formatObject(mySecurity));
        myBuilder.append(": ");
        myBuilder.append(myFormatter.formatObject(myPrice));
        myBuilder.append('@');
        myBuilder.append(myFormatter.formatObject(myDate));

        /* return it */
        return myBuilder.toString();
    }

    @Override
    public String formatObject(final OceanusDataFormatter pFormatter) {
        return toString();
    }

    /**
     * Obtain Price.
     *
     * @return the price
     */
    public OceanusPrice getPrice() {
        return getValues().getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE, OceanusPrice.class);
    }

    /**
     * Obtain Encrypted Price.
     *
     * @return the Bytes
     */
    public byte[] getPriceBytes() {
        return getValues().getEncryptedBytes(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE);
    }

    /**
     * Obtain Encrypted Price Field.
     *
     * @return the field
     */
    public PrometheusEncryptedPair getPriceField() {
        return getValues().getEncryptedPair(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE);
    }

    /**
     * Obtain Date.
     *
     * @return the date
     */
    public OceanusDate getDate() {
        return getValues().getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, OceanusDate.class);
    }

    /**
     * Obtain Security.
     *
     * @return the security
     */
    public MoneyWiseSecurity getSecurity() {
        return getValues().getValue(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurity.class);
    }

    /**
     * Obtain SecurityId.
     *
     * @return the securityId
     */
    public Integer getSecurityId() {
        final MoneyWiseSecurity mySecurity = getSecurity();
        return mySecurity == null
                ? null
                : mySecurity.getIndexedId();
    }

    /**
     * Obtain SecurityName.
     *
     * @return the securityName
     */
    public String getSecurityName() {
        final MoneyWiseSecurity mySecurity = getSecurity();
        return mySecurity == null
                ? null
                : mySecurity.getName();
    }

    /**
     * Set the security.
     *
     * @param pValue the security
     */
    private void setValueSecurity(final MoneyWiseSecurity pValue) {
        getValues().setUncheckedValue(MoneyWiseBasicDataType.SECURITY, pValue);
    }

    /**
     * Set the security id.
     *
     * @param pId the security id
     */
    private void setValueSecurity(final Integer pId) {
        getValues().setUncheckedValue(MoneyWiseBasicDataType.SECURITY, pId);
    }

    /**
     * Set the security name.
     *
     * @param pName the security name
     */
    private void setValueSecurity(final String pName) {
        getValues().setUncheckedValue(MoneyWiseBasicDataType.SECURITY, pName);
    }

    /**
     * Set the price.
     *
     * @param pValue the price
     * @throws OceanusException on error
     */
    private void setValuePrice(final OceanusPrice pValue) throws OceanusException {
        setEncryptedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE, pValue);
    }

    /**
     * Set the encrypted price.
     *
     * @param pBytes the encrypted price
     * @throws OceanusException on error
     */
    private void setValuePrice(final byte[] pBytes) throws OceanusException {
        setEncryptedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE, pBytes, OceanusPrice.class);
    }

    /**
     * Set the price.
     *
     * @param pValue the price
     */
    private void setValuePrice(final String pValue) {
        getValues().setUncheckedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE, pValue);
    }

    /**
     * Set the price.
     *
     * @param pValue the price
     */
    public void setValuePrice(final PrometheusEncryptedPair pValue) {
        getValues().setUncheckedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE, pValue);
    }

    /**
     * Set the date.
     *
     * @param pValue the date
     */
    private void setValueDate(final OceanusDate pValue) {
        getValues().setUncheckedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, pValue);
    }

    @Override
    public MoneyWiseDataSet getDataSet() {
        return (MoneyWiseDataSet) super.getDataSet();
    }

    @Override
    @SuppressWarnings("unchecked")
    public MoneyWiseSecurityPriceBaseList<? extends MoneyWiseSecurityPrice> getList() {
        return (MoneyWiseSecurityPriceBaseList<? extends MoneyWiseSecurityPrice>) super.getList();
    }

    @Override
    public MoneyWiseSecurityPrice getBase() {
        return (MoneyWiseSecurityPrice) super.getBase();
    }

    @Override
    public int compareValues(final PrometheusDataItem pThat) {
        /* Access as SecurityPrice */
        final MoneyWiseSecurityPrice myThat = (MoneyWiseSecurityPrice) pThat;

        /* If header settings differ */
        if (isHeader() != pThat.isHeader()) {
            return isHeader()
                    ? -1
                    : 1;
        }

        /* If the date differs */
        final int iDiff = MetisDataDifference.compareObject(getDate(), myThat.getDate());
        if (iDiff != 0) {
            /* Sort in reverse date order !! */
            return -iDiff;
        }

        /* Compare the securities */
        return getSecurity().compareTo(myThat.getSecurity());
    }

    @Override
    public void resolveDataSetLinks() throws OceanusException {
        /* Update the Encryption details */
        super.resolveDataSetLinks();

        /* Resolve data links */
        final MoneyWiseDataSet myData = getDataSet();
        resolveDataLink(MoneyWiseBasicDataType.SECURITY, myData.getSecurities());
    }

    /**
     * Resolve links in an editSet.
     *
     * @param pEditSet the edit Set
     * @throws OceanusException on error
     */
    protected void resolveEditSetLinks(final PrometheusEditSet pEditSet) throws OceanusException {
        /* Resolve parent within list */
        final MoneyWiseSecurityList mySecurities = pEditSet.getDataList(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurityList.class);
        resolveDataLink(MoneyWiseBasicDataType.SECURITY, mySecurities);
    }

    /**
     * Set the security.
     *
     * @param pValue the security
     */
    public void setSecurity(final MoneyWiseSecurity pValue) {
        setValueSecurity(pValue);
    }

    /**
     * Set a new price.
     *
     * @param pPrice the price
     * @throws OceanusException on error
     */
    public void setPrice(final OceanusPrice pPrice) throws OceanusException {
        setValuePrice(pPrice);
    }

    /**
     * Set a new date.
     *
     * @param pDate the new date
     */
    public void setDate(final OceanusDate pDate) {
        setValueDate(pDate);
    }

    @Override
    public void touchUnderlyingItems() {
        /* touch the underlying security */
        getSecurity().touchItem(this);
    }

    @Override
    public void touchOnUpdate() {
        /* Touch security */
        getSecurity().touchItem(this);
    }

    @Override
    public boolean applyChanges(final PrometheusDataItem pPrice) {
        /* Can only update from a SecurityPrice */
        if (!(pPrice instanceof MoneyWiseSecurityPrice)) {
            return false;
        }
        final MoneyWiseSecurityPrice myPrice = (MoneyWiseSecurityPrice) pPrice;

        /* Store the current detail into history */
        pushHistory();

        /* Update the price if required */
        if (!MetisDataDifference.isEqual(getPrice(), myPrice.getPrice())) {
            setValuePrice(myPrice.getPriceField());
        }

        /* Update the date if required */
        if (!MetisDataDifference.isEqual(getDate(), myPrice.getDate())) {
            setValueDate(myPrice.getDate());
        }

        /* Check for changes */
        return checkForHistory();
    }

    @Override
    public void adjustMapForItem() {
        final MoneyWiseSecurityPriceBaseList<? extends MoneyWiseSecurityPrice> myList = getList();
        final MoneyWiseSecurityPriceDataMap myMap = myList.getDataMap();
        myMap.adjustForItem(this);
    }

    /**
     * Price List.
     *
     * @param <T> the data type
     */
    public abstract static class MoneyWiseSecurityPriceBaseList<T extends MoneyWiseSecurityPrice>
            extends PrometheusEncryptedList<T> {
        /*
         * Report fields.
         */
        static {
            MetisFieldSet.newFieldSet(MoneyWiseSecurityPriceBaseList.class);
        }

        /**
         * Construct an empty CORE Price list.
         *
         * @param pData     the DataSet for the list
         * @param pClass    the class of the item
         * @param pItemType the item type
         */
        protected MoneyWiseSecurityPriceBaseList(final MoneyWiseDataSet pData,
                                                 final Class<T> pClass,
                                                 final MoneyWiseBasicDataType pItemType) {
            /* Call super-constructor */
            super(pClass, pData, pItemType, PrometheusListStyle.CORE);
        }

        /**
         * Constructor for a cloned List.
         *
         * @param pSource the source List
         */
        protected MoneyWiseSecurityPriceBaseList(final MoneyWiseSecurityPriceBaseList<T> pSource) {
            /* Call super-constructor */
            super(pSource);
        }

        @Override
        public MoneyWiseSecurityPriceDataMap getDataMap() {
            return (MoneyWiseSecurityPriceDataMap) super.getDataMap();
        }

        @Override
        protected MoneyWiseSecurityPriceDataMap allocateDataMap() {
            return new MoneyWiseSecurityPriceDataMap();
        }
    }

    /**
     * Price List.
     */
    public static class MoneyWiseSecurityPriceList
            extends MoneyWiseSecurityPriceBaseList<MoneyWiseSecurityPrice> {
        /**
         * Report fields.
         */
        private static final MetisFieldSet<MoneyWiseSecurityPriceList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseSecurityPriceList.class);

        /**
         * Construct an empty CORE price list.
         *
         * @param pData the DataSet for the list
         */
        protected MoneyWiseSecurityPriceList(final MoneyWiseDataSet pData) {
            super(pData, MoneyWiseSecurityPrice.class, MoneyWiseBasicDataType.SECURITYPRICE);
        }

        /**
         * Constructor for a cloned List.
         *
         * @param pSource the source List
         */
        private MoneyWiseSecurityPriceList(final MoneyWiseSecurityPriceList pSource) {
            super(pSource);
        }

        @Override
        public MetisFieldSet<MoneyWiseSecurityPriceList> getDataFieldSet() {
            return FIELD_DEFS;
        }

        @Override
        public String listName() {
            return LIST_NAME;
        }

        @Override
        public MetisFieldSetDef getItemFields() {
            return MoneyWiseSecurityPrice.FIELD_DEFS;
        }

        @Override
        public MoneyWiseDataSet getDataSet() {
            return (MoneyWiseDataSet) super.getDataSet();
        }

        @Override
        protected MoneyWiseSecurityPriceList getEmptyList(final PrometheusListStyle pStyle) {
            final MoneyWiseSecurityPriceList myList = new MoneyWiseSecurityPriceList(this);
            myList.setStyle(pStyle);
            return myList;
        }

        /**
         * Construct an edit extract of a Rate list.
         *
         * @param pEditSet the editSet
         * @return the edit list
         * @throws OceanusException on error
         */
        public MoneyWiseSecurityPriceList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
            /* Build an empty List */
            final MoneyWiseSecurityPriceList myList = getEmptyList(PrometheusListStyle.EDIT);
            myList.ensureMap();
            pEditSet.setEditEntryList(MoneyWiseBasicDataType.SECURITYPRICE, myList);

            /* Loop through the list */
            final Iterator<MoneyWiseSecurityPrice> myIterator = iterator();
            while (myIterator.hasNext()) {
                final MoneyWiseSecurityPrice myCurr = myIterator.next();

                /* Copy the item */
                final MoneyWiseSecurityPrice myItem = new MoneyWiseSecurityPrice(myList, myCurr);
                myItem.resolveEditSetLinks(pEditSet);
                myList.add(myItem);

                /* Adjust the map */
                myItem.adjustMapForItem();
            }

            /* Return the List */
            return myList;
        }

        /**
         * Add a new item to the core list.
         *
         * @param pPrice item
         * @return the newly added item
         */
        @Override
        public MoneyWiseSecurityPrice addCopyItem(final PrometheusDataItem pPrice) {
            if (pPrice instanceof MoneyWiseSecurityPrice p) {
                final MoneyWiseSecurityPrice myPrice = new MoneyWiseSecurityPrice(this, p);
                add(myPrice);
                return myPrice;
            } else {
                throw new UnsupportedOperationException();
            }
        }

        /**
         * Add a new item to the edit list.
         *
         * @return the newly added item
         */
        @Override
        public MoneyWiseSecurityPrice addNewItem() {
            final MoneyWiseSecurityPrice myPrice = new MoneyWiseSecurityPrice(this);
            add(myPrice);
            return myPrice;
        }

        @Override
        public MoneyWiseSecurityPrice addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
            /* Create the price */
            final MoneyWiseSecurityPrice myPrice = new MoneyWiseSecurityPrice(this, pValues);

            /* Check that this PriceId has not been previously added */
            if (!isIdUnique(myPrice.getIndexedId())) {
                myPrice.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
                throw new MoneyWiseDataException(myPrice, ERROR_VALIDATION);
            }

            /* Add to the list */
            add(myPrice);

            /* Return it */
            return myPrice;
        }
    }

    /**
     * The dataMap class.
     */
    public static class MoneyWiseSecurityPriceDataMap
            implements PrometheusDataMapItem, MetisFieldItem {
        /**
         * Report fields.
         */
        @SuppressWarnings("rawtypes")
        private static final MetisFieldSet<MoneyWiseSecurityPriceDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseSecurityPriceDataMap.class);

        /*
         * UnderlyingMap Field Id.
         */
        static {
            FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_MAPOFMAPS, MoneyWiseSecurityPriceDataMap::getMapOfMaps);
            FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.SECURITYPRICE_MAP_MAPOFPRICES, MoneyWiseSecurityPriceDataMap::getMapOfPrices);
        }

        /**
         * Map of Maps.
         */
        private final Map<MoneyWiseSecurity, Map<OceanusDate, Integer>> theMapOfMaps;

        /**
         * Map of Prices.
         */
        private final Map<MoneyWiseSecurity, MoneyWiseSecurityPriceList> theMapOfPrices;

        /**
         * Constructor.
         */
        public MoneyWiseSecurityPriceDataMap() {
            /* Create the maps */
            theMapOfMaps = new HashMap<>();
            theMapOfPrices = new HashMap<>();
        }

        @SuppressWarnings("rawtypes")
        @Override
        public MetisFieldSet<MoneyWiseSecurityPriceDataMap> getDataFieldSet() {
            return FIELD_DEFS;
        }

        @Override
        public String formatObject(final OceanusDataFormatter pFormatter) {
            return FIELD_DEFS.getName();
        }

        /**
         * Obtain mapOfMaps.
         *
         * @return the map
         */
        private Map<MoneyWiseSecurity, Map<OceanusDate, Integer>> getMapOfMaps() {
            return theMapOfMaps;
        }

        /**
         * Obtain mapOfPrices.
         *
         * @return the map
         */
        private Map<MoneyWiseSecurity, MoneyWiseSecurityPriceList> getMapOfPrices() {
            return theMapOfPrices;
        }

        @Override
        public void resetMap() {
            theMapOfMaps.clear();
            theMapOfPrices.clear();
        }

        @Override
        public void adjustForItem(final PrometheusDataItem pItem) {
            /* Access the Security Id */
            final MoneyWiseSecurityPrice myItem = (MoneyWiseSecurityPrice) pItem;
            final MoneyWiseSecurity mySecurity = myItem.getSecurity();
            if (mySecurity == null) {
                return;
            }

            /* Access the map */
            final Map<OceanusDate, Integer> myMap = theMapOfMaps.computeIfAbsent(mySecurity, s -> new HashMap<>());

            /* Adjust price count */
            final OceanusDate myDate = myItem.getDate();
            final Integer myCount = myMap.get(myDate);
            if (myCount == null) {
                myMap.put(myDate, PrometheusDataInstanceMap.ONE);
            } else {
                myMap.put(myDate, myCount + 1);
            }

            /* Access the list */
            final MoneyWiseSecurityPriceList myList = theMapOfPrices.computeIfAbsent(mySecurity, MoneyWiseSecurityPriceList::new);

            /* Add element to the list */
            myList.add(myItem);
        }

        /**
         * Check validity of Price.
         *
         * @param pItem the price
         * @return true/false
         */
        public boolean validPriceCount(final MoneyWiseSecurityPrice pItem) {
            /* Access the Details */
            final MoneyWiseSecurity mySecurity = pItem.getSecurity();
            final OceanusDate myDate = pItem.getDate();

            /* Access the map */
            final Map<OceanusDate, Integer> myMap = theMapOfMaps.get(mySecurity);
            if (myMap != null) {
                final Integer myResult = myMap.get(myDate);
                return PrometheusDataInstanceMap.ONE.equals(myResult);
            }
            return false;
        }

        /**
         * Check availability of date for a security.
         *
         * @param pSecurity the security
         * @param pDate     the key to look up
         * @return true/false
         */
        public boolean availableDate(final MoneyWiseSecurity pSecurity,
                                     final OceanusDate pDate) {
            /* Access the map */
            final Map<OceanusDate, Integer> myMap = theMapOfMaps.get(pSecurity);
            return myMap == null
                    || myMap.get(pDate) == null;
        }

        /**
         * Obtain price for date.
         *
         * @param pSecurity the security
         * @param pDate     the date
         * @return the latest price for the date.
         */
        public OceanusPrice getPriceForDate(final MoneyWiseAssetBase pSecurity,
                                            final OceanusDate pDate) {
            /* Access as security */
            final MoneyWiseSecurity mySecurity = MoneyWiseSecurity.class.cast(pSecurity);

            /* Access list for security */
            final MoneyWiseSecurityPriceList myList = theMapOfPrices.get(mySecurity);
            if (myList != null) {
                /* Loop through the prices */
                final Iterator<MoneyWiseSecurityPrice> myIterator = myList.iterator();
                while (myIterator.hasNext()) {
                    final MoneyWiseSecurityPrice myCurr = myIterator.next();

                    /* Return this price if this is earlier or equal to the the date */
                    if (pDate.compareTo(myCurr.getDate()) >= 0) {
                        return myCurr.getPrice();
                    }
                }
            }

            /* return single unit price */
            final Currency myCurrency = mySecurity.getCurrency();
            return OceanusPrice.getWholeUnits(PrometheusDataInstanceMap.ONE, myCurrency);
        }

        /**
         * Obtain prices for range.
         *
         * @param pSecurity the security
         * @param pRange    the date range
         * @return the two deep array of prices for the range.
         */
        public OceanusPrice[] getPricesForRange(final MoneyWiseSecurity pSecurity,
                                                final OceanusDateRange pRange) {
            /* Set price */
            final Currency myCurrency = pSecurity.getCurrency();
            OceanusPrice myFirst = OceanusPrice.getWholeUnits(PrometheusDataInstanceMap.ONE, myCurrency);
            OceanusPrice myLatest = myFirst;
            final OceanusDate myStart = pRange.getStart();

            /* Access list for security */
            final MoneyWiseSecurityPriceList myList = theMapOfPrices.get(pSecurity);
            if (myList != null) {
                /* Loop through the prices */
                final ListIterator<MoneyWiseSecurityPrice> myIterator = myList.listIterator(myList.size());
                while (myIterator.hasPrevious()) {
                    final MoneyWiseSecurityPrice myCurr = myIterator.previous();

                    /* Check for the range of the date */
                    final OceanusDate myDate = myCurr.getDate();
                    final int iComp = pRange.compareToDate(myDate);

                    /* If this is later than the range we are finished */
                    if (iComp < 0) {
                        break;
                    }

                    /* Record as best price */
                    myLatest = myCurr.getPrice();

                    /* Record early price */
                    if (iComp > 0
                            || myDate.compareTo(myStart) == 0) {
                        myFirst = myLatest;
                    }
                }
            }

            /* Return the prices */
            return new OceanusPrice[]
                    {myFirst, myLatest};
        }

        /**
         * Obtain priceList cursor.
         *
         * @param pSecurity the security
         * @return the latest price for the date.
         */
        public ListIterator<MoneyWiseSecurityPrice> priceIterator(final MoneyWiseSecurity pSecurity) {
            /* Access list for currency */
            final MoneyWiseSecurityPriceList myList = theMapOfPrices.get(pSecurity);
            return myList != null
                    ? myList.listIterator(myList.size())
                    : null;
        }

        /**
         * Price List class.
         */
        private static final class MoneyWiseSecurityPriceList
                implements MetisFieldItem, MetisDataList<MoneyWiseSecurityPrice> {
            /**
             * Report fields.
             */
            private static final MetisFieldSet<MoneyWiseSecurityPriceList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseSecurityPriceList.class);

            /*
             * UnderlyingMap Field Id.
             */
            static {
                FIELD_DEFS.declareLocalField(MetisDataResource.LIST_SIZE, MoneyWiseSecurityPriceList::size);
            }

            /**
             * The list.
             */
            private final List<MoneyWiseSecurityPrice> theList;

            /**
             * The security.
             */
            private final MoneyWiseSecurity theSecurity;

            /**
             * Constructor.
             *
             * @param pSecurity the security
             */
            private MoneyWiseSecurityPriceList(final MoneyWiseSecurity pSecurity) {
                theSecurity = pSecurity;
                theList = new ArrayList<>();
            }

            @Override
            public MetisFieldSet<MoneyWiseSecurityPriceList> getDataFieldSet() {
                return FIELD_DEFS;
            }

            @Override
            public String formatObject(final OceanusDataFormatter pFormatter) {
                return theSecurity.formatObject(pFormatter)
                        + "("
                        + size()
                        + ")";
            }

            @Override
            public String toString() {
                return theSecurity.toString()
                        + "("
                        + size()
                        + ")";
            }

            @Override
            public List<MoneyWiseSecurityPrice> getUnderlyingList() {
                return theList;
            }
        }
    }
}