/*
 * Copyright 2009, 2010 Lari Natri <lari.natri@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package compid.data.db_derby;

import compid.data.*;
import compid.data.tuples.*;
import compid.data.tuples.DatasetInfo.AlgType;
import compid.data.PeptideSequenceWithHits;
import compid.data.tuples.ProteinHit.GroupMemberStatus;
import compid.report.ReportPageModel;
import compid.report.protgroup.ProtGroup;
import compid.report.protgroup.ProtGroupCollection;
import compid.report.protgroup.ProtGroupMember;
import compid.report.protgroup.ProtGroupParams.ParagonScoreType;
import compid.report.protgroup.ProtGroupQuery;
import compid.sequences.AminoSequence;
import net.aavalla.utils.jobpool.parameters.SyncEnumSet;
import net.aavalla.utils.general.RetrievalHashSet;
import net.aavalla.utils.general.StrUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

/**
 *
 * @author Lari Natri
 */
public class QueryHandlerDerby extends BaseDAODerby implements QueryHandler {
    

    private final DAOFactory daoFactory;

    public QueryHandlerDerby(DAOFactory daoFactory) {
        this.daoFactory = daoFactory;
    }


    @Override
    public Vector<DatasetInfo> getDataSetInfos() throws DAOException {
        Vector<DatasetInfo> datasets = new Vector<DatasetInfo>(10);
        Connection conn;
        ResultSet rs;
        PreparedStatement ps;
        try {
            conn = getNewConnection();
            ps = prepareStatement(conn,SQL.GETALLDATASETS);
            rs = ps.executeQuery();
            while (rs.next()) {
                int id = rs.getInt("DATASETINFO_ID");
                String name = rs.getString("DSNAME");
                String descr = rs.getString("DSDESCR");
                String pepdb = rs.getString("PEPDB");
                String source = rs.getString("SOURCESTR");
                String alg = rs.getString("ALGORITHM");
                String params = rs.getString("PARAMS");
                Date date = rs.getDate("ADDTIME");
                String dataCounts = rs.getString("DATACOUNTS");
                AlgType algorithm = AlgType.getAlgTypeFromString(alg);
                DatasetInfo dsi = new DatasetInfo(name, descr, pepdb, source, algorithm, params, date);
                dsi.setPrimaryKey(id);
                dsi.setDataCountsStr(dataCounts);
                datasets.addElement(dsi);
            }
            closeAll(conn,ps,rs);
            return datasets;
        } catch (SQLException ex) {
            throw new DAOException(ex);
        }
    }

    @Override
    public int getDistinctPeptidesInDataSetsCount(Collection<DatasetInfo> dataSetInfos)
            throws DAOException
    {
        return getCountOfUnionByPK(SQL.GETDISTINCTPEPSINDATASET,dataSetInfos);
    }

    @Override
    public int getDistinctPepsUsedInProtsByDataSetsCount(Collection<DatasetInfo> dataSetInfos)
            throws DAOException
    {
        return getCountOfUnionByPK(SQL.GETDISTINCTPROTIDPEPSINDATASET,dataSetInfos);
    }

    @Override
    public RetrievalHashSet<PeptideSequenceWithHits> getDistinctPepsInDataSets(Collection<DatasetInfo> dataSetInfos, boolean onlyPepsUsedInProteins)
            throws DAOException
    {
        RetrievalHashSet<Peptide> peps = new RetrievalHashSet<Peptide>(256);
        RetrievalHashSet<Query> queries = new RetrievalHashSet<Query>(256);
        RetrievalHashSet<PeptideHit> hits = new RetrievalHashSet<PeptideHit>(256);
        RetrievalHashSet<PeptideSequenceWithHits> seqs = new RetrievalHashSet<PeptideSequenceWithHits>(256);
        ResultSet rs = null;

        Connection conn = getNewConnection();

        Iterator<DatasetInfo> dsIter = dataSetInfos.iterator();
        while (dsIter.hasNext()) {
            ArrayList<DatasetInfo> tmpList = new ArrayList<DatasetInfo>(1);
            DatasetInfo thisDS = dsIter.next();
            tmpList.add(thisDS);
            if (onlyPepsUsedInProteins) {
                rs = getUnionByPK(conn,SQL.GETPEPSINPROTSINDATASET,tmpList,1);
            } else {
                rs = getUnionByPK(conn,SQL.GETPEPSINDATASET,tmpList,1);
            }
            try {
                int r = 0;
                while (rs.next()) {
                    ++r;
                    if (r % 10000==0) System.out.println("DEBUG: "+thisDS.getName()+" row "+r);

                    Query q = new Query(thisDS, rs.getString("SCANTITLE"),
                                        rs.getDouble("SUBMITTEDMASS"),
                                        rs.getDouble("CHARGE"));
                    q.setPrimaryKey(rs.getInt("QUERY_ID"));
                    if (!queries.add(q)) q=queries.getKey(q);

                    Peptide pep = new Peptide(rs.getString("PEPSEQUENCE"));
                    pep.setPrimaryKey(rs.getInt("PEPTIDE_ID"));
                    if (!peps.add(pep)) pep=peps.getKey(pep);

                    PeptideHit pepHit = new PeptideHit(pep,q,
                                                       Math.max(rs.getDouble("PG_CONF"),
                                                                rs.getDouble("MS_IONSSCORE")));
                    pepHit.setPrimaryKey(rs.getInt("PEPTIDEHIT_ID"));
                    if (!hits.add(pepHit)) pepHit = hits.getKey(pepHit);

                    PeptideSequenceWithHits pswh = new PeptideSequenceWithHits(pep.getSequence(),null);
                    if (!seqs.add(pswh)) {
                        pswh = seqs.getKey(pswh);
                    }
                    pswh.addHit(pepHit);
                }
            } catch (SQLException ex) {
                throw new DAOException(ex);
            } catch (Exception ex) { // never happens w/peptides
            } finally {
                close(rs);
            }
        }
        close(conn);
        if (onlyPepsUsedInProteins) {
            System.out.print("DEBUG: peps-in-prot: ");
        } else System.out.print("DEBUG: peps-all: ");
        System.out.println(seqs.size());
        return seqs;
    }

    @Override
    public ArrayList<Integer> getQueryHitCountsPerProtein(Collection<DatasetInfo> dataSetInfos)
        throws DAOException
    {
        Connection conn = getNewConnection();
        ArrayList<Integer> counts = new ArrayList<Integer>();

        ResultSet rs = getUnionByPK(conn, SQL.GETQUERYCOUNTSPERPROTINDATASET,dataSetInfos,dataSetInfos.size());
        try {
            while (rs.next()) {
                counts.add(rs.getInt("QUERIES"));
            }
        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            close(rs);
        }
        close(conn);
        return counts;
    }

    @Override
    public ArrayList<Integer> getDistinctPeptideCountsPerProtein(Collection<DatasetInfo> dataSetInfos)
        throws DAOException
    {
        Connection conn = getNewConnection();
        ArrayList<Integer> counts = new ArrayList<Integer>();

        ResultSet rs = getUnionByPK(conn, SQL.GETDISTINCTPEPCOUNTSPERPROTINDATASET,dataSetInfos,dataSetInfos.size());
        try {
            while (rs.next()) {
                counts.add(rs.getInt("PEPTIDES"));
            }
        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            close(rs);
        }
        close(conn);
        return counts;
    }

    @Override
    public int getDistinctProteinCount(Collection<DatasetInfo> dataSetInfos)
            throws DAOException
    {
        return getCountOfUnionByPK(SQL.GETPROTEINSINDATASET,dataSetInfos);
    }

    @Override
    public int getSequencelessProteinCount(Collection<DatasetInfo> datasetInfos)
    {
        return getCountOfUnionByPK(SQL.GETPROTSWITHNOSEQBYDATASETID,datasetInfos);
    }

    @Override
    public RetrievalHashSet<Protein> getProteinsHavingSameSequence(Protein prot)
            throws DAOException
    {
        RetrievalHashSet<Protein> prots = new RetrievalHashSet<Protein>();
        Connection conn = getNewConnection();
        int protPK = prot.getPrimaryKey();
        if (protPK<1) { // PK not set: try to get it from database
            protPK = findPrimaryKey(conn,prot);
            prot.setPrimaryKey(protPK);
        }
        if (protPK<1) { // protein not in database!
            throw new DAOException(DAOException.ExceptionType.TUPLENOTFOUND,
                                   "Protein by sequence: "+prot.getAccession());
        }
        PreparedStatement stmt = prepareStatement(conn,
                                                  SQL.GETPROTSWITHSAMESEQUENCE);
        ResultSet rs = null;
        try {
            stmt.setInt(1, protPK);
            rs = stmt.executeQuery();
            while (rs.next()) {
                int id = rs.getInt("PROTEIN_ID");
                //int seqId = rsProts.getInt("PROTSEQ_ID");
                //int dbId = rsProts.getInt("PROTDBINFO_ID");
                String acc = rs.getString("ACCESSION");
                String desc = rs.getString("DESCRIPTION");
                String species = rs.getString("SPECIES");
                Protein p = new Protein(acc,desc,species);
                prot.setPrimaryKey(id);
                prots.add(p);
            }
        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            closeAll(conn,stmt,rs);
        }

        return prots;
    }

    @Override
    public RetrievalHashSet<Protein> getAllProteinsWithoutSequence()
            throws DAOException
    {
        return this.getAllProteinsBySequenceId(1);
    }

    @Override
    public RetrievalHashSet<Protein> getAllProteinsBySequenceId(int sequenceId)
            throws DAOException
    { // TODO: make possible to get prots in batches
        RetrievalHashSet<Protein> prots = new RetrievalHashSet<Protein>();
        Connection conn = getNewConnection();
        ResultSet rs = null;
        PreparedStatement stmt
                = prepareStatement(conn, SQL.GETPROTSBYSEQID);
        try {
            stmt.setInt(1, sequenceId);
            rs = stmt.executeQuery();
            while (rs.next()) {
                int id = rs.getInt("PROTEIN_ID");
                //int seqId = rsProts.getInt("PROTSEQ_ID");
                //int dbId = rsProts.getInt("PROTDBINFO_ID");
                String acc = rs.getString("ACCESSION");
                String desc = rs.getString("DESCRIPTION");
                String species = rs.getString("SPECIES");
                Protein prot = new Protein(acc,desc,species);
                prot.setPrimaryKey(id);
                prots.add(prot);
            }
        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            closeAll(conn,stmt,rs);
        }
        return prots;
    }


    @Override
    public Set<Integer> getProteinHitIdsInDatasets(Collection<DatasetInfo> dataSetInfos, boolean winnersOnly)
            throws DAOException
    {
        String sql = winnersOnly ? SQL.GETWINNERPROTHITIDSINDATASET : SQL.GETALLPROTHITIDSINDATASET;

        Connection conn = getNewConnection();
        Set<Integer> ids = new TreeSet<Integer>();

        Iterator<DatasetInfo> dsIter = dataSetInfos.iterator();
        while (dsIter.hasNext()) {

            ArrayList<DatasetInfo> tmpList = new ArrayList<DatasetInfo>(1);
            tmpList.add(dsIter.next());
            ResultSet rs = getIntersectByPK(conn, sql, tmpList,1);
            try {
                while (rs.next()) {
                    ids.add(rs.getInt(1));
                }
            } catch (SQLException ex) {
                throw new DAOException(ex);
            } finally {
                close(rs);
            }
        }
        close(conn);
        return ids;

    }

    @Override
    public int getQueriesWhichFoundPeptidesInDatasetsCount(Collection<DatasetInfo> dataSetInfos)
            throws DAOException
    {
        return getCountOfUnionByPK(SQL.GETDISTQUERIESWHICHFOUNDPEPSINDATASET,dataSetInfos);
    }

    @Override
    public int getQueriesWhichFoundPeptidesInProteinsInDatasetsCount(Collection<DatasetInfo> dataSetInfos)
            throws DAOException
    {
        return getCountOfUnionByPK(SQL.GETDISTQUERIESWHICHFOUNDPEPSINPROTSINDATASET,dataSetInfos);
    }

    @Override
    public ProtGroupCollection getProtGroupsInDatasets(
            Collection<DatasetInfo> datasetInfos,
            SyncEnumSet<GroupMemberStatus> inclGroupMembers,
            boolean inclSequences, boolean inclPeptides,
            boolean splitExistingGroups,
            ParagonScoreType paragonScoreType)
            throws DAOException
    {
        ProtGroupCollection result = new ProtGroupCollection();

        StringBuilder sqlProts = new StringBuilder();
        sqlProts.append("SELECT DISTINCT PROTEINHIT.PROTEINHIT_ID, PROTEINHIT.PROTEIN_ID, " +
                        "                PROTEINHIT.DATASETINFO_ID, PROTEINHIT.GROUPID, " +
                        "                PROTEINHIT.GROUPMEMBERSTATUS, PROTEINHIT.MS_PROTSCORE, " +
                        "                PROTEINHIT.PG_UNUSED, PROTEINHIT.PG_TOTAL, PROTEIN.ACCESSION, PROTEIN.DESCRIPTION"); //NOI18N
        if (inclSequences) sqlProts.append(", PROTEIN.PROTSEQ_ID, PROTSEQ.SEQUENCE "); //NOI18N
        sqlProts.append(" FROM PROTEINHIT, PROTEIN"); //NOI18N
        if (inclSequences) sqlProts.append(", PROTSEQ "); //NOI18N
        sqlProts.append(" WHERE PROTEINHIT.PROTEIN_ID = PROTEIN.PROTEIN_ID "); //NOI18N
        if (inclSequences) sqlProts.append("   AND PROTEIN.PROTSEQ_ID = PROTSEQ.PROTSEQ_ID "); //NOI18N
        sqlProts.append("   AND ("); //NOI18N
        sqlProts.append(StrUtils.catStringTimes("PROTEINHIT.DATASETINFO_ID=?", " OR ", datasetInfos.size())); //NOI18N
        sqlProts.append(") AND ("); //NOI18N
        sqlProts.append(StrUtils.catStringTimes("GROUPMEMBERSTATUS=?", " OR ", inclGroupMembers.size())); //NOI18N
        sqlProts.append(") "); //NOI18N
        // IMPORTANT! Correct matching depends on order being "DATASETINFO_ID, GROUP_ID"
        sqlProts.append(" ORDER BY DATASETINFO_ID, GROUPID"); //NOI18N
        
        StringBuilder sqlPeps = new StringBuilder();
        sqlPeps.append("SELECT PROTPEPTHITSJOIN.PROTEINHIT_ID, QUERY.DATASETINFO_ID," +
                       "       QUERY.SCANTITLE, PEPTIDEHIT.PG_CONF," +
                       "       PEPTIDEHIT.MS_IONSSCORE, PEPTIDE.PEPSEQUENCE"); //NOI18N
        sqlPeps.append(" FROM QUERY, PEPTIDEHIT, PROTPEPTHITSJOIN, PEPTIDE"); //NOI18N
        sqlPeps.append(" WHERE ("); //NOI18N
        sqlPeps.append(StrUtils.catStringTimes("QUERY.DATASETINFO_ID = ?", " OR ", datasetInfos.size())); //NOI18N
        sqlPeps.append(")");
        sqlPeps.append(" AND  PEPTIDEHIT.QUERY_ID = QUERY.QUERY_ID"); //NOI18N
        sqlPeps.append(" AND PEPTIDEHIT.PEPTIDEHIT_ID = PROTPEPTHITSJOIN.PEPTIDEHIT_ID"); //NOI18N
        sqlPeps.append(" AND PEPTIDEHIT.PEPTIDE_ID = PEPTIDE.PEPTIDE_ID"); //NOI18N
        // IMPORTANT! Correct matching depends on this ordering starting with PROTEINHIT_ID
        sqlPeps.append(" ORDER BY PROTEINHIT_ID"); //NOI18N

        Connection conn = getNewConnection();
        PreparedStatement psGetProts = null;
        PreparedStatement psGetPeps = null;
        ResultSet rsProts = null;

        final String paragonScoreColName;
        switch (paragonScoreType) {
            case Total:
                paragonScoreColName = "PG_TOTAL"; //NOI18N
                break;
            case Unused:
            default:
                paragonScoreColName = "PG_UNUSED"; //NOI18N
        }


        int debug_protcount = 0;
        int debug_pepcount = 0;

        try {

            HashMap<Integer,DatasetInfo> idToDataset = new HashMap<Integer,DatasetInfo>(datasetInfos.size(),1);

            psGetProts = prepareStatement(conn,sqlProts.toString());

            if (inclPeptides) {
                psGetPeps  = prepareStatement(conn,sqlPeps.toString());
            }

            int i = 0;
            for (DatasetInfo dsi : datasetInfos) {
                idToDataset.put(dsi.getPrimaryKey(), dsi);
                psGetProts.setInt(++i, dsi.getPrimaryKey());
                if(inclPeptides) {
                    psGetPeps.setInt(i, dsi.getPrimaryKey());
                }
            }
            for (GroupMemberStatus gms : inclGroupMembers) {
                psGetProts.setInt(++i, gms.getCode());
            }

            // Sequences are held in a key-retrievable set to ensure that
            // memory is only reserved once for exactly same sequences
            RetrievalHashSet<AminoSequence> sequencesTemp = new RetrievalHashSet<AminoSequence>(2048);

            // Will contain the peptides for each protein hit
            HashMap<Integer,LinkedHashSet<ProtGroupQuery>> prothitidToPeps = new HashMap<Integer,LinkedHashSet<ProtGroupQuery>>(512);

            if (inclPeptides) { // Get peptides if so requested
                AminoSequence currPepSeq = null;
                AlgType currPepAlg = null;
                double currPepScore = -1d;
                LinkedHashSet<ProtGroupQuery> currPeps = null;
                int prothitId = -7;
                int lastProthitId = -7; // seven is a likable number
                ResultSet rsPeps = psGetPeps.executeQuery();
                while (rsPeps.next()) {
                    ++debug_pepcount;
                    prothitId = rsPeps.getInt("PROTEINHIT_ID"); //NOI18N
                    if (prothitId!=lastProthitId) {

                        if (currPeps!=null) { // skip first iteration
                            prothitidToPeps.put(lastProthitId, currPeps);
                        }
                        currPeps = new LinkedHashSet<ProtGroupQuery>(3);
                        lastProthitId = prothitId;
                    }

                    currPepSeq = new AminoSequence(rsPeps.getString("PEPSEQUENCE")); //NOI18N
                    if (!sequencesTemp.add(currPepSeq)) { // use the old one if possible
                        currPepSeq = sequencesTemp.getKey(currPepSeq);
                    }
                    currPepAlg = idToDataset.get(rsPeps.getInt("DATASETINFO_ID")).getAlgorithm();

                    switch(currPepAlg) { //NOI18N
                        case PARAGON :
                            currPepScore = rsPeps.getDouble("PG_CONF"); //NOI18N
                            break;
                        case MASCOT :
                            currPepScore = rsPeps.getDouble("MS_IONSSCORE"); //NOI18N
                            break;
                        default :
                            throw new RuntimeException("Programming error:" +
                                    "New dataset types added, but type not " +
                                    "added everywhere! Aborting.");
                    }

                    currPeps.add(new ProtGroupQuery(currPepSeq,currPepScore,currPepAlg,rsPeps.getString("SCANTITLE"))); //NOI18N
                }
                if (currPeps!=null) {
                    prothitidToPeps.put(lastProthitId,currPeps); // add last
                }
                close(rsPeps);
                close(psGetPeps);
                sequencesTemp.clear(); // make empty for proteins
            } // Got peptides
            
            double currProtScore = -1d;
            DatasetInfo currDataset = null;
            ProtGroupMember currProt = null;
            ProtGroup currGroup = null;
            AminoSequence currProtSeq = null;
            String currAcc = null;
            String currName = null;
            
            int currGroupId = -1;
            int currDatasetId = -1;
            int lastGroupId = -1;
            int lastDatasetId = -1;

            // Strings are held in a key-retrievable set to ensure that
            // memory is only reserved once for exactly same strings
            RetrievalHashSet<String> stringsTemp = new RetrievalHashSet<String>(512);

            // Get Proteins

            rsProts = psGetProts.executeQuery();
            while(rsProts.next()) {
                ++debug_protcount;
                currDataset = idToDataset.get(rsProts.getInt("DATASETINFO_ID")); //NOI18N
                currDatasetId = currDataset.getPrimaryKey();
                currGroupId = rsProts.getInt("GROUPID"); //NOI18N
                if (splitExistingGroups || currDatasetId!=lastDatasetId || currGroupId!=lastGroupId) {
                    if (currGroup!=null) {
                        result.add(currGroup);
                    }
                    currGroup = new ProtGroup();
                    lastGroupId = currGroupId;
                    lastDatasetId = currDatasetId;
                }
                currDataset = idToDataset.get(currDatasetId);

                switch(currDataset.getAlgorithm()) {
                    case PARAGON :
                        currProtScore = rsProts.getDouble(paragonScoreColName); //NOI18N
                        break;
                    case MASCOT :
                        currProtScore = rsProts.getDouble("MS_PROTSCORE"); //NOI18N
                        break;
                    default :
                        throw new RuntimeException("Programming error:" +
                                "New dataset types added, but type not " +
                                "added everywhere! Aborting.");
                }

                currProtSeq = null;
                if (inclSequences) {
                    currProtSeq = new AminoSequence(rsProts.getString("SEQUENCE")); //NOI18N
                    if (!sequencesTemp.add(currProtSeq)) {
                        currProtSeq = sequencesTemp.getKey(currProtSeq);
                    }
                }
                currAcc = rsProts.getString("ACCESSION");
                if (!stringsTemp.add(currAcc)) {
                    currAcc = stringsTemp.getKey(currAcc);
                }
                currName = rsProts.getString("DESCRIPTION");
                if (!stringsTemp.add(currName)) {
                    currName = stringsTemp.getKey(currName);
                }
                currProt = new ProtGroupMember(currAcc, //NOI18N
                        currProtScore,
                        GroupMemberStatus.getInstanceByCode(rsProts.getInt("GROUPMEMBERSTATUS")), //NOI18N
                        currDataset,currProtSeq,currName,
                        prothitidToPeps.get(rsProts.getInt("PROTEINHIT_ID"))); //NOI18N
                currGroup.addMember(currProt);
            }

            if (currGroup!=null) { // add the last group
                result.add(currGroup);
            }

            sequencesTemp.clear(); // ensure(?) freeing of memory
            sequencesTemp = null;
            stringsTemp.clear();
            stringsTemp = null;
            prothitidToPeps.clear();
            prothitidToPeps = null;

        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            closeAll(conn,psGetProts,rsProts);
        }

        System.out.println("DEBUG: "+debug_protcount+" prots, "+debug_pepcount+" peps");

        return result;

    }

    @Override
    public void executeManualQuery(String sqlQuery, ReportPageModel model)
            throws DAOException
    {
        Connection conn = getNewConnection();
        PreparedStatement stmt = prepareStatement(conn, sqlQuery);
        ResultSet rs = null;

        try {
            rs = stmt.executeQuery();
            int colCount = rs.getMetaData().getColumnCount();
            for (int c = 1; c <= colCount; ++c) {
                model.addColumn(rs.getMetaData().getColumnName(c));
            }
            while (rs.next()) {
                ArrayList<Object> row = new ArrayList<Object>(colCount);
                for (int c = 1; c <= colCount; ++c) {
                    row.add(rs.getObject(c));
                }
                model.addRow(row);
            }
        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            close(rs);
            close(stmt);
            close(conn);
        }

    }



}
