/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.client.model;

import com.mongodb.lang.Nullable;
import org.bson.conversions.Bson;

/**
 * The options to apply to a $push update operator.
 *
 * @mongodb.driver.manual reference/operator/update/push/ $push
 * @see Updates#pushEach(String, java.util.List, PushOptions)
 * @since 3.1
 */
public class PushOptions {
    private Integer position;
    private Integer slice;
    private Integer sort;
    private Bson sortDocument;

    /**
     * Gets the position at which to add the pushed values in the array.
     *
     * @return the position, which may be null
     * @mongodb.driver.manual reference/operator/update/position/ $position
     */
    @Nullable
    public Integer getPosition() {
        return position;
    }

    /**
     * Sets the position at which to add the pushed values in the array.
     *
     * @param position the position
     * @return this
     * @mongodb.driver.manual reference/operator/update/position/ $position
     */
    public PushOptions position(@Nullable final Integer position) {
        this.position = position;
        return this;
    }

    /**
     * Gets the slice value, which is the limit on the number of array elements allowed.
     *
     * @return the slice value representing the limit on the number of array elements allowed
     * @mongodb.driver.manual reference/operator/update/slice/ $slice
     */
    @Nullable
    public Integer getSlice() {
        return slice;
    }

    /**
     * Sets the limit on the number of array elements allowed.
     *
     * @param slice the limit
     * @return this
     * @mongodb.driver.manual reference/operator/update/slice/ $slice
     */
    public PushOptions slice(@Nullable final Integer slice) {
        this.slice = slice;
        return this;
    }

    /**
     * Gets the sort direction for sorting array elements that are not documents.
     *
     * @return the sort direction
     * @mongodb.driver.manual reference/operator/update/sort/ $sort
     * @mongodb.driver.manual reference/operator/update/sort/#sort-array-elements-that-are-not-documents
     */
    @Nullable
    public Integer getSort() {
        return sort;
    }

    /**
     * Sets the sort direction for sorting array elements that are not documents.
     *
     * @param sort the sort direction
     * @return this
     * @throws IllegalStateException if sortDocument property is already set
     * @mongodb.driver.manual reference/operator/update/sort/ $sort
     * @mongodb.driver.manual reference/operator/update/sort/#sort-array-elements-that-are-not-documents
     */
    public PushOptions sort(@Nullable final Integer sort) {
        if (sortDocument != null) {
            throw new IllegalStateException("sort can not be set if sortDocument already is");
        }
        this.sort = sort;
        return this;
    }

    /**
     * Gets the sort direction for sorting array elements that are documents.
     *
     * @return the sort document
     * @mongodb.driver.manual reference/operator/update/sort/ $sort
     */
    @Nullable
    public Bson getSortDocument() {
        return sortDocument;
    }

    /**
     * Sets the sort direction for sorting array elements that are documents.
     *
     * @param sortDocument the sort document
     * @return this
     * @throws IllegalStateException if sort property is already set
     * @mongodb.driver.manual reference/operator/update/sort/ $sort
     */
    public PushOptions sortDocument(@Nullable final Bson sortDocument) {
        if (sort != null) {
            throw new IllegalStateException("sortDocument can not be set if sort already is");
        }
        this.sortDocument = sortDocument;
        return this;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        PushOptions that = (PushOptions) o;

        if (position != null ? !position.equals(that.position) : that.position != null) {
            return false;
        }
        if (slice != null ? !slice.equals(that.slice) : that.slice != null) {
            return false;
        }
        if (sort != null ? !sort.equals(that.sort) : that.sort != null) {
            return false;
        }
        return sortDocument != null ? sortDocument.equals(that.sortDocument) : that.sortDocument == null;
    }

    @Override
    public int hashCode() {
        int result = position != null ? position.hashCode() : 0;
        result = 31 * result + (slice != null ? slice.hashCode() : 0);
        result = 31 * result + (sort != null ? sort.hashCode() : 0);
        result = 31 * result + (sortDocument != null ? sortDocument.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Push Options{"
                       + "position=" + position
                       + ", slice=" + slice
                       + ((sort == null) ? "" : ", sort=" + sort)
                       + ((sortDocument == null) ? "" :  ", sortDocument=" + sortDocument)
                       + '}';
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.client.model.changestream;

import com.mongodb.MongoNamespace;
import com.mongodb.assertions.Assertions;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonTimestamp;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.annotations.BsonCreator;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.bson.codecs.pojo.annotations.BsonProperty;

/**
 * Represents the {@code $changeStream} aggregation output document.
 *
 *
Note: this class will not be applicable for all change stream outputs. If using custom pipelines that radically change the
 * change stream result, then an alternative document format should be used.

 *
 * @param <TDocument> The type that this collection will encode the {@code fullDocument} field into.
 * @since 3.6
 */
public final class ChangeStreamDocument<TDocument> {

    @BsonId()
    private final BsonDocument resumeToken;
    private final BsonDocument namespaceDocument;
    private final TDocument fullDocument;
    private final BsonDocument documentKey;
    private final BsonTimestamp clusterTime;
    private final OperationType operationType;
    private final UpdateDescription updateDescription;

    /**
     * Creates a new instance
     *
     * @param resumeToken the resume token
     * @param namespace the namespace
     * @param documentKey a document containing the _id of the changed document
     * @param fullDocument the fullDocument
     * @param operationType the operation type
     * @param updateDescription the update description
     * @deprecated Prefer {@link #ChangeStreamDocument(BsonDocument, MongoNamespace, Object, BsonDocument, BsonTimestamp, OperationType,
     *                                                 UpdateDescription)}
     */
    @Deprecated
    public ChangeStreamDocument(final BsonDocument resumeToken,
                                final MongoNamespace namespace,
                                final TDocument fullDocument,
                                final BsonDocument documentKey,
                                final OperationType operationType,
                                final UpdateDescription updateDescription) {
        this(resumeToken, namespace, fullDocument, documentKey, null, operationType, updateDescription);
    }

    /**
     * Creates a new instance
     *
     * @param resumeToken the resume token
     * @param namespace the namespace
     * @param documentKey a document containing the _id of the changed document
     * @param clusterTime the cluster time at which the change occurred
     * @param fullDocument the fullDocument
     * @param operationType the operation type
     * @param updateDescription the update description
     */
    @Deprecated
    public ChangeStreamDocument(final BsonDocument resumeToken,
                                final MongoNamespace namespace,
                                final TDocument fullDocument,
                                final BsonDocument documentKey,
                                @Nullable final BsonTimestamp clusterTime,
                                final OperationType operationType,
                                final UpdateDescription updateDescription) {
        this(resumeToken, namespaceToDocument(namespace), fullDocument, documentKey,
                clusterTime, operationType, updateDescription);
    }

    /**
     * Creates a new instance
     *
     * @param resumeToken the resume token
     * @param namespaceDocument the BsonDocument representing the namespace
     * @param fullDocument the full document
     * @param documentKey a document containing the _id of the changed document
     * @param clusterTime the cluster time at which the change occured
     * @param operationType the operation type
     * @param updateDescription the update description
     *
     * @since 3.8
     */
    @BsonCreator
    public ChangeStreamDocument(@BsonProperty("resumeToken") final BsonDocument resumeToken,
                                @BsonProperty("ns") final BsonDocument namespaceDocument,
                                @BsonProperty("fullDocument") final TDocument fullDocument,
                                @BsonProperty("documentKey") final BsonDocument documentKey,
                                @Nullable @BsonProperty("clusterTime") final BsonTimestamp clusterTime,
                                @BsonProperty("operationType") final OperationType operationType,
                                @BsonProperty("updateDescription") final UpdateDescription updateDescription) {
        this.resumeToken = resumeToken;
        this.namespaceDocument = namespaceDocument;
        this.documentKey = documentKey;
        this.fullDocument = fullDocument;
        this.clusterTime = clusterTime;
        this.operationType = operationType;
        this.updateDescription = updateDescription;
    }

    private static BsonDocument namespaceToDocument(final MongoNamespace namespace) {
        Assertions.notNull("namespace", namespace);
        return new BsonDocument("db", new BsonString(namespace.getDatabaseName()))
                .append("coll", new BsonString(namespace.getCollectionName()));
    }

    /**
     * Returns the resumeToken
     *
     * @return the resumeToken
     */
    public BsonDocument getResumeToken() {
        return resumeToken;
    }

    /**
     * Returns the namespace
     *
     * The invalidate operation type does include a MongoNamespace in the ChangeStreamDocument response. The
     * dropDatabase operation type includes a MongoNamespace, but does not include a collection name as part
     * of the namespace.
     *
     * @return the namespace. If the namespaceDocument is null or if it is missing either the 'db' or 'coll' keys,
     * then this will return null.
     */
    @BsonIgnore @Nullable
    public MongoNamespace getNamespace() {
        if (namespaceDocument == null) {
            return null;
        }
        if (!namespaceDocument.containsKey("db") || !namespaceDocument.containsKey("coll")) {
            return null;
        }

        return new MongoNamespace(namespaceDocument.getString("db").getValue(), namespaceDocument.getString("coll").getValue());
    }

    /**
     * Returns the namespaceDocument
     *
     * The namespaceDocument is a BsonDocument containing the values associated with a MongoNamespace. The
     * 'db' key refers to the database name and the 'coll' key refers to the collection name.
     *
     * @return the namespaceDocument
     * @since 3.8
     */
    @BsonProperty("ns")
    public BsonDocument getNamespaceDocument() {
        return namespaceDocument;
    }

    /**
     * Returns the database name
     *
     * @return the databaseName. If the namespaceDocument is null or if it is missing the 'db' key, then this will
     * return null.
     * @since 3.8
     */
    @BsonIgnore @Nullable
    public String getDatabaseName() {
        if (namespaceDocument == null) {
            return null;
        }
        if (!namespaceDocument.containsKey("db")) {
            return null;
        }
        return namespaceDocument.getString("db").getValue();
    }

    /**
     * Returns the fullDocument
     *
     * @return the fullDocument
     */
    @Nullable
    public TDocument getFullDocument() {
        return fullDocument;
    }

    /**
     * Returns a document containing just the _id of the changed document.
     *

     * For unsharded collections this contains a single field, _id, with the
     * value of the _id of the document updated.  For sharded collections,
     * this will contain all the components of the shard key in order,
     * followed by the _id if the _id isn’t part of the shard key.
     *

     *
     * @return the document key
     */
    public BsonDocument getDocumentKey() {
        return documentKey;
    }

    /**
     * Gets the cluster time at which the change occurred.
     *
     * @return the cluster time at which the change occurred
     * @since 3.8
     * @mongodb.server.release 4.0
     */
    @Nullable
    public BsonTimestamp getClusterTime() {
        return clusterTime;
    }

    /**
     * Returns the operationType
     *
     * @return the operationType
     */
    public OperationType getOperationType() {
        return operationType;
    }

    /**
     * Returns the updateDescription
     *
     * @return the updateDescription
     */
    public UpdateDescription getUpdateDescription() {
        return updateDescription;
    }

    /**
     * Creates the codec for the specific ChangeStreamOutput type
     *
     * @param fullDocumentClass the class to use to represent the fullDocument
     * @param codecRegistry the codec registry
     * @param <TFullDocument> the fullDocument type
     * @return the codec
     */
    public static <TFullDocument> Codec<ChangeStreamDocument<TFullDocument>> createCodec(final Class<TFullDocument> fullDocumentClass,
                                                                                final CodecRegistry codecRegistry) {
        return new ChangeStreamDocumentCodec<TFullDocument>(fullDocumentClass, codecRegistry);
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ChangeStreamDocument<?> that = (ChangeStreamDocument<?>) o;

        if (resumeToken != null ? !resumeToken.equals(that.resumeToken) : that.resumeToken != null) {
            return false;
        }
        if (namespaceDocument != null ? !namespaceDocument.equals(that.namespaceDocument) : that.namespaceDocument != null) {
            return false;
        }
        if (fullDocument != null ? !fullDocument.equals(that.fullDocument) : that.fullDocument != null) {
            return false;
        }
        if (documentKey != null ? !documentKey.equals(that.documentKey) : that.documentKey != null) {
            return false;
        }
        if (clusterTime != null ? !clusterTime.equals(that.clusterTime) : that.clusterTime != null) {
            return false;
        }
        if (operationType != that.operationType) {
            return false;
        }
        if (updateDescription != null ? !updateDescription.equals(that.updateDescription) : that.updateDescription != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = resumeToken != null ? resumeToken.hashCode() : 0;
        result = 31 * result + (namespaceDocument != null ? namespaceDocument.hashCode() : 0);
        result = 31 * result + (fullDocument != null ? fullDocument.hashCode() : 0);
        result = 31 * result + (documentKey != null ? documentKey.hashCode() : 0);
        result = 31 * result + (clusterTime != null ? clusterTime.hashCode() : 0);
        result = 31 * result + (operationType != null ? operationType.hashCode() : 0);
        result = 31 * result + (updateDescription != null ? updateDescription.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "ChangeStreamDocument{"
                + "resumeToken=" + resumeToken
                + ", namespace=" + getNamespace()
                + ", fullDocument=" + fullDocument
                + ", documentKey=" + documentKey
                + ", clusterTime=" + clusterTime
                + ", operationType=" + operationType
                + ", updateDescription=" + updateDescription
                + "}";
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 tour;

import com.mongodb.Block;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.BulkWriteOptions;
import com.mongodb.client.model.DeleteOneModel;
import com.mongodb.client.model.InsertOneModel;
import com.mongodb.client.model.ReplaceOneModel;
import com.mongodb.client.model.UpdateOneModel;
import com.mongodb.client.model.WriteModel;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;

import java.util.ArrayList;
import java.util.List;

import static com.mongodb.client.model.Accumulators.sum;
import static com.mongodb.client.model.Aggregates.group;
import static com.mongodb.client.model.Aggregates.match;
import static com.mongodb.client.model.Aggregates.project;
import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.exists;
import static com.mongodb.client.model.Filters.gt;
import static com.mongodb.client.model.Filters.gte;
import static com.mongodb.client.model.Filters.lt;
import static com.mongodb.client.model.Filters.lte;
import static com.mongodb.client.model.Projections.excludeId;
import static com.mongodb.client.model.Sorts.descending;
import static com.mongodb.client.model.Updates.inc;
import static com.mongodb.client.model.Updates.set;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

/**
 * The QuickTour code example
 */
public class QuickTour {
    /**
     * Run this main method to see the output of this quick example.
     *
     * @param args takes an optional single argument for the connection string
     */
    public static void main(final String[] args) {
        MongoClient mongoClient;

        if (args.length == 0) {
            // connect to the local database server
            mongoClient = MongoClients.create();
        } else {
            mongoClient = MongoClients.create(args[0]);
        }

        // get handle to "mydb" database
        MongoDatabase database = mongoClient.getDatabase("mydb");

        // get a handle to the "test" collection
        MongoCollection<Document> collection = database.getCollection("test");

        // drop all the data in it
        collection.drop();

        // make a document and insert it
        Document doc = new Document("name", "MongoDB")
                       .append("type", "database")
                       .append("count", 1)
                       .append("info", new Document("x", 203).append("y", 102));

        collection.insertOne(doc);

        // get it (since it's the only one in there since we dropped the rest earlier on)
        Document myDoc = collection.find().first();
        System.out.println(myDoc.toJson());

        // now, lets add lots of little documents to the collection so we can explore queries and cursors
        List<Document> documents = new ArrayList<Document>();
        for (int i = 0; i < 100; i++) {
            documents.add(new Document("i", i));
        }
        collection.insertMany(documents);
        System.out.println("total # of documents after inserting 100 small ones (should be 101) " + collection.countDocuments());

        // find first
        myDoc = collection.find().first();
        System.out.println(myDoc.toJson());

        // lets get all the documents in the collection and print them out
        MongoCursor<Document> cursor = collection.find().iterator();
        try {
            while (cursor.hasNext()) {
                System.out.println(cursor.next().toJson());
            }
        } finally {
            cursor.close();
        }

        for (Document cur : collection.find()) {
            System.out.println(cur.toJson());
        }

        // now use a query to get 1 document out
        myDoc = collection.find(eq("i", 71)).first();
        System.out.println(myDoc.toJson());

        // now use a range query to get a larger subset
        cursor = collection.find(gt("i", 50)).iterator();

        try {
            while (cursor.hasNext()) {
                System.out.println(cursor.next().toJson());
            }
        } finally {
            cursor.close();
        }

        // range query with multiple constraints
        cursor = collection.find(and(gt("i", 50), lte("i", 100))).iterator();

        try {
            while (cursor.hasNext()) {
                System.out.println(cursor.next().toJson());
            }
        } finally {
            cursor.close();
        }

        // Query Filters
        myDoc = collection.find(eq("i", 71)).first();
        System.out.println(myDoc.toJson());

        // now use a range query to get a larger subset
        Block<Document> printBlock = new Block<Document>() {
            @Override
            public void apply(final Document document) {
                System.out.println(document.toJson());
            }
        };
        collection.find(gt("i", 50)).forEach(printBlock);

        // filter where; 50 < i <= 100
        collection.find(and(gt("i", 50), lte("i", 100))).forEach(printBlock);

        // Sorting
        myDoc = collection.find(exists("i")).sort(descending("i")).first();
        System.out.println(myDoc.toJson());

        // Projection
        myDoc = collection.find().projection(excludeId()).first();
        System.out.println(myDoc.toJson());

        // Aggregation
        collection.aggregate(asList(
                match(gt("i", 0)),
                project(Document.parse("{ITimes10: {$multiply: ['$i', 10]}}")))
        ).forEach(printBlock);

        myDoc = collection.aggregate(singletonList(group(null, sum("total", "$i")))).first();
        System.out.println(myDoc.toJson());

        // Update One
        collection.updateOne(eq("i", 10), set("i", 110));

        // Update Many
        UpdateResult updateResult = collection.updateMany(lt("i", 100), inc("i", 100));
        System.out.println(updateResult.getModifiedCount());

        // Delete One
        collection.deleteOne(eq("i", 110));

        // Delete Many
        DeleteResult deleteResult = collection.deleteMany(gte("i", 100));
        System.out.println(deleteResult.getDeletedCount());

        collection.drop();

        // ordered bulk writes
        List<WriteModel<Document>> writes = new ArrayList<WriteModel<Document>>();
        writes.add(new InsertOneModel<Document>(new Document("_id", 4)));
        writes.add(new InsertOneModel<Document>(new Document("_id", 5)));
        writes.add(new InsertOneModel<Document>(new Document("_id", 6)));
        writes.add(new UpdateOneModel<Document>(new Document("_id", 1), new Document("$set", new Document("x", 2))));
        writes.add(new DeleteOneModel<Document>(new Document("_id", 2)));
        writes.add(new ReplaceOneModel<Document>(new Document("_id", 3), new Document("_id", 3).append("x", 4)));

        collection.bulkWrite(writes);

        collection.drop();

        collection.bulkWrite(writes, new BulkWriteOptions().ordered(false));
        //collection.find().forEach(printBlock);

        // Clean up
        database.drop();

        // release resources
        mongoClient.close();
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.client.model;

import java.util.concurrent.TimeUnit;

import static com.mongodb.assertions.Assertions.notNull;

/**
 * The options an estimated count operation.
 *
 * @since 3.8
 * @mongodb.driver.manual reference/command/count/ Count
 */
public class EstimatedDocumentCountOptions {
    private long maxTimeMS;

    /**
     * Gets the maximum execution time on the server for this operation.  The default is 0, which places no limit on the execution time.
     *
     * @param timeUnit the time unit to return the result in
     * @return the maximum execution time in the given time unit
     */
    public long getMaxTime(final TimeUnit timeUnit) {
        notNull("timeUnit", timeUnit);
        return timeUnit.convert(maxTimeMS, TimeUnit.MILLISECONDS);
    }

    /**
     * Sets the maximum execution time on the server for this operation.
     *
     * @param maxTime  the max time
     * @param timeUnit the time unit, which may not be null
     * @return this
     */
    public EstimatedDocumentCountOptions maxTime(final long maxTime, final TimeUnit timeUnit) {
        notNull("timeUnit", timeUnit);
        this.maxTimeMS = TimeUnit.MILLISECONDS.convert(maxTime, timeUnit);
        return this;
    }

    @Override
    public String toString() {
        return "EstimatedCountOptions{"
                + ", maxTimeMS=" + maxTimeMS
                + '}';
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 * Copyright 2018 Cezary Bartosiak
 *
 * 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 org.bson.codecs.jsr310;

import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneOffset;

/**
 * LocalTime Codec.
 *
 *
Encodes and decodes {@code LocalTime} objects to and from {@code DateTime}. Data is stored to millisecond accuracy.

 *
Converts the {@code LocalTime} values to and from EpochDay at {@link ZoneOffset#UTC}.

 *
Note: Requires Java 8 or greater.

 *
 * @mongodb.driver.manual reference/bson-types
 * @since 3.7
 */
public class LocalTimeCodec extends DateTimeBasedCodec<LocalTime> {

    @Override
    public LocalTime decode(final BsonReader reader, final DecoderContext decoderContext) {
        return Instant.ofEpochMilli(validateAndReadDateTime(reader)).atOffset(ZoneOffset.UTC).toLocalTime();
    }

    /**
     * {@inheritDoc}
     *
Converts the {@code LocalTime} to {@link ZoneOffset#UTC} at EpochDay via {@link LocalTime#atDate(LocalDate)} and
     * {@link java.time.LocalDateTime#toInstant(ZoneOffset)}.

     */
    @Override
    public void encode(final BsonWriter writer, final LocalTime value, final EncoderContext encoderContext) {
        writer.writeDateTime(value.atDate(LocalDate.ofEpochDay(0L)).toInstant(ZoneOffset.UTC).toEpochMilli());
    }

    @Override
    public Class<LocalTime> getEncoderClass() {
        return LocalTime.class;
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb;

import com.mongodb.annotations.Immutable;
import com.mongodb.annotations.NotThreadSafe;
import com.mongodb.lang.Nullable;
import com.mongodb.session.ClientSession;

import static com.mongodb.assertions.Assertions.notNull;

/**
 * The options to apply to a {@code ClientSession}.
 *
 * @mongodb.server.release 3.6
 * @since 3.6
 * @see ClientSession
 * @mongodb.driver.dochub core/causal-consistency Causal Consistency
 */
@Immutable
public final class ClientSessionOptions {

    private final Boolean causallyConsistent;
    private final TransactionOptions defaultTransactionOptions;

    /**
     * Whether operations using the session should causally consistent with each other.
     *
     * @return whether operations using the session should be causally consistent.  A null value indicates to use the global default,
     * which is currently true.
     * @mongodb.driver.dochub core/causal-consistency Causal Consistency
     */
    @Nullable
    public Boolean isCausallyConsistent() {
        return causallyConsistent;
    }

    /**
     * Gets the default transaction options for the session.
     *
     * @return the default transaction options for the session
     * @since 3.8
     * @mongodb.server.release 4.0
     */
    public TransactionOptions getDefaultTransactionOptions() {
        return defaultTransactionOptions;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ClientSessionOptions that = (ClientSessionOptions) o;

        if (causallyConsistent != null ? !causallyConsistent.equals(that.causallyConsistent) : that.causallyConsistent != null) {
            return false;
        }
        if (defaultTransactionOptions != null ? !defaultTransactionOptions.equals(that.defaultTransactionOptions)
                : that.defaultTransactionOptions != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = causallyConsistent != null ? causallyConsistent.hashCode() : 0;
        result = 31 * result + (defaultTransactionOptions != null ? defaultTransactionOptions.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "ClientSessionOptions{"
                + "causallyConsistent=" + causallyConsistent
                + ", defaultTransactionOptions=" + defaultTransactionOptions
                + '}';
    }

    /**
     * Gets an instance of a builder
     *
     * @return a builder instance
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Gets an instance of a builder initialized with the given options
     *
     * @param options the options with which to initialize the builder
     * @return a builder instance
     * @since 3.8
     */
    public static Builder builder(final ClientSessionOptions options) {
        notNull("options", options);
        Builder builder = new Builder();
        builder.causallyConsistent = options.isCausallyConsistent();
        builder.defaultTransactionOptions = options.getDefaultTransactionOptions();
        return builder;
    }

    /**
     * A builder for instances of {@code ClientSession}
     */
    @NotThreadSafe
    public static final class Builder {
        private Boolean causallyConsistent;
        private TransactionOptions defaultTransactionOptions = TransactionOptions.builder().build();

        /**
         * Sets whether operations using the session should causally consistent with each other.
         *
         * @param causallyConsistent whether operations using the session should be causally consistent
         * @return this
         * @mongodb.driver.dochub core/causal-consistency Causal Consistency
         */
        public Builder causallyConsistent(final boolean causallyConsistent) {
            this.causallyConsistent = causallyConsistent;
            return this;
        }

        /**
         * Sets whether operations using the session should causally consistent with each other.
         *
         * @param defaultTransactionOptions the default transaction options to use for all transactions on this session,
         * @return this
         * @since 3.8
         * @mongodb.server.release 4.0
         */
        public Builder defaultTransactionOptions(final TransactionOptions defaultTransactionOptions) {
            this.defaultTransactionOptions = notNull("defaultTransactionOptions", defaultTransactionOptions);
            return this;
        }

        /**
         * Build the session options instance.
         *
         * @return The {@code ClientSessionOptions}
         */
        public ClientSessionOptions build() {
            return new ClientSessionOptions(this);
        }

        private Builder() {
        }
    }

    private ClientSessionOptions(final Builder builder) {
        this.causallyConsistent = builder.causallyConsistent;
        this.defaultTransactionOptions = builder.defaultTransactionOptions;
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.
 */

// MongoOptions.java

package com.mongodb;

import javax.net.SocketFactory;
import java.util.concurrent.TimeUnit;

import static com.mongodb.MongoClientOptions.Builder;

/**
 * Various settings for a Mongo instance. Not thread safe, and superseded by MongoClientOptions.  This class may be deprecated in a future
 * release.
 *
 * @see MongoClientOptions
 * @see MongoClient
 * @deprecated Please use {@link MongoClientOptions} instead.
 */
@Deprecated
public class MongoOptions {

    /**
     * The description for {@code Mongo} instances created with these options. This is used in various places like logging.
     */
    public String description;

    /**
     * The maximum number of connections allowed per host for this Mongo instance. Those connections will be kept in a pool when idle. Once
     * the pool is exhausted, any operation requiring a connection will block waiting for an available connection. Default is 10.
     *
     * @see MongoOptions#threadsAllowedToBlockForConnectionMultiplier
     */
    public int connectionsPerHost;

    /**
     * This multiplier, multiplied with the connectionsPerHost setting, gives the maximum number of threads that may be waiting for a
     * connection to become available from the pool. All further threads will get an exception right away. For example if connectionsPerHost
     * is 10 and threadsAllowedToBlockForConnectionMultiplier is 5, then up to 50 threads can wait for a connection. Default is 5.
     */
    public int threadsAllowedToBlockForConnectionMultiplier;

    /**
     * The maximum wait time in milliseconds that a thread may wait for a connection to become available. Default is 120,000. A value of 0
     * means that it will not wait.  A negative value means to wait indefinitely.
     */
    public int maxWaitTime;

    /**
     * The connection timeout in milliseconds.  A value of 0 means no timeout. It is used solely when establishing a new connection {@link
     * java.net.Socket#connect(java.net.SocketAddress, int) } Default is 10,000.
     */
    public int connectTimeout;

    /**
     * The socket timeout in milliseconds It is used for I/O socket read and write operations {@link java.net.Socket#setSoTimeout(int)}
     * Default is 0 and means no timeout.
     */
    public int socketTimeout;

    /**
     * This flag controls the socket keep alive feature that keeps a connection alive through firewalls {@link
     * java.net.Socket#setKeepAlive(boolean)} Default is false.
     */
    public boolean socketKeepAlive;

    /**
     * Specifies the read preference.
     */
    public ReadPreference readPreference;

    /**
     * Override the DBCallback factory. Default is for the standard Mongo Java driver configuration.
     */
    public DBDecoderFactory dbDecoderFactory;

    /**
     * Override the encoding factory. Default is for the standard Mongo Java driver configuration.
     */
    public DBEncoderFactory dbEncoderFactory;

    /**
     * If {@code true} the driver will use a WriteConcern of WriteConcern.SAFE for all operations. If w, wtimeout, fsync or j are specified,
     * this setting is ignored. Default is false.
     */
    public boolean safe;

    /**
     * The "w" value, (number of writes), of the global WriteConcern. Default is 0.
     */
    public int w;

    /**
     * The "wtimeout" value of the global WriteConcern. Default is 0.
     */
    public int wtimeout;

    /**
     * The "fsync" value of the global WriteConcern. true indicates writes should wait for data to be written to server data file Default is
     * false.
     */
    public boolean fsync;

    /**
     * The "j" value of the global WriteConcern. true indicates writes should wait for a journaling group commit Default is false.
     */
    public boolean j;

    /**
     * Sets the socket factory for creating sockets to mongod Default is SocketFactory.getDefault()
     */
    public SocketFactory socketFactory;

    /**
     *
Sets whether there is a a finalize method created that cleans up instances of DBCursor that the client does not close.  If you are
     * careful to always call the close method of DBCursor, then this can safely be set to false.

     *
     *
Default is true.

     *
     * @see DBCursor#close()
     */
    public boolean cursorFinalizerEnabled;

    /**
     * Sets the write concern.  If this is not set, the write concern defaults to the combination of settings of the other write
     * concern-related fields.  If set, this will override all of the other write concern-related fields.
     *
     * @see #w
     * @see #safe
     * @see #wtimeout
     * @see #fsync
     * @see #j
     */
    public WriteConcern writeConcern;

    /**
     * Sets whether JMX beans registered by the driver should always be MBeans, regardless of whether the VM is Java 6 or greater. If false,
     * the driver will use MXBeans if the VM is Java 6 or greater, and use MBeans if the VM is Java 5.
 Default is false.

     */
    public boolean alwaysUseMBeans;

    String requiredReplicaSetName;

    /**
     * Creates a new default {@code MongoOptions}. This class is deprecated, use {@link com.mongodb.MongoClientOptions}.
     *
     * @deprecated use {@link com.mongodb.MongoClientOptions}
     */
    @Deprecated
    public MongoOptions() {
        reset();
    }

    /**
     * Creates a new {@code MongoOptions} with the given options. This class is deprecated, use {@link com.mongodb.MongoClientOptions}.
     *
     * @param options the MongoClientOptions to copy values from into the new MongoOptions.
     * @deprecated use {@link com.mongodb.MongoClientOptions}
     */
    @SuppressWarnings("deprecation")
    @Deprecated
    public MongoOptions(final MongoClientOptions options) {
        connectionsPerHost = options.getConnectionsPerHost();
        threadsAllowedToBlockForConnectionMultiplier = options.getThreadsAllowedToBlockForConnectionMultiplier();
        maxWaitTime = options.getMaxWaitTime();
        connectTimeout = options.getConnectTimeout();
        socketFactory = options.getSocketFactory();
        socketTimeout = options.getSocketTimeout();
        socketKeepAlive = options.isSocketKeepAlive();
        readPreference = options.getReadPreference();
        dbDecoderFactory = options.getDbDecoderFactory();
        dbEncoderFactory = options.getDbEncoderFactory();
        description = options.getDescription();
        writeConcern = options.getWriteConcern();
        alwaysUseMBeans = options.isAlwaysUseMBeans();
        requiredReplicaSetName = options.getRequiredReplicaSetName();
    }

    /**
     * Reset all settings to the default.
     */
    public void reset() {
        connectionsPerHost = 10;
        threadsAllowedToBlockForConnectionMultiplier = 5;
        maxWaitTime = 1000 * 60 * 2;
        connectTimeout = 1000 * 10;
        socketFactory = SocketFactory.getDefault();
        socketTimeout = 0;
        socketKeepAlive = false;
        readPreference = null;
        writeConcern = null;
        safe = false;
        w = 0;
        wtimeout = 0;
        fsync = false;
        j = false;
        dbDecoderFactory = DefaultDBDecoder.FACTORY;
        dbEncoderFactory = DefaultDBEncoder.FACTORY;
        description = null;
        cursorFinalizerEnabled = true;
        alwaysUseMBeans = false;
        requiredReplicaSetName = null;
    }

    /**
     * Copy this MongoOptions instance into a new instance.
     *
     * @return the new MongoOptions with the same settings as this instance.
     */
    public MongoOptions copy() {
        MongoOptions m = new MongoOptions();
        m.connectionsPerHost = connectionsPerHost;
        m.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
        m.maxWaitTime = maxWaitTime;
        m.connectTimeout = connectTimeout;
        m.socketFactory = socketFactory;
        m.socketTimeout = socketTimeout;
        m.socketKeepAlive = socketKeepAlive;
        m.readPreference = readPreference;
        m.writeConcern = writeConcern;
        m.safe = safe;
        m.w = w;
        m.wtimeout = wtimeout;
        m.fsync = fsync;
        m.j = j;
        m.dbDecoderFactory = dbDecoderFactory;
        m.dbEncoderFactory = dbEncoderFactory;
        m.description = description;
        m.cursorFinalizerEnabled = cursorFinalizerEnabled;
        m.alwaysUseMBeans = alwaysUseMBeans;
        m.requiredReplicaSetName = requiredReplicaSetName;
        return m;
    }

    @SuppressWarnings("deprecation")
    MongoClientOptions toClientOptions() {
        Builder builder = MongoClientOptions.builder()
                                            .requiredReplicaSetName(requiredReplicaSetName)
                                            .connectionsPerHost(connectionsPerHost)
                                            .connectTimeout(connectTimeout)
                                            .dbDecoderFactory(dbDecoderFactory)
                                            .dbEncoderFactory(dbEncoderFactory)
                                            .description(description)
                                            .maxWaitTime(maxWaitTime)
                                            .socketFactory(socketFactory)
                                            .socketKeepAlive(socketKeepAlive)
                                            .socketTimeout(socketTimeout)
                                            .threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier)
                                            .cursorFinalizerEnabled(cursorFinalizerEnabled)
                                            .alwaysUseMBeans(alwaysUseMBeans);

        builder.writeConcern(getWriteConcern());

        if (readPreference != null) {
            builder.readPreference(getReadPreference());
        }
        return builder.build();
    }

    /**
     * Helper method to return the appropriate WriteConcern instance based on the current related options settings.
     *
     * @return a WriteConcern for the current MongoOptions.
     */
    @SuppressWarnings("deprecation")
    public WriteConcern getWriteConcern() {
        WriteConcern retVal;

        if (writeConcern != null) {
            retVal = writeConcern;
        } else if (w != 0 || wtimeout != 0 || fsync | j) {
            retVal = WriteConcern.ACKNOWLEDGED;
            if (w != 0) {
                retVal = retVal.withW(w);
            }
            if (wtimeout != 0) {
                retVal = retVal.withWTimeout(wtimeout, TimeUnit.MILLISECONDS);
            }
            if (fsync) {
                retVal = retVal.withFsync(fsync);
            }
            if (j) {
                retVal = retVal.withJ(j);
            }
        } else if (safe) {
            retVal = WriteConcern.ACKNOWLEDGED;
        } else {
            retVal = WriteConcern.UNACKNOWLEDGED;
        }
        return retVal;
    }

    /**
     * Sets the write concern.  If this is not set, the write concern defaults to the combination of settings of the other write
     * concern-related fields.  If set, this will override all of the other write concern-related fields.
     *
     * @param writeConcern sets the write concern
     */
    public void setWriteConcern(final WriteConcern writeConcern) {
        this.writeConcern = writeConcern;
    }

    /**
     * Gets the socket factory for creating sockets to mongod. Default is SocketFactory.getDefault()
     *
     * @return the socket factory for creating sockets to mongod
     */
    public synchronized SocketFactory getSocketFactory() {
        return socketFactory;
    }

    /**
     * Sets the socket factory for creating sockets to mongod.
     *
     * @param factory sets the socket factory for creating sockets to mongod
     */
    public synchronized void setSocketFactory(final SocketFactory factory) {
        socketFactory = factory;
    }

    /**
     * Gets the description for {@code Mongo} instances created with these options.
     *
     * @return The description for {@code MongoClient} instances created with these options
     */
    public synchronized String getDescription() {
        return description;
    }

    /**
     * Sets the description for {@code Mongo} instances created with these options. This is used in various places like logging.
     *
     * @param desc The description for {@code Mongo} instances created with these options
     */
    public synchronized void setDescription(final String desc) {
        description = desc;
    }

    /**
     * Gets the maximum number of connections allowed per host for this Mongo instance.
     *
     * @return the maximum number of connections allowed per host for this Mongo instance
     */
    public synchronized int getConnectionsPerHost() {
        return connectionsPerHost;
    }

    /**
     * Sets the maximum number of connections allowed per host for this Mongo instance. Those connections will be kept in a pool when idle.
     * Once the pool is exhausted, any operation requiring a connection will block waiting for an available connection. Default is 10.
     *
     * @param connections sets the maximum number of connections allowed per host for this Mongo instance
     */
    public synchronized void setConnectionsPerHost(final int connections) {
        connectionsPerHost = connections;
    }

    /**
     * Gets the multiplier which, when multiplied with the connectionsPerHost setting, gives the maximum number of threads that may be
     * waiting for a connection to become available from the pool.
     *
     * @return the maximum number of threads that may be waiting for a connection
     */
    public synchronized int getThreadsAllowedToBlockForConnectionMultiplier() {
        return threadsAllowedToBlockForConnectionMultiplier;
    }

    /**
     * Sets the multiplier which, when multiplied with the connectionsPerHost setting, gives the maximum number of threads that may be
     * waiting for a connection to become available from the pool. All further threads will get an exception right away. For example if
     * connectionsPerHost is 10 and threadsAllowedToBlockForConnectionMultiplier is 5, then up to 50 threads can wait for a connection.
     * Default is 5.
     *
     * @param threads multiplied with connectionsPerHost, sets the maximum number of threads that may be waiting for a connection
     */
    public synchronized void setThreadsAllowedToBlockForConnectionMultiplier(final int threads) {
        threadsAllowedToBlockForConnectionMultiplier = threads;
    }

    /**
     * Gets the maximum wait time in milliseconds that a thread may wait for a connection to become available.
     *
     * @return The maximum time in milliseconds that threads wait for a connection
     */
    public synchronized int getMaxWaitTime() {
        return maxWaitTime;
    }

    /**
     * Sets the maximum wait time in milliseconds that a thread may wait for a connection to become available. Default is 120, 000. A value
     * of 0 means that it will not wait.  A negative value means to wait indefinitely.
     *
     * @param timeMS set the maximum time in milliseconds that threads wait for a connection
     */
    public synchronized void setMaxWaitTime(final int timeMS) {
        maxWaitTime = timeMS;
    }

    /**
     * Gets the connection timeout in milliseconds.  A value of 0 means no timeout.
     *
     * @return the connection timeout in milliseconds.
     */
    public synchronized int getConnectTimeout() {
        return connectTimeout;
    }

    /**
     * Sets the connection timeout in milliseconds.  A value of 0 means no timeout. It is used solely when establishing a new connection
     * {@link java.net.Socket#connect(java.net.SocketAddress, int) } Default is 10,000.
     *
     * @param timeoutMS set the connection timeout in milliseconds.
     */
    public synchronized void setConnectTimeout(final int timeoutMS) {
        connectTimeout = timeoutMS;
    }

    /**
     * Gets the socket timeout in milliseconds. 0 means no timeout.
     *
     * @return The socket timeout in milliseconds
     */
    public synchronized int getSocketTimeout() {
        return socketTimeout;
    }

    /**
     * Sets the socket timeout in milliseconds It is used for I/O socket read and write operations {@link java.net.Socket#setSoTimeout
     * (int)} Default is 0 and means no timeout.
     *
     * @param timeoutMS set the socket timeout in milliseconds
     */
    public synchronized void setSocketTimeout(final int timeoutMS) {
        socketTimeout = timeoutMS;
    }

    /**
     * Gets the flag that controls the socket keep alive feature that keeps a connection alive through firewalls.
     *
     * @return connection keep-alive flag
     */
    public synchronized boolean isSocketKeepAlive() {
        return socketKeepAlive;
    }

    /**
     * Sets the flag that controls the socket keep alive feature that keeps a connection alive through firewalls {@link
     * java.net.Socket#setKeepAlive(boolean)} Default is false.
     *
     * @param keepAlive set connection keep-alive flag
     */
    public synchronized void setSocketKeepAlive(final boolean keepAlive) {
        socketKeepAlive = keepAlive;
    }

    /**
     * Gets the DBCallback factory.
     *
     * @return the DBCallback decoding factory
     */
    public synchronized DBDecoderFactory getDbDecoderFactory() {
        return dbDecoderFactory;
    }

    /**
     * Override the DBCallback factory. Default is for the standard Mongo Java driver configuration.
     *
     * @param factory sets the DBCallback decoding factory
     */
    public synchronized void setDbDecoderFactory(final DBDecoderFactory factory) {
        dbDecoderFactory = factory;
    }

    /**
     * Gets the encoding factory.
     *
     * @return the encoding factory
     */
    public synchronized DBEncoderFactory getDbEncoderFactory() {
        return dbEncoderFactory;
    }

    /**
     * Override the encoding factory. Default is for the standard Mongo Java driver configuration.
     *
     * @param factory sets the encoding factory
     */
    public synchronized void setDbEncoderFactory(final DBEncoderFactory factory) {
        dbEncoderFactory = factory;
    }

    /**
     * Returns whether the driver will use a WriteConcern of WriteConcern.ACKNOWLEDGED for all operations.
     *
     * @return true if driver uses WriteConcern.SAFE for all operations.
     */
    public synchronized boolean isSafe() {
        return safe;
    }

    /**
     * If {@code true} the driver will use a WriteConcern of WriteConcern.SAFE for all operations. If w, wtimeout, fsync or j are specified,
     * this setting is ignored. Default is false.
     *
     * @param isSafe true if driver uses WriteConcern.SAFE for all operations.
     */
    public synchronized void setSafe(final boolean isSafe) {
        safe = isSafe;
    }

    /**
     * Gets the "w" value, (number of writes), of the global WriteConcern.
     *
     * @return value returns the number of writes of the global WriteConcern.
     */
    public synchronized int getW() {
        return w;
    }

    /**
     * Sets the "w" value, (number of writes), of the global WriteConcern. Default is 0.
     *
     * @param val set the number of writes of the global WriteConcern.
     */
    public synchronized void setW(final int val) {
        w = val;
    }

    /**
     * Gets the "wtimeout" value of the global WriteConcern.
     *
     * @return timeout in millis for write operation
     */
    public synchronized int getWtimeout() {
        return wtimeout;
    }

    /**
     * Sets the "wtimeout" value of the global WriteConcern. Default is 0.
     *
     * @param timeoutMS sets timeout in millis for write operation
     */
    public synchronized void setWtimeout(final int timeoutMS) {
        wtimeout = timeoutMS;
    }

    /**
     * Gets the "fsync" value of the global WriteConcern. True indicates writes should wait for data to be written to server data file
     *
     * @return true if global write concern is set to fsync
     */
    public synchronized boolean isFsync() {
        return fsync;
    }

    /**
     * Sets the "fsync" value of the global WriteConcern. True indicates writes should wait for data to be written to server data file
     * Default is false.
     *
     * @param sync sets global write concern's fsync safe value
     */
    public synchronized void setFsync(final boolean sync) {
        fsync = sync;
    }

    /**
     * Gets the "j" value of the global WriteConcern. True indicates writes should wait for a journaling group commit
     *
     * @return true if global write concern is set to journal safe
     */
    public synchronized boolean isJ() {
        return j;
    }

    /**
     * Sets the "j" value of the global WriteConcern. True indicates writes should wait for a journaling group commit. Default is false.
     *
     * @param safe sets global write concern's journal safe value
     */
    public synchronized void setJ(final boolean safe) {
        j = safe;
    }

    /**
     * Gets the read preference.
     *
     * @return the read preference
     */
    public ReadPreference getReadPreference() {
        return readPreference;
    }

    /**
     * Specifies the read preference.
     *
     * @param readPreference the read preference
     */
    public void setReadPreference(final ReadPreference readPreference) {
        this.readPreference = readPreference;
    }

    /**
     * Gets whether there is a a finalize method created that cleans up instances of DBCursor that the client does not close.
     *
     * @return whether DBCursor finalizer is enabled
     */
    public boolean isCursorFinalizerEnabled() {
        return cursorFinalizerEnabled;
    }

    /**
     * Sets whether there is a a finalize method created that cleans up instances of DBCursor that the client does not close.  If you are
     * careful to always call the close method of DBCursor, then this can safely be set to false.  Default is true.
     *
     * @param cursorFinalizerEnabled whether cursor finalizer is enabled
     */
    public void setCursorFinalizerEnabled(final boolean cursorFinalizerEnabled) {
        this.cursorFinalizerEnabled = cursorFinalizerEnabled;

    }

    /**
     * Gets whether JMX beans registered by the driver should always be MBeans, regardless of whether the VM is Java 6 or greater. If false,
     * the driver will use MXBeans if the VM is Java 6 or greater, and use MBeans if the VM is Java 5.
     *
     * @return true if the driver should always use MBeans, regardless of VM
     */
    public boolean isAlwaysUseMBeans() {
        return alwaysUseMBeans;
    }

    /**
     * Sets whether JMX beans registered by the driver should always be MBeans, regardless of whether the VM is Java 6 or greater. If false,
     * the driver will use MXBeans if the VM is Java 6 or greater, and use MBeans if the VM is Java 5. Default is false.
     *
     * @param alwaysUseMBeans sets whether the driver should always use MBeans, regardless of VM
     */
    public void setAlwaysUseMBeans(final boolean alwaysUseMBeans) {
        this.alwaysUseMBeans = alwaysUseMBeans;
    }

    /**
     * Gets the required replica set name that this client should be connecting to.
     *
     * @return the required replica set name, or null if none is required
     * @since 2.12
     */
    public String getRequiredReplicaSetName() {
        return requiredReplicaSetName;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        MongoOptions options = (MongoOptions) o;

        if (alwaysUseMBeans != options.alwaysUseMBeans) {
            return false;
        }
        if (connectTimeout != options.connectTimeout) {
            return false;
        }
        if (connectionsPerHost != options.connectionsPerHost) {
            return false;
        }
        if (cursorFinalizerEnabled != options.cursorFinalizerEnabled) {
            return false;
        }
        if (fsync != options.fsync) {
            return false;
        }
        if (j != options.j) {
            return false;
        }
        if (maxWaitTime != options.maxWaitTime) {
            return false;
        }
        if (safe != options.safe) {
            return false;
        }
        if (socketKeepAlive != options.socketKeepAlive) {
            return false;
        }
        if (socketTimeout != options.socketTimeout) {
            return false;
        }
        if (threadsAllowedToBlockForConnectionMultiplier != options.threadsAllowedToBlockForConnectionMultiplier) {
            return false;
        }
        if (w != options.w) {
            return false;
        }
        if (wtimeout != options.wtimeout) {
            return false;
        }
        if (dbDecoderFactory != null ? !dbDecoderFactory.equals(options.dbDecoderFactory) : options.dbDecoderFactory != null) {
            return false;
        }
        if (dbEncoderFactory != null ? !dbEncoderFactory.equals(options.dbEncoderFactory) : options.dbEncoderFactory != null) {
            return false;
        }
        if (description != null ? !description.equals(options.description) : options.description != null) {
            return false;
        }
        if (readPreference != null ? !readPreference.equals(options.readPreference) : options.readPreference != null) {
            return false;
        }
        if (socketFactory != null ? !socketFactory.equals(options.socketFactory) : options.socketFactory != null) {
            return false;
        }
        if (writeConcern != null ? !writeConcern.equals(options.writeConcern) : options.writeConcern != null) {
            return false;
        }
        if (requiredReplicaSetName != null ? !requiredReplicaSetName.equals(options.requiredReplicaSetName)
                                           : options.requiredReplicaSetName != null) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result = description != null ? description.hashCode() : 0;
        result = 31 * result + connectionsPerHost;
        result = 31 * result + threadsAllowedToBlockForConnectionMultiplier;
        result = 31 * result + maxWaitTime;
        result = 31 * result + connectTimeout;
        result = 31 * result + socketTimeout;
        result = 31 * result + (socketKeepAlive ? 1 : 0);
        result = 31 * result + (readPreference != null ? readPreference.hashCode() : 0);
        result = 31 * result + (dbDecoderFactory != null ? dbDecoderFactory.hashCode() : 0);
        result = 31 * result + (dbEncoderFactory != null ? dbEncoderFactory.hashCode() : 0);
        result = 31 * result + (safe ? 1 : 0);
        result = 31 * result + w;
        result = 31 * result + wtimeout;
        result = 31 * result + (fsync ? 1 : 0);
        result = 31 * result + (j ? 1 : 0);
        result = 31 * result + (socketFactory != null ? socketFactory.hashCode() : 0);
        result = 31 * result + (cursorFinalizerEnabled ? 1 : 0);
        result = 31 * result + (writeConcern != null ? writeConcern.hashCode() : 0);
        result = 31 * result + (alwaysUseMBeans ? 1 : 0);
        result = 31 * result + (requiredReplicaSetName != null ? requiredReplicaSetName.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "MongoOptions{"
               + "description='" + description + '\''
               + ", connectionsPerHost=" + connectionsPerHost
               + ", threadsAllowedToBlockForConnectionMultiplier=" + threadsAllowedToBlockForConnectionMultiplier
               + ", maxWaitTime=" + maxWaitTime
               + ", connectTimeout=" + connectTimeout
               + ", socketTimeout=" + socketTimeout
               + ", socketKeepAlive=" + socketKeepAlive
               + ", readPreference=" + readPreference
               + ", dbDecoderFactory=" + dbDecoderFactory
               + ", dbEncoderFactory=" + dbEncoderFactory
               + ", safe=" + safe
               + ", w=" + w
               + ", wtimeout=" + wtimeout
               + ", fsync=" + fsync
               + ", j=" + j
               + ", socketFactory=" + socketFactory
               + ", cursorFinalizerEnabled=" + cursorFinalizerEnabled
               + ", writeConcern=" + writeConcern
               + ", alwaysUseMBeans=" + alwaysUseMBeans
               + ", requiredReplicaSetName=" + requiredReplicaSetName
               + '}';
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb;

import com.mongodb.annotations.Immutable;
import com.mongodb.connection.ClusterDescription;
import com.mongodb.connection.ClusterType;
import com.mongodb.connection.ServerDescription;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonInt64;
import org.bson.BsonString;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * Abstract class for all preference which can be combined with tags
 */
@Immutable
public abstract class TaggableReadPreference extends ReadPreference {
    private static final int SMALLEST_MAX_STALENESS_MS = 90000;
    private static final int IDLE_WRITE_PERIOD_MS = 10000;

    private final List<TagSet> tagSetList = new ArrayList<TagSet>();
    private final Long maxStalenessMS;

    TaggableReadPreference() {
        this.maxStalenessMS = null;
    }

    TaggableReadPreference(final List<TagSet> tagSetList, @Nullable final Long maxStaleness, final TimeUnit timeUnit) {
        notNull("tagSetList", tagSetList);
        isTrueArgument("maxStaleness is null or >= 0", maxStaleness == null || maxStaleness >= 0);
        this.maxStalenessMS = maxStaleness == null ? null : MILLISECONDS.convert(maxStaleness, timeUnit);

        this.tagSetList.addAll(tagSetList);
    }

    @Override
    public boolean isSlaveOk() {
        return true;
    }

    @Override
    public BsonDocument toDocument() {
        BsonDocument readPrefObject = new BsonDocument("mode", new BsonString(getName()));

        if (!tagSetList.isEmpty()) {
            readPrefObject.put("tags", tagsListToBsonArray());
        }

        if (maxStalenessMS != null) {
            readPrefObject.put("maxStalenessSeconds", new BsonInt64(MILLISECONDS.toSeconds(maxStalenessMS)));
        }
        return readPrefObject;
    }

    /**
     * Gets the list of tag sets as a list of {@code TagSet} instances.
     *
     * @return the list of tag sets
     * @since 2.13
     */
    public List<TagSet> getTagSetList() {
        return Collections.unmodifiableList(tagSetList);
    }

    /**
     * Gets the maximum acceptable staleness of a secondary in order to be considered for read operations.
     *

     * The maximum staleness feature is designed to prevent badly-lagging servers from being selected. The staleness estimate is imprecise
     * and shouldn't be used to try to select "up-to-date" secondaries.
     *

     *

     * The driver estimates the staleness of each secondary, based on lastWriteDate values provided in server isMaster responses,
     * and selects only those secondaries whose staleness is less than or equal to maxStaleness.
     *

     * @param timeUnit the time unit in which to return the value
     * @return the maximum acceptable staleness in the given time unit, or null if the value is not set
     * @mongodb.server.release 3.4
     * @since 3.4
     */
    @Nullable
    public Long getMaxStaleness(final TimeUnit timeUnit) {
        notNull("timeUnit", timeUnit);
        if (maxStalenessMS == null) {
            return null;
        }
        return timeUnit.convert(maxStalenessMS, TimeUnit.MILLISECONDS);
    }

    @Override
    public String toString() {
        return "ReadPreference{"
                       + "name=" + getName()
                       + (tagSetList.isEmpty() ? "" : ", tagSetList=" + tagSetList)
                       + (maxStalenessMS == null ? "" : ", maxStalenessMS=" + maxStalenessMS)
                       + '}';
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        TaggableReadPreference that = (TaggableReadPreference) o;

        if (maxStalenessMS != null ? !maxStalenessMS.equals(that.maxStalenessMS) : that.maxStalenessMS != null) {
            return false;
        }
        if (!tagSetList.equals(that.tagSetList)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = tagSetList.hashCode();
        result = 31 * result + getName().hashCode();
        result = 31 * result + (maxStalenessMS != null ? maxStalenessMS.hashCode() : 0);
        return result;
    }

    @Override
    @SuppressWarnings("deprecation")
    protected List<ServerDescription> chooseForNonReplicaSet(final ClusterDescription clusterDescription) {
        return selectFreshServers(clusterDescription, clusterDescription.getAny());
    }

    protected static ClusterDescription copyClusterDescription(final ClusterDescription clusterDescription,
                                                               final List<ServerDescription> selectedServers) {
        return new ClusterDescription(clusterDescription.getConnectionMode(),
                                             clusterDescription.getType(),
                                             selectedServers,
                                             clusterDescription.getClusterSettings(),
                                             clusterDescription.getServerSettings());
    }

    protected List<ServerDescription> selectFreshServers(final ClusterDescription clusterDescription,
                                                         final List<ServerDescription> servers) {
        Long maxStaleness = getMaxStaleness(MILLISECONDS);
        if (maxStaleness == null) {
            return servers;
        }

        if (clusterDescription.getServerSettings() == null) {
            throw new MongoConfigurationException("heartbeat frequency must be provided in cluster description");
        }

        if (!serversAreAllThreeDotFour(clusterDescription)) {
            throw new MongoConfigurationException("Servers must all be at least version 3.4 when max staleness is configured");
        }

        if (clusterDescription.getType() != ClusterType.REPLICA_SET) {
            return servers;
        }

        long heartbeatFrequencyMS = clusterDescription.getServerSettings().getHeartbeatFrequency(MILLISECONDS);

        if (maxStaleness < Math.max(SMALLEST_MAX_STALENESS_MS, heartbeatFrequencyMS + IDLE_WRITE_PERIOD_MS)) {
            if (SMALLEST_MAX_STALENESS_MS > heartbeatFrequencyMS + IDLE_WRITE_PERIOD_MS){
                throw new MongoConfigurationException(format("Max staleness (%d sec) must be at least 90 seconds",
                        getMaxStaleness(SECONDS)));
            } else {
                throw new MongoConfigurationException(format("Max staleness (%d ms) must be at least the heartbeat period (%d ms) "
                                                                     + "plus the idle write period (%d ms)",
                        maxStaleness, heartbeatFrequencyMS, IDLE_WRITE_PERIOD_MS));
            }
        }
        List<ServerDescription> freshServers = new ArrayList<ServerDescription>(servers.size());

        ServerDescription primary = findPrimary(clusterDescription);

        if (primary != null) {
            for (ServerDescription cur : servers) {
                if (cur.isPrimary()) {
                    freshServers.add(cur);
                } else {
                    if (getStalenessOfSecondaryRelativeToPrimary(primary, cur, heartbeatFrequencyMS) <= maxStaleness) {
                        freshServers.add(cur);
                    }
                }
            }
        } else {
            ServerDescription mostUpToDateSecondary = findMostUpToDateSecondary(clusterDescription);
            for (ServerDescription cur : servers) {
                if (getLastWriteDateNonNull(mostUpToDateSecondary).getTime() - getLastWriteDateNonNull(cur).getTime()
                        + heartbeatFrequencyMS <= maxStaleness) {
                    freshServers.add(cur);
                }
            }
        }

        return freshServers;
    }

    private long getStalenessOfSecondaryRelativeToPrimary(final ServerDescription primary, final ServerDescription serverDescription,
                                                          final long heartbeatFrequencyMS) {
        return getLastWriteDateNonNull(primary).getTime()
                       + (serverDescription.getLastUpdateTime(MILLISECONDS) - primary.getLastUpdateTime(MILLISECONDS))
                       - getLastWriteDateNonNull(serverDescription).getTime() + heartbeatFrequencyMS;
    }

    @Nullable
    private ServerDescription findPrimary(final ClusterDescription clusterDescription) {
        for (ServerDescription cur : clusterDescription.getServerDescriptions()) {
            if (cur.isPrimary()) {
                return cur;
            }
        }
        return null;
    }

    private ServerDescription findMostUpToDateSecondary(final ClusterDescription clusterDescription) {
        ServerDescription mostUpdateToDateSecondary = null;
        for (ServerDescription cur : clusterDescription.getServerDescriptions()) {
            if (cur.isSecondary()) {
                if (mostUpdateToDateSecondary == null
                            || getLastWriteDateNonNull(cur).getTime() > getLastWriteDateNonNull(mostUpdateToDateSecondary).getTime()) {
                    mostUpdateToDateSecondary = cur;
                }
            }
        }
        if (mostUpdateToDateSecondary == null) {
            throw new MongoInternalException("Expected at least one secondary in cluster description: " + clusterDescription);
        }
        return mostUpdateToDateSecondary;
    }

    private Date getLastWriteDateNonNull(final ServerDescription serverDescription) {
        Date lastWriteDate = serverDescription.getLastWriteDate();
        if (lastWriteDate == null) {
            throw new MongoClientException("lastWriteDate should not be null in " + serverDescription);
        }
        return lastWriteDate;
    }

    private boolean serversAreAllThreeDotFour(final ClusterDescription clusterDescription) {
        for (ServerDescription cur : clusterDescription.getServerDescriptions()) {
            if (cur.isOk() && cur.getMaxWireVersion() < 5) {
                return false;
            }
        }
        return true;
    }

    /**
     * Read from secondary
     */
    static class SecondaryReadPreference extends TaggableReadPreference {
        SecondaryReadPreference() {
        }

        SecondaryReadPreference(final List<TagSet> tagSetList, @Nullable final Long maxStaleness, final TimeUnit timeUnit) {
            super(tagSetList, maxStaleness, timeUnit);
        }

        @Override
        public String getName() {
            return "secondary";
        }

        @Override
        @SuppressWarnings("deprecation")
        protected List<ServerDescription> chooseForReplicaSet(final ClusterDescription clusterDescription) {
            List<ServerDescription> selectedServers = selectFreshServers(clusterDescription, clusterDescription.getSecondaries());
            if (!getTagSetList().isEmpty()) {
                ClusterDescription nonStaleClusterDescription = copyClusterDescription(clusterDescription, selectedServers);
                selectedServers = Collections.emptyList();
                for (final TagSet tagSet : getTagSetList()) {
                    List<ServerDescription> servers = nonStaleClusterDescription.getSecondaries(tagSet);
                    if (!servers.isEmpty()) {
                        selectedServers = servers;
                        break;
                    }
                }
            }
            return selectedServers;
        }
    }

    /**
     * Read from secondary if available, otherwise from primary, irrespective of tags.
     */
    static class SecondaryPreferredReadPreference extends SecondaryReadPreference {
        SecondaryPreferredReadPreference() {
        }

        SecondaryPreferredReadPreference(final List<TagSet> tagSetList, @Nullable final Long maxStaleness, final TimeUnit timeUnit) {
            super(tagSetList, maxStaleness, timeUnit);
        }

        @Override
        public String getName() {
            return "secondaryPreferred";
        }

        @Override
        @SuppressWarnings("deprecation")
        protected List<ServerDescription> chooseForReplicaSet(final ClusterDescription clusterDescription) {
            List<ServerDescription> selectedServers = super.chooseForReplicaSet(clusterDescription);
            if (selectedServers.isEmpty()) {
                selectedServers = clusterDescription.getPrimaries();
            }
            return selectedServers;
        }
    }

    /**
     * Read from nearest node respective of tags.
     */
    static class NearestReadPreference extends TaggableReadPreference {
        NearestReadPreference() {
        }

        NearestReadPreference(final List<TagSet> tagSetList, @Nullable final Long maxStaleness, final TimeUnit timeUnit) {
            super(tagSetList, maxStaleness, timeUnit);
        }

        @Override
        public String getName() {
            return "nearest";
        }

        @Override
        @SuppressWarnings("deprecation")
        public List<ServerDescription> chooseForReplicaSet(final ClusterDescription clusterDescription) {
            List<ServerDescription> selectedServers = selectFreshServers(clusterDescription, clusterDescription.getAnyPrimaryOrSecondary());
            if (!getTagSetList().isEmpty()) {
                ClusterDescription nonStaleClusterDescription = copyClusterDescription(clusterDescription, selectedServers);
                selectedServers = Collections.emptyList();
                for (final TagSet tagSet : getTagSetList()) {
                    List<ServerDescription> servers = nonStaleClusterDescription.getAnyPrimaryOrSecondary(tagSet);
                    if (!servers.isEmpty()) {
                        selectedServers = servers;
                        break;
                    }
                }
            }
            return selectedServers;
        }
    }

    /**
     * Read from primary if available, otherwise a secondary.
     */
    static class PrimaryPreferredReadPreference extends SecondaryReadPreference {
        PrimaryPreferredReadPreference() {
        }

        PrimaryPreferredReadPreference(final List<TagSet> tagSetList, @Nullable final Long maxStaleness, final TimeUnit timeUnit) {
            super(tagSetList, maxStaleness, timeUnit);
        }

        @Override
        public String getName() {
            return "primaryPreferred";
        }

        @Override
        @SuppressWarnings("deprecation")
        protected List<ServerDescription> chooseForReplicaSet(final ClusterDescription clusterDescription) {
            List<ServerDescription> selectedServers = selectFreshServers(clusterDescription, clusterDescription.getPrimaries());
            if (selectedServers.isEmpty()) {
                selectedServers = super.chooseForReplicaSet(clusterDescription);
            }
            return selectedServers;
        }
    }

    private BsonArray tagsListToBsonArray() {
        BsonArray bsonArray = new BsonArray();
        for (TagSet tagSet : tagSetList) {
            bsonArray.add(toDocument(tagSet));
        }
        return bsonArray;
    }

    private BsonDocument toDocument(final TagSet tagSet) {
        BsonDocument document = new BsonDocument();

        for (Tag tag : tagSet) {
            document.put(tag.getName(), new BsonString(tag.getValue()));
        }

        return document;
    }

}


/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See LICENSE in the project root for
 * license information.
 */

package com.microsoft.azure.spring.autoconfigure.metrics;

import com.microsoft.applicationinsights.TelemetryConfiguration;
import io.micrometer.azuremonitor.AzureMonitorConfig;
import io.micrometer.azuremonitor.AzureMonitorMeterRegistry;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Auto-Configuration for exporting metrics to Azure Application Insights.
 *
 * @author Dhaval Doshi
 */
@Configuration
@AutoConfigureBefore({CompositeMeterRegistryAutoConfiguration.class,
        SimpleMetricsExportAutoConfiguration.class})
@AutoConfigureAfter(MetricsAutoConfiguration.class)
@ConditionalOnBean(Clock.class)
@ConditionalOnClass(AzureMonitorMeterRegistry.class)
@ConditionalOnProperty(prefix = "management.metrics.export.azuremonitor",
        name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(AzureMonitorProperties.class)
public class AzureMonitorMetricsExportAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AzureMonitorConfig azureConfig(AzureMonitorProperties properties) {
        return new AzureMonitorPropertiesConfigAdapter(properties);
    }

    /**
     * This bean is already available when the
     * {@see Azure Application Insights starter}
     * is present.
     */
    @Bean
    @ConditionalOnMissingBean
    public TelemetryConfiguration telemetryConfiguration(AzureMonitorConfig config) {
        // Gets the active instance of TelemetryConfiguration either created by starter or xml
        final TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.getActive();
        if (StringUtils.isEmpty(telemetryConfiguration.getInstrumentationKey())) {
            telemetryConfiguration.setInstrumentationKey(config.instrumentationKey());
        }
        return telemetryConfiguration;
    }

    @Bean
    @ConditionalOnMissingBean
    public AzureMonitorMeterRegistry azureMeterRegistry(AzureMonitorConfig config,
                                                        TelemetryConfiguration configuration, Clock clock) {
        return AzureMonitorMeterRegistry.builder(config)
                .clock(clock)
                .telemetryConfiguration(configuration)
                .build();
    }
}


/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See LICENSE in the project root for
 * license information.
 */

package com.microsoft.azure.spring.support;

public class UserAgent {
    /**
     * Generate UserAgent string for given service.
     *
     * @param serviceName    Name of the service from which called this method.
     * @param allowTelemetry Whether allows telemtry
     * @return generated UserAgent string
     */
    public static String getUserAgent(String serviceName, boolean allowTelemetry) {
        String macAddress = "Not Collected";
        if (allowTelemetry) {
            macAddress = GetHashMac.getHashMac();
        }

        return String.format(serviceName + " MacAddressHash:%s", macAddress);
    }
}


/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See LICENSE in the project root for
 * license information.
 */
package com.microsoft.azure.spring.autoconfigure.b2c;

import lombok.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class AADB2CAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {

    private static final String REQUEST_BASE_URI =
            OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;

    private static final String REGISTRATION_ID_NAME = "registrationId";

    private static final String PARAMETER_X_CLIENT_SKU = "x-client-SKU";

    private static final String AAD_B2C_USER_AGENT = "spring-boot-starter";

    private static final String MATCHER_PATTERN = String.format("%s/{%s}", REQUEST_BASE_URI, REGISTRATION_ID_NAME);

    private static final AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher(MATCHER_PATTERN);

    private final OAuth2AuthorizationRequestResolver defaultResolver;

    private final String passwordResetUserFlow;

    public AADB2CAuthorizationRequestResolver(@NonNull ClientRegistrationRepository repository) {
        this.passwordResetUserFlow = null;
        this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(repository, REQUEST_BASE_URI);
    }

    public AADB2CAuthorizationRequestResolver(@NonNull ClientRegistrationRepository repository,
                                              @Nullable String passwordResetUserFlow) {
        this.passwordResetUserFlow = passwordResetUserFlow;
        this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(repository, REQUEST_BASE_URI);
    }

    @Override
    public OAuth2AuthorizationRequest resolve(@NonNull HttpServletRequest request) {
        return resolve(request, getRegistrationId(request));
    }

    @Override
    public OAuth2AuthorizationRequest resolve(@NonNull HttpServletRequest request, String registrationId) {
        if (StringUtils.hasText(passwordResetUserFlow) && isForgotPasswordAuthorizationRequest(request)) {
            final OAuth2AuthorizationRequest authRequest = defaultResolver.resolve(request, passwordResetUserFlow);
            return getB2CAuthorizationRequest(authRequest, passwordResetUserFlow);
        }

        if (StringUtils.hasText(registrationId) && requestMatcher.matches(request)) {
            return getB2CAuthorizationRequest(defaultResolver.resolve(request), registrationId);
        }

        // Return null may not be the good practice, but we need to align with oauth2.client.web
        // DefaultOAuth2AuthorizationRequestResolver.
        return null;
    }

    private void cleanupSecurityContextAuthentication() {
        SecurityContextHolder.getContext().setAuthentication(null);
    }

    private OAuth2AuthorizationRequest getB2CAuthorizationRequest(@Nullable OAuth2AuthorizationRequest request,
                                                                  String userFlow) {
        Assert.hasText(userFlow, "User flow should contain text.");

        if (request == null) {
            return null;
        }

        cleanupSecurityContextAuthentication();

        final Map<String, Object> parameters = new HashMap<>(request.getAdditionalParameters());

        parameters.put("p", userFlow);
        parameters.put(PARAMETER_X_CLIENT_SKU, AAD_B2C_USER_AGENT);

        return OAuth2AuthorizationRequest.from(request).additionalParameters(parameters).build();
    }

    private String getRegistrationId(HttpServletRequest request) {
        if (requestMatcher.matches(request)) {
            return requestMatcher.extractUriTemplateVariables(request).get(REGISTRATION_ID_NAME);
        }

        return null;
    }

    // Handle the forgot password of sign-up-or-in page cannot redirect user to password-reset page.
    // The B2C service will enhance that, and then related code will be removed.
    private boolean isForgotPasswordAuthorizationRequest(@NonNull HttpServletRequest request) {
        final String error = request.getParameter("error");
        final String description = request.getParameter("error_description");

        if ("access_denied".equals(error)) {
            Assert.hasText(description, "description should contain text.");
            return description.startsWith("AADB2C90118:");
        }

        return false;
    }
}