/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 */

import { QueryOperation } from "./enums";
import { QueryObject } from "./interfaces";

export default class AGCCloudDBQuery {
    private readonly query: QueryObject;

    private constructor(className: string) {
        this.query = {
            className: className,
            queryElements: [],
        }
    }

    /**
     * Obtains a AGCCloudDBQuery object and specifies the object type to be queried. 
     * The AGCCloudDBQuery object does not provide any construction method. 
     * You can obtain an object type instance only by using the where method.
     * @param className Cloud DB zone name
     */
    static where(className: string) {
        return new AGCCloudDBQuery(className);
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is equal to a specified value.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    equalTo(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.EQUAL_TO,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is not equal to a specified value.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    notEqualTo(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.NOT_EQUAL_TO,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is greater than a specified value.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    greaterThan(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.GREATER_THAN,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is greater than or equal to a specified value.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    greaterThanOrEqualTo(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.GREATER_THAN_OR_EQUAL_TO,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is less than a specified value.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    lessThan(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.LESS_THAN,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is less than or equal to a specified value.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    lessThanOrEqualTo(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.LESS_THAN_OR_EQUAL_TO,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field in an entity class 
     * is contained in a specified array.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    in(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.IN,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field of the string or 
     * text type in an entity class starts with a specified substring.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    beginsWith(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.BEGINS_WITH,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field of the string or 
     * text type in an entity class ends with a specified substring.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    endsWith(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.ENDS_WITH,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where the value of a field of the string 
     * or text type in an entity class contains a specified substring.
     * @param fieldName Name of a field in an entity class.
     * @param value Specified value of the selected field. 
     * The data type of the value must be the same as that of the selected field value. 
     * Otherwise, an exception is thrown.
     */
    contains(fieldName: string, value: any) {
        this.query.queryElements.push({
            operation: QueryOperation.CONTAINS,
            fieldName: fieldName,
            value: value,
        })
        return this
    }

    /**
     * Adds a query condition where a field in an entity class is null.
     * @param fieldName Name of a field in an entity class.
     */
    isNull(fieldName: string) {
        this.query.queryElements.push({
            operation: QueryOperation.IS_NULL,
            fieldName: fieldName,
        })
        return this
    }

    /**
     * Adds a query condition where a field in an entity class is not null.
     * @param fieldName Name of a field in an entity class.
     */
    isNotNull(fieldName: string) {
        this.query.queryElements.push({
            operation: QueryOperation.IS_NOT_NULL,
            fieldName: fieldName,
        })
        return this
    }

    /**
     * Sorts the query results in ascending order by a specified field.
     * @param fieldName Name of a field in an entity class.
     */
    orderByAsc(fieldName: string) {
        this.query.queryElements.push({
            operation: QueryOperation.ORDER_BY_ASC,
            fieldName: fieldName,
        })
        return this
    }

    /**
     * Sorts the query results in descending order by a specified field.
     * @param fieldName Name of a field in an entity class.
     */
    orderByDesc(fieldName: string) {
        this.query.queryElements.push({
            operation: QueryOperation.ORDER_BY_DESC,
            fieldName: fieldName,
        })
        return this
    }

    /**
     * Add query conditions to specify the number of 
     * data records in the returned query result set.
     * @param count Maximum number of data records that can be obtained.
     * @param offset Specifies the start position of data records. 
     * You can specify whether to set the offset position based on the actual requirements.
     */
    limit(count: any, offset?: number) {
        if (offset) {
            this.query.queryElements.push({
                operation: QueryOperation.LIMIT_CONDITION,
                value: count,
                offset: offset
            })
            return this
        } else {
            this.query.queryElements.push({
                operation: QueryOperation.LIMIT_CONDITION,
                value: count,
            })
            return this
        }
    }

    /**
     * Set the start position of the data to be queried, and return to 
     * the data records with the corresponding records at the start position included.
     * @param object The object corresponding to the start position.
     */
    startAt(object: any) {
        this.query.queryElements.push({
            operation: QueryOperation.START_AT,
            value: object
        })
        return this
    }

    /**
     * Set the start position of the data to be queried, and return to 
     * the data records with the corresponding records at the start position not included.
     * @param object The object corresponding to the start position.
     */
    startAfter(object: any) {
        this.query.queryElements.push({
            operation: QueryOperation.START_AFTER,
            value: object
        })
        return this
    }

    /**
     * Set the end position of the data to be queried, and return to 
     * the data records with the corresponding records at the end position included.
     * @param object The object corresponding to the start position.
     */
    endAt(object: any) {
        this.query.queryElements.push({
            operation: QueryOperation.END_AT,
            value: object
        })
        return this
    }

    /**
     * Set the end position of the data to be queried, and return to 
     * the data records with the corresponding records at the end position not included.
     * @param object The object corresponding to the start position.
     */
    endBefore(object: any) {
        this.query.queryElements.push({
            operation: QueryOperation.END_BEFORE,
            value: object
        })
        return this
    }

    /**
     * This method is used to build the query. 
     * This method has to be called after query chain has been completed.
     */
    build() {
        Object.freeze(this);
        return this.query;
    }
}
