/*
 * Copyright (c) 1998, 2021 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.testing.tests.types;

import java.util.*;
import java.io.StringWriter;
import org.eclipse.persistence.descriptors.RelationalDescriptor;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.tools.schemaframework.*;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.exceptions.*;

/**
 * Tests the use of Long ({@literal >} 255) strings
 */
public class CLOBTester extends TypeTester {
    public String longString;
    protected int length;

    public CLOBTester() {
        this(0);
    }

    public CLOBTester(int numChars) {
        super("CLOB(" + numChars + ")");
        length = numChars;
        longString = null;
    }

    public static RelationalDescriptor descriptor() {
        RelationalDescriptor descriptor = new RelationalDescriptor();

        /* First define the class, table and descriptor properties. */
        descriptor.setJavaClass(CLOBTester.class);
        descriptor.setTableName("CLOBS");
        descriptor.setPrimaryKeyFieldName("NAME");

        /* Next define the attribute mappings. */
        descriptor.addDirectMapping("testName", "getTestName", "setTestName", "NAME");
        descriptor.addDirectMapping("longString", "THECLOB");
        return descriptor;
    }

    public static RelationalDescriptor descriptorWithAccessors() {
        RelationalDescriptor descriptor = new RelationalDescriptor();

        /* First define the class, table and descriptor properties. */
        descriptor.setJavaClass(CLOBTester.class);
        descriptor.setTableName("CLOBS");
        descriptor.setPrimaryKeyFieldName("NAME");

        /* Next define the attribute mappings. */
        descriptor.addDirectMapping("testName", "getTestName", "setTestName", "NAME");
        descriptor.addDirectMapping("longString", "getLongString", "setLongString", "THECLOB");
        return descriptor;
    }

    public boolean equals(CLOBTester otherTester, Session session) throws TestWarningException {
        if (!getTestName().equals(otherTester.getTestName())) {
            return false;
        }
        if ((!getLongString().equals(otherTester.getLongString())) && !((getLongString().length() == 0) && (otherTester.getLongString() == null))) {
            return false;
        }
        if (otherTester.getLongString() == null) {
            throw new TestWarningException("Empty columns can be returned as NULL.");
        }
        return true;
    }

    public String getLongString() {
        return longString;
    }

    public void setLongString(String newString) {
        longString = newString;
    }

    @Override
    public void setup(Session session) {
        // Access and DB2 do not support BLOBS or CLOBS
        if (session.getPlatform().isAccess()) {
            throw new TestWarningException("Access does not support BLOBS or CLOBS.");
        }

        if (longString == null) {
            String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`1234567890-=\\~!@#$%^&*()_+|[]{};':\",./<>?";

            // \ is always treated as escape and removed from String in MySQL.  Not sure if it is a bug
            if (session.getPlatform().isMySQL()) {
                chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`1234567890-=~!@#$%^&*()_+|[]{};':\",./<>?";
            }
            StringWriter writer = new StringWriter();
            for (int index = 0, charIndex = 0; index < length; index++, charIndex++) {
                if (charIndex == chars.length()) {
                    charIndex = 0;
                }
                writer.write(chars.charAt(charIndex));
            }
            longString = writer.toString();
        }
    }

    /**
     *Return a platform independent definition of the database table.
     */
    public static TableDefinition tableDefinition(Session session) {
        TableDefinition definition = TypeTester.tableDefinition();

        definition.setName("CLOBS");

        definition.addField("THECLOB", Character[].class, 25000);
        return definition;
    }

    public static Vector testInstances() {
        Vector tests = new Vector();

        tests.addElement(new CLOBTester(1000));
        tests.addElement(new CLOBTester(5000));
        tests.addElement(new CLOBTester(10000));
        tests.addElement(new CLOBTester(25000));
        return tests;
    }

    public String toString() {
        return getTestName();
    }

    /**
     * Verify if the objects match completely through allowing the session to use the descriptors.
     * This will compare the objects and all of their privately owned parts.
     */
    @Override
    protected void verify(WriteTypeObjectTest testCase) throws TestErrorException, TestWarningException {
        try {
            super.verify(testCase);
        } catch (TestErrorException verifyFailedException) {
            // Database bridges which do not support long literal values
            if ((caughtException != null) && (testCase.getSession().getPlatform().isOracle() && (((java.sql.SQLException) caughtException.getInternalException()).getErrorCode() == 1704))) {
                throw new TestWarningException("CLOB Write failed. " + caughtException.toString() + "\n Turn on String Binding and re-run test");
            }

            if (testCase.getObjectFromDatabase() == null) {
                throw new TestWarningException("CLOB Write failed.  Object returned as null");
            }
            if (equals((CLOBTester)testCase.getObjectFromDatabase(), testCase.getSession())) {
                return;
            }
            throw verifyFailedException;
        }
    }
}
