/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.sessions;

import java.io.StringWriter;
import java.util.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.internal.helper.DatabaseField;

/**
 * PERF: Optimized record implementation using arrays instead of Vector.
 * Currently only used when fetch rows from the database.
 */
public class ArrayRecord extends DatabaseRecord {
    protected DatabaseField[] fieldsArray;
    protected Object[] valuesArray;

    protected ArrayRecord() {
        super();
    }

    public ArrayRecord(Vector fields, DatabaseField[] fieldsArray, Object[] valuesArray) {
        super(fields, null, fieldsArray.length);
        this.fieldsArray = fieldsArray;
        this.valuesArray = valuesArray;
    }

    /**
     * Reset the fields and values from the arrays.
     * This removes the optimization if a non-optimized method is called.
     */
    protected void checkValues() {
        if (this.values == null) {
            this.values = new NonSynchronizedVector(this.valuesArray.length);
            for (Object value : this.valuesArray) {
                this.values.add(value);
            }
        }
    }

    /**
     * INTERNAL:
     * Add the field-value pair to the row.  Will not check,
     * will simply add to the end of the row
     */
    public void add(DatabaseField key, Object value) {
        checkValues();
        this.fieldsArray = null;
        this.valuesArray = null;
        super.add(key, value);
    }

    /**
     * PUBLIC:
     * Clear the contents of the row.
     */
    public void clear() {
        this.fieldsArray = null;
        this.valuesArray = null;
        super.clear();
    }

    /**
     * INTERNAL:
     * Clone the row and its values.
     */
    public AbstractRecord clone() {
        checkValues();
        return super.clone();
    }

    /**
     * INTERNAL:
     * Check if the field is contained in the row.
     */
    public boolean containsKey(DatabaseField key) {
        if (this.fieldsArray != null) {
            // Optimize check.
            int index = key.index;
            if ((index >= 0) && (index < this.size)) {
                DatabaseField field = this.fieldsArray[index];
                if ((field == key) || field.equals(key)) {
                    return true;
                }
            }
            for (DatabaseField field : this.fieldsArray) {
                if ((field == key) || field.equals(key)) {
                    return true;
                }
            }
            return false;
        } else {
            return super.containsKey(key);
        }
    }

    /**
     * PUBLIC:
     * Check if the value is contained in the row.
     */
    public boolean containsValue(Object value) {
        if (this.valuesArray != null) {
            for (Object rowValue : this.valuesArray) {
                if ((value == rowValue) || rowValue.equals(value)) {
                    return true;
                }
            }
            return false;
        } else {
            return super.containsValue(value);
        }
    }

    /**
     * INTERNAL:
     * Retrieve the value for the field. If missing null is returned.
     */
    public Object get(DatabaseField key) {
        if (this.fieldsArray != null) {
            // Optimize check.
            int index = key.index;
            if ((index >= 0) && (index < this.size)) {
                DatabaseField field = this.fieldsArray[index];
                if ((field == key) || field.equals(key)) {
                    return this.valuesArray[index];
                }
            }
            for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) {
                DatabaseField field = this.fieldsArray[fieldIndex];
                if ((field == key) || field.equals(key)) {
                    // PERF: If the fields index was not set, then set it.
                    if (index == -1) {
                        key.setIndex(fieldIndex);
                    }
                    return this.valuesArray[fieldIndex];
                }
            }
            return null;
        } else {
            return super.get(key);
        }
    }

    /**
     * INTERNAL:
     * Retrieve the value for the field. If missing DatabaseRow.noEntry is returned.
     */
    public Object getIndicatingNoEntry(DatabaseField key) {
        if (this.fieldsArray != null) {
            // Optimize check.
            int index = key.index;
            if ((index >= 0) && (index < this.size)) {
                DatabaseField field = this.fieldsArray[index];
                if ((field == key) || field.equals(key)) {
                    return this.valuesArray[index];
                }
            }
            for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) {
                DatabaseField field = this.fieldsArray[fieldIndex];
                if ((field == key) || field.equals(key)) {
                    // PERF: If the fields index was not set, then set it.
                    if (index == -1) {
                        key.setIndex(fieldIndex);
                    }
                    return this.valuesArray[fieldIndex];
                }
            }
            return AbstractRecord.noEntry;
        } else {
            return super.get(key);
        }
    }

    /**
     * INTERNAL:
     * Returns the row's field with the same name.
     */
    public DatabaseField getField(DatabaseField key) {
        if (this.fieldsArray != null) {
            // Optimize check.
            int index = key.index;
            if ((index >= 0) && (index < this.size)) {
                DatabaseField field = this.fieldsArray[index];
                if ((field == key) || field.equals(key)) {
                    return field;
                }
            }
            for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) {
                DatabaseField field = this.fieldsArray[fieldIndex];
                if ((field == key) || field.equals(key)) {
                    return field;
                }
            }
            return null;
        } else {
            return super.getField(key);
        }
    }

    /**
     * INTERNAL:
     */
    public Vector getFields() {
        checkValues();
        return super.getFields();
    }

    /**
     * INTERNAL:
     */
    public Vector getValues() {
        checkValues();
        return super.getValues();
    }

    /**
     * INTERNAL:
     * Add the field-value pair to the row.
     */
    public Object put(DatabaseField key, Object value) {
        checkValues();
        this.fieldsArray = null;
        this.valuesArray = null;
        return super.put(key, value);
    }

    /**
     * INTERNAL:
     * Remove the field key from the row.
     */
    public Object remove(DatabaseField key) {
        checkValues();
        this.fieldsArray = null;
        this.valuesArray = null;
        return super.remove(key);
    }

    /**
     * INTERNAL:
     * replaces the value at index with value
     */
    public void replaceAt(Object value, int index) {
        if (this.valuesArray != null) {
            this.valuesArray[index] = value;
        } else {
            super.replaceAt(value, index);
        }
    }

    /**
     * INTERNAL:
     * replaces the value at field with value
     */
    public void replaceAt(Object value, DatabaseField key) {
        if (this.fieldsArray != null) {
            // Optimize check.
            int index = key.index;
            if ((index >= 0) && (index < this.size)) {
                DatabaseField field = this.fieldsArray[index];
                if ((field == key) || field.equals(key)) {
                    this.valuesArray[index] = value;
                    return;
                }
            }
            for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) {
                DatabaseField field = this.fieldsArray[fieldIndex];
                if ((field == key) || field.equals(key)) {
                    // PERF: If the fields index was not set, then set it.
                    if (index == -1) {
                        key.setIndex(fieldIndex);
                    }
                    this.valuesArray[fieldIndex] = value;
                    return;
                }
            }
        } else {
            super.replaceAt(value, key);
        }
    }

    protected void setFields(Vector fields) {
        checkValues();
        this.fieldsArray = null;
        this.valuesArray = null;
        super.setFields(fields);
    }

    protected void setValues(Vector values) {
        checkValues();
        this.fieldsArray = null;
        this.valuesArray = null;
        super.setValues(values);
    }

    /**
     * PUBLIC:
     * Return the number of field/value pairs in the row.
     */
    public int size() {
        if (this.fieldsArray == null) {
            return this.fields.size();
        } else {
            return this.fieldsArray.length;
        }
    }

    @Override
    public String toString() {
        if (this.valuesArray != null) {
            StringWriter writer = new StringWriter();
            writer.write(Helper.getShortClassName(getClass()));
            writer.write("(");
            writer.write(toStringAditional());
            for (int index = 0; index < this.fieldsArray.length; index++) {
                writer.write(Helper.cr());
                writer.write("\t");
                writer.write(String.valueOf(this.fieldsArray[index]));
                writer.write(" => ");
                writer.write(String.valueOf(this.valuesArray[index]));
            }
            if (this.sopObject != null) {
                writer.write(Helper.cr());
                writer.write(" sopObject = ");
                writer.write(this.sopObject.toString());
            }
            writer.write(")");

            return writer.toString();
        } else {
            return super.toString();
        }
    }

    protected String toStringAditional() {
        return "";
    }
}
