// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.ai.formrecognizer.v3;

import com.azure.ai.formrecognizer.FormRecognizerAsyncClient;
import com.azure.ai.formrecognizer.FormRecognizerClient;
import com.azure.ai.formrecognizer.FormRecognizerClientBuilder;
import com.azure.ai.formrecognizer.models.FieldValueType;
import com.azure.ai.formrecognizer.models.FormField;
import com.azure.ai.formrecognizer.models.FormPage;
import com.azure.ai.formrecognizer.models.FormRecognizerOperationResult;
import com.azure.ai.formrecognizer.models.RecognizedForm;
import com.azure.ai.formrecognizer.training.FormTrainingAsyncClient;
import com.azure.ai.formrecognizer.training.FormTrainingClient;
import com.azure.ai.formrecognizer.training.FormTrainingClientBuilder;
import com.azure.ai.formrecognizer.training.models.AccountProperties;
import com.azure.ai.formrecognizer.training.models.CustomFormModel;
import com.azure.ai.formrecognizer.training.models.CustomFormModelInfo;
import com.azure.ai.formrecognizer.training.models.TrainingOptions;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.exception.HttpResponseException;
import com.azure.core.http.rest.PagedIterable;
import com.azure.core.util.Context;
import com.azure.core.util.polling.SyncPoller;
import com.azure.identity.DefaultAzureCredentialBuilder;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;

/**
 * WARNING: MODIFYING THIS FILE WILL REQUIRE CORRESPONDING UPDATES TO README.md FILE. LINE NUMBERS ARE USED TO EXTRACT
 * APPROPRIATE CODE SEGMENTS FROM THIS FILE. ADD NEW CODE AT THE BOTTOM TO AVOID CHANGING LINE NUMBERS OF EXISTING CODE
 * SAMPLES.
 * <p>
 * Class containing code snippets that will be injected to README.md.
 */
public class ReadmeSamples {
    private final FormRecognizerClient formRecognizerClient = new FormRecognizerClientBuilder().buildClient();
    private final FormTrainingClient formTrainingClient = new FormTrainingClientBuilder().buildClient();

    /**
     * Code snippet for getting sync client using the AzureKeyCredential authentication.
     */
    public void useAzureKeyCredentialClient() {
        // BEGIN: readme-sample-createFormRecognizerClient
        FormRecognizerClient formRecognizerClient = new FormRecognizerClientBuilder()
            .credential(new AzureKeyCredential("{key}"))
            .endpoint("{endpoint}")
            .buildClient();
        //END: readme-sample-createFormRecognizerClient
    }

    public void useAzureKeyCredentialAsyncClient() {
        // BEGIN: readme-sample-createFormRecognizerAsyncClient
        FormRecognizerAsyncClient formRecognizerAsyncClient = new FormRecognizerClientBuilder()
            .credential(new AzureKeyCredential("{key}"))
            .endpoint("{endpoint}")
            .buildAsyncClient();
        //END: readme-sample-createFormRecognizerAsyncClient
    }

    /**
     * Code snippet for getting sync FormTraining client using the AzureKeyCredential authentication.
     */
    public void useAzureKeyCredentialFormTrainingClient() {
        // BEGIN: readme-sample-createFormTrainingClient
        FormTrainingClient formTrainingClient = new FormTrainingClientBuilder()
            .credential(new AzureKeyCredential("{key}"))
            .endpoint("{endpoint}")
            .buildClient();
        //END: readme-sample-createFormTrainingClient
    }

    public void useAzureKeyCredentialFormTrainingAsyncClient() {
        // BEGIN: readme-sample-createFormTrainingAsyncClient
        FormTrainingAsyncClient formTrainingAsyncClient = new FormTrainingClientBuilder()
            .credential(new AzureKeyCredential("{key}"))
            .endpoint("{endpoint}")
            .buildAsyncClient();
        //END: readme-sample-createFormTrainingAsyncClient
    }

    /**
     * Code snippet for rotating AzureKeyCredential of the client
     */
    public void rotatingAzureKeyCredential() {
        AzureKeyCredential credential = new AzureKeyCredential("{key}");
        FormRecognizerClient formRecognizerClient = new FormRecognizerClientBuilder()
            .credential(credential)
            .endpoint("{endpoint}")
            .buildClient();

        credential.update("{new_key}");
    }

    /**
     * Code snippet for getting async client using AAD authentication.
     */
    public void useAadSyncClient() {
        // BEGIN: readme-sample-createFormRecognizerClientWithAAD
        FormRecognizerClient formRecognizerClient = new FormRecognizerClientBuilder()
            .endpoint("{endpoint}")
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildClient();
        // END: readme-sample-createFormRecognizerClientWithAAD
    }

    public void useAadAsyncClient() {
        // BEGIN: readme-sample-createFormRecognizerAsyncClientWithAAD
        FormRecognizerAsyncClient formRecognizerAsyncClient = new FormRecognizerClientBuilder()
            .endpoint("{endpoint}")
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildAsyncClient();
        // END: readme-sample-createFormRecognizerAsyncClientWithAAD
    }

    /**
     * Code snippet for getting async client using AAD authentication.
     */
    public void useAadFormTrainingClient() {
        // BEGIN: readme-sample-createFormTrainingClientWithAAD
        FormTrainingClient client = new FormTrainingClientBuilder()
            .endpoint("{endpoint}")
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildClient();
        // END: readme-sample-createFormTrainingClientWithAAD
    }

    public void useAadFormTrainingAsyncClient() {
        // BEGIN: readme-sample-createFormTrainingAsyncClientWithAAD
        FormTrainingAsyncClient client = new FormTrainingClientBuilder()
            .endpoint("{endpoint}")
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildAsyncClient();
        // END: readme-sample-createFormTrainingAsyncClientWithAAD
    }

    /**
     * Code snippet for recognizing custom forms using custom built models.
     */
    public void recognizeCustomForm() {
        String formUrl = "{form_url}";
        String modelId = "{custom_trained_model_id}";
        SyncPoller<FormRecognizerOperationResult, List<RecognizedForm>> recognizeFormPoller =
            formRecognizerClient.beginRecognizeCustomFormsFromUrl(modelId, formUrl);

        List<RecognizedForm> recognizedForms = recognizeFormPoller.getFinalResult();

        for (int i = 0; i < recognizedForms.size(); i++) {
            RecognizedForm form = recognizedForms.get(i);
            System.out.printf("----------- Recognized custom form info for page %d -----------%n", i);
            System.out.printf("Form type: %s%n", form.getFormType());
            System.out.printf("Form type confidence: %.2f%n", form.getFormTypeConfidence());
            form.getFields().forEach((label, formField) ->
                System.out.printf("Field %s has value %s with confidence score of %f.%n", label,
                    formField.getValueData().getText(),
                    formField.getConfidence())
            );
        }
    }

    /**
     * Recognize content/layout data for provided form.
     *
     * @throws IOException Exception thrown when there is an error in reading all the bytes from the File.
     */
    public void recognizeContent() throws IOException {
        // recognize form content using file input stream
        File form = new File("local/file_path/filename.png");
        byte[] fileContent = Files.readAllBytes(form.toPath());
        InputStream inputStream = new ByteArrayInputStream(fileContent);

        SyncPoller<FormRecognizerOperationResult, List<FormPage>> recognizeContentPoller =
            formRecognizerClient.beginRecognizeContent(inputStream, form.length());

        List<FormPage> contentPageResults = recognizeContentPoller.getFinalResult();

        for (int i = 0; i < contentPageResults.size(); i++) {
            FormPage formPage = contentPageResults.get(i);
            System.out.printf("----Recognizing content info for page %d ----%n", i);
            // Table information
            System.out.printf("Has width: %f and height: %f, measured with unit: %s.%n", formPage.getWidth(),
                formPage.getHeight(),
                formPage.getUnit());
            formPage.getTables().forEach(formTable -> {
                System.out.printf("Table has %d rows and %d columns.%n", formTable.getRowCount(),
                    formTable.getColumnCount());
                formTable.getCells().forEach(formTableCell ->
                    System.out.printf("Cell has text %s.%n", formTableCell.getText()));
            });
            // Selection Mark
            formPage.getSelectionMarks().forEach(selectionMark -> System.out.printf(
                "Page: %s, Selection mark is %s within bounding box %s has a confidence score %.2f.%n",
                selectionMark.getPageNumber(), selectionMark.getState(), selectionMark.getBoundingBox().toString(),
                selectionMark.getConfidence()));
        }
    }

    /**
     * Code snippet for recognizing receipt data using prebuilt receipt models.
     */
    public void recognizeReceiptFromUrl() {
        // BEGIN: readme-sample-recognize-receipt-url
        String receiptUrl = "https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/formrecognizer"
                + "/azure-ai-formrecognizer/src/samples/resources/sample-forms/receipts/contoso-allinone.jpg";
        SyncPoller<FormRecognizerOperationResult, List<RecognizedForm>> syncPoller =
            formRecognizerClient.beginRecognizeReceiptsFromUrl(receiptUrl);
        List<RecognizedForm> receiptPageResults = syncPoller.getFinalResult();

        for (int i = 0; i < receiptPageResults.size(); i++) {
            RecognizedForm recognizedForm = receiptPageResults.get(i);
            Map<String, FormField> recognizedFields = recognizedForm.getFields();
            System.out.printf("----------- Recognizing receipt info for page %d -----------%n", i);
            FormField merchantNameField = recognizedFields.get("MerchantName");
            if (merchantNameField != null) {
                if (FieldValueType.STRING == merchantNameField.getValue().getValueType()) {
                    String merchantName = merchantNameField.getValue().asString();
                    System.out.printf("Merchant Name: %s, confidence: %.2f%n",
                        merchantName, merchantNameField.getConfidence());
                }
            }

            FormField merchantPhoneNumberField = recognizedFields.get("MerchantPhoneNumber");
            if (merchantPhoneNumberField != null) {
                if (FieldValueType.PHONE_NUMBER == merchantPhoneNumberField.getValue().getValueType()) {
                    String merchantAddress = merchantPhoneNumberField.getValue().asPhoneNumber();
                    System.out.printf("Merchant Phone number: %s, confidence: %.2f%n",
                        merchantAddress, merchantPhoneNumberField.getConfidence());
                }
            }

            FormField transactionDateField = recognizedFields.get("TransactionDate");
            if (transactionDateField != null) {
                if (FieldValueType.DATE == transactionDateField.getValue().getValueType()) {
                    LocalDate transactionDate = transactionDateField.getValue().asDate();
                    System.out.printf("Transaction Date: %s, confidence: %.2f%n",
                        transactionDate, transactionDateField.getConfidence());
                }
            }

            FormField receiptItemsField = recognizedFields.get("Items");
            if (receiptItemsField != null) {
                System.out.printf("Receipt Items: %n");
                if (FieldValueType.LIST == receiptItemsField.getValue().getValueType()) {
                    List<FormField> receiptItems = receiptItemsField.getValue().asList();
                    receiptItems.stream()
                        .filter(receiptItem -> FieldValueType.MAP == receiptItem.getValue().getValueType())
                        .map(formField -> formField.getValue().asMap())
                        .forEach(formFieldMap -> formFieldMap.forEach((key, formField) -> {
                            if ("Quantity".equals(key)) {
                                if (FieldValueType.FLOAT == formField.getValue().getValueType()) {
                                    Float quantity = formField.getValue().asFloat();
                                    System.out.printf("Quantity: %f, confidence: %.2f%n",
                                        quantity, formField.getConfidence());
                                }
                            }
                        }));
                }
            }
        }
        // END: readme-sample-recognize-receipt-url
    }

    /**
     * Code snippet for recognizing invoice forms using prebuilt models.
     */
    public void recognizeBusinessCardFromUrl() {
        String businessCardUrl =
            "https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/formrecognizer"
                + "/azure-ai-formrecognizer/src/samples/resources/sample-forms/businessCards/businessCard.jpg";

        SyncPoller<FormRecognizerOperationResult, List<RecognizedForm>> analyzeBusinessCardPoller =
            formRecognizerClient.beginRecognizeBusinessCardsFromUrl(businessCardUrl);

        List<RecognizedForm> businessCardPageResults = analyzeBusinessCardPoller.getFinalResult();

        for (int i = 0; i < businessCardPageResults.size(); i++) {
            RecognizedForm recognizedForm = businessCardPageResults.get(i);
            Map<String, FormField> recognizedFields = recognizedForm.getFields();
            System.out.printf("----------- Recognized business card info for page %d -----------%n", i);
            FormField contactNamesFormField = recognizedFields.get("ContactNames");
            if (contactNamesFormField != null) {
                if (FieldValueType.LIST == contactNamesFormField.getValue().getValueType()) {
                    List<FormField> contactNamesList = contactNamesFormField.getValue().asList();
                    contactNamesList.stream()
                        .filter(contactName -> FieldValueType.MAP == contactName.getValue().getValueType())
                        .map(contactName -> {
                            System.out.printf("Contact name: %s%n", contactName.getValueData().getText());
                            return contactName.getValue().asMap();
                        })
                        .forEach(contactNamesMap -> contactNamesMap.forEach((key, contactName) -> {
                            if ("FirstName".equals(key)) {
                                if (FieldValueType.STRING == contactName.getValue().getValueType()) {
                                    String firstName = contactName.getValue().asString();
                                    System.out.printf("\tFirst Name: %s, confidence: %.2f%n",
                                        firstName, contactName.getConfidence());
                                }
                            }
                            if ("LastName".equals(key)) {
                                if (FieldValueType.STRING == contactName.getValue().getValueType()) {
                                    String lastName = contactName.getValue().asString();
                                    System.out.printf("\tLast Name: %s, confidence: %.2f%n",
                                        lastName, contactName.getConfidence());
                                }
                            }
                        }));
                }
            }
            FormField jobTitles = recognizedFields.get("JobTitles");
            if (jobTitles != null) {
                if (FieldValueType.LIST == jobTitles.getValue().getValueType()) {
                    List<FormField> jobTitlesItems = jobTitles.getValue().asList();
                    jobTitlesItems.forEach(jobTitlesItem -> {
                        if (FieldValueType.STRING == jobTitlesItem.getValue().getValueType()) {
                            String jobTitle = jobTitlesItem.getValue().asString();
                            System.out.printf("Job Title: %s, confidence: %.2f%n",
                                jobTitle, jobTitlesItem.getConfidence());
                        }
                    });
                }
            }
        }
    }

    /**
     * Code snippet for creating custom models using training data.
     */
    public void trainModel() {
        // BEGIN: readme-sample-train-model
        String trainingFilesUrl = "{SAS_URL_of_your_container_in_blob_storage}";
        SyncPoller<FormRecognizerOperationResult, CustomFormModel> trainingPoller =
            formTrainingClient.beginTraining(trainingFilesUrl,
                false,
                new TrainingOptions()
                    .setModelName("my model trained without labels"),
                Context.NONE);

        CustomFormModel customFormModel = trainingPoller.getFinalResult();

        // Model Info
        System.out.printf("Model Id: %s%n", customFormModel.getModelId());
        System.out.printf("Model name given by user: %s%n", customFormModel.getModelName());
        System.out.printf("Model Status: %s%n", customFormModel.getModelStatus());
        System.out.printf("Training started on: %s%n", customFormModel.getTrainingStartedOn());
        System.out.printf("Training completed on: %s%n%n", customFormModel.getTrainingCompletedOn());

        System.out.println("Recognized Fields:");
        // looping through the subModels, which contains the fields they were trained on
        // Since the given training documents are unlabeled, we still group them but they do not have a label.
        customFormModel.getSubmodels().forEach(customFormSubmodel -> {
            System.out.printf("Submodel Id: %s%n: ", customFormSubmodel.getModelId());
            // Since the training data is unlabeled, we are unable to return the accuracy of this model
            customFormSubmodel.getFields().forEach((field, customFormModelField) ->
                System.out.printf("Field: %s Field Label: %s%n",
                    field, customFormModelField.getLabel()));
        });
        // END: readme-sample-train-model
    }

    /**
     * Code snippet for managing models in form recognizer account.
     */
    public void manageModels() {
        // BEGIN: readme-sample-manage-models
        // First, we see how many custom models we have, and what our limit is
        AccountProperties accountProperties = formTrainingClient.getAccountProperties();
        System.out.printf("The account has %d custom models, and we can have at most %d custom models",
            accountProperties.getCustomModelCount(), accountProperties.getCustomModelLimit());

        // Next, we get a paged list of all of our custom models
        PagedIterable<CustomFormModelInfo> customModels = formTrainingClient.listCustomModels();
        System.out.println("We have following models in the account:");
        customModels.forEach(customFormModelInfo -> {
            System.out.printf("Model Id: %s%n", customFormModelInfo.getModelId());
            // get specific custom model info
            CustomFormModel customModel = formTrainingClient.getCustomModel(customFormModelInfo.getModelId());
            System.out.printf("Model Status: %s%n", customModel.getModelStatus());
            System.out.printf("Training started on: %s%n", customModel.getTrainingStartedOn());
            System.out.printf("Training completed on: %s%n", customModel.getTrainingCompletedOn());
            customModel.getSubmodels().forEach(customFormSubmodel -> {
                System.out.printf("Custom Model Form type: %s%n", customFormSubmodel.getFormType());
                System.out.printf("Custom Model Accuracy: %f%n", customFormSubmodel.getAccuracy());
                if (customFormSubmodel.getFields() != null) {
                    customFormSubmodel.getFields().forEach((fieldText, customFormModelField) -> {
                        System.out.printf("Field Text: %s%n", fieldText);
                        System.out.printf("Field Accuracy: %f%n", customFormModelField.getAccuracy());
                    });
                }
            });
        });

        // Delete Custom Model
        formTrainingClient.deleteModel("{modelId}");
        // END: readme-sample-manage-models
    }

    /**
     * Code snippet for handling exception
     */
    public void handlingException() {
        try {
            formRecognizerClient.beginRecognizeContentFromUrl("invalidSourceUrl");
        } catch (HttpResponseException e) {
            System.out.println(e.getMessage());
        }
    }

    public void trainAndRecognizeCustomForm() {
        // BEGIN: readme-sample-train-recognize-custom-forms
        String trainingFilesUrl = "{SAS_URL_of_your_container_in_blob_storage}";
        boolean useTrainingLabels = true;

        SyncPoller<FormRecognizerOperationResult, CustomFormModel> trainingPoller =
            formTrainingClient.beginTraining(trainingFilesUrl,
                useTrainingLabels,
                new TrainingOptions()
                    .setModelName("my model trained with labels"),
                Context.NONE);

        CustomFormModel customFormModel = trainingPoller.getFinalResult();

        // Model Info
        System.out.printf("Model Id: %s%n", customFormModel.getModelId());

        String customFormUrl = "customFormUrl";
        String modelId = customFormModel.getModelId();
        SyncPoller<FormRecognizerOperationResult, List<RecognizedForm>> recognizeFormPoller =
            formRecognizerClient.beginRecognizeCustomFormsFromUrl(modelId, customFormUrl);

        List<RecognizedForm> recognizedForms = recognizeFormPoller.getFinalResult();

        for (int i = 0; i < recognizedForms.size(); i++) {
            RecognizedForm form = recognizedForms.get(i);
            System.out.printf("----------- Recognized custom form info for page %d -----------%n", i);
            System.out.printf("Form type: %s%n", form.getFormType());
            System.out.printf("Form type confidence: %.2f%n", form.getFormTypeConfidence());
            form.getFields().forEach((label, formField) ->
                System.out.printf("Field %s has value %s with confidence score of %f.%n", label,
                    formField.getValueData().getText(),
                    formField.getConfidence())
            );
        }
        // END: readme-sample-train-recognize-custom-forms
    }
}
