/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.openjpa.jdbc.schema;

import java.io.Serializable;
import java.util.Map;
import java.util.TreeMap;

import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;

/**
 * Represents a database schema.
 *
 * @author Abe White
 */
public class Schema
    implements Comparable<Schema>, Serializable {

    private static final long serialVersionUID = 1L;
    private DBIdentifier _name = DBIdentifier.NULL;
    private SchemaGroup _group = null;
    private Map<DBIdentifier, Table> _tableMap = null;
    private Map<DBIdentifier, Sequence> _seqMap = null;

    // cache
    private Table[] _tables = null;
    private Sequence[] _seqs = null;

    /**
     * Default constructor.
     */
    public Schema() {
    }

    /**
     * Constructor.
     *
     * @param name the schema name, if any
     * @param group the schema's owning group
     * @deprecated
     */
    @Deprecated
    public Schema(String name, SchemaGroup group) {
        this(DBIdentifier.newSchema(name), group);
    }

    public Schema(DBIdentifier name, SchemaGroup group) {
        setIdentifier(name);
        _group = group;
    }

    /**
     * Called when the schema is removed from its group. Invalidates the
     * schema and removes all its member tables.
     */
    void remove() {
        Table[] tabs = getTables();
        for (Table tab : tabs) {
            removeTable(tab);
        }
        Sequence[] seqs = getSequences();
        for (Sequence seq : seqs) {
            removeSequence(seq);
        }
        _group = null;
    }

    /**
     * Return the schema's group.
     */
    public SchemaGroup getSchemaGroup() {
        return _group;
    }

    /**
     * Return the name of the schema, or null if none.
     * @deprecated
     */
    @Deprecated
    public String getName() {
        return getIdentifier().getName();
    }

    public DBIdentifier getIdentifier() {
        return _name;
    }

    /**
     * Set the name of the schema. This method can only be used for schemas
     * not attached to a group.
     * @deprecated
     */
    @Deprecated
    public void setName(String name) {
        setIdentifier(DBIdentifier.trimToNull(DBIdentifier.newSchema(name)));
    }

    public void setIdentifier(DBIdentifier name) {
        if (getSchemaGroup() != null)
            throw new IllegalStateException();
        _name = DBIdentifier.trimToNull(name);
    }

    /**
     * Return the schema's tables.
     */
    public Table[] getTables() {
        if (_tables == null)
            _tables = (_tableMap == null) ? new Table[0] : (Table[])
                _tableMap.values().toArray(new Table[_tableMap.size()]);
        return _tables;
    }

    /**
     * Return the table with the given name, or null if none.
     * @deprecated
     */
    @Deprecated
    public Table getTable(String name) {
        if (name == null || _tableMap == null)
            return null;
        return getTable(DBIdentifier.newIdentifier(name, DBIdentifierType.TABLE, true));
    }

    public Table getTable(DBIdentifier name) {
        if (DBIdentifier.isNull(name) || _tableMap == null)
            return null;
        DBIdentifier sName = DBIdentifier.toUpper(name);
        return _tableMap.get(sName);
    }

    /**
     * Add a table to the schema.
     * @deprecated
     */
    @Deprecated
    public Table addTable(String name) {
        return addTable(DBIdentifier.newTable(name));
    }

    public Table addTable(DBIdentifier name) {
        SchemaGroup group = getSchemaGroup();
        Table tab;
        name = name.getUnqualifiedName();
        if (group != null) {
            group.addName(name, true);
            tab = group.newTable(name, this);
        } else
            tab = new Table(name, this);
        if (_tableMap == null)
            _tableMap = new TreeMap<>();
        DBIdentifier sName = DBIdentifier.toUpper(name);
        _tableMap.put(sName, tab);
        _tables = null;
        return tab;
    }


    /**
     * Add a table with a shortened (i.e., validated) name to the schema
     * @deprecated
     */
    @Deprecated
    public Table addTable(String name, String validName) {
        return addTable(DBIdentifier.newTable(name), DBIdentifier.newTable(validName));
    }

    public Table addTable(DBIdentifier name, DBIdentifier validName) {
        SchemaGroup group = getSchemaGroup();
        Table tab;
        if (group != null) {
            group.addName(validName, true);
            tab = group.newTable(validName, this);
        } else
            tab = new Table(validName, this);
        if (_tableMap == null)
            _tableMap = new TreeMap<>();
        DBIdentifier sName = DBIdentifier.toUpper(name);
        _tableMap.put(sName, tab);
        _tables = null;
        return tab;
    }

    /**
     * Remove the given table from the schema.
     *
     * @return true if the table was removed, false if not in the schema
     */
    public boolean removeTable(Table tab) {
        if (tab == null || _tableMap == null)
            return false;

        DBIdentifier sName = DBIdentifier.toUpper(tab.getIdentifier());
        Table cur = _tableMap.get(sName);
        if (!cur.equals(tab))
            return false;

        _tableMap.remove(sName);
        _tables = null;
        SchemaGroup group = getSchemaGroup();
        if (group != null)
            group.removeName(tab.getIdentifier());
        tab.remove();
        return true;
    }

    /**
     * Import a table from another schema.	 Note that this method does
     * <strong>not</strong> import foreign keys, indexes, or unique constraints.
     */
    public Table importTable(Table table) {
        if (table == null)
            return null;

        Table copy = addTable(table.getIdentifier());
        Column[] cols = table.getColumns();
        for (Column col : cols) {
            copy.importColumn(col);
        }

        copy.importPrimaryKey(table.getPrimaryKey());
        return copy;
    }

    /**
     * Return the schema's sequences.
     */
    public Sequence[] getSequences() {
        if (_seqs == null)
            _seqs = (_seqMap == null) ? new Sequence[0] : (Sequence[])
                _seqMap.values().toArray(new Sequence[_seqMap.size()]);
        return _seqs;
    }

    /**
     * Return the sequence with the given name, or null if none.
     * @deprecated
     */
    @Deprecated
    public Sequence getSequence(String name) {
        if (name == null || _seqMap == null)
            return null;
        return getSequence(DBIdentifier.newIdentifier(name, DBIdentifierType.SEQUENCE, true));
    }

    public Sequence getSequence(DBIdentifier name) {
        if (DBIdentifier.isNull(name) || _seqMap == null)
            return null;

        DBIdentifier sName = DBIdentifier.toUpper(name);
        Sequence seq = _seqMap.get(sName);
        return seq;
    }

    /**
     * Add a sequence to the schema.
     * @deprecated
     */
    @Deprecated
    public Sequence addSequence(String name) {
        return addSequence(DBIdentifier.newIdentifier(name, DBIdentifierType.SEQUENCE, true));
    }

    public Sequence addSequence(DBIdentifier name) {
        SchemaGroup group = getSchemaGroup();
        Sequence seq;
        if (group != null) {
            group.addName(name, true);
            seq = group.newSequence(name, this);
        } else
            seq = new Sequence(name, this);
        if (_seqMap == null)
            _seqMap = new TreeMap<>();

        DBIdentifier sName = DBIdentifier.toUpper(name);
        _seqMap.put(sName, seq);
        _seqs = null;
        return seq;
    }

    /**
     * Remove the given sequence from the schema.
     *
     * @return true if the sequence was removed, false if not in the schema
     */
    public boolean removeSequence(Sequence seq) {
        if (seq == null || _seqMap == null)
            return false;

        DBIdentifier sName = DBIdentifier.toUpper(seq.getIdentifier());
        Sequence cur = _seqMap.get(sName);
        if (!cur.equals(seq))
            return false;

        _seqMap.remove(sName);
        _seqs = null;
        SchemaGroup group = getSchemaGroup();
        if (group != null)
            group.removeName(seq.getIdentifier());
        seq.remove();
        return true;
    }

    /**
     * Import a sequence from another schema.
     */
    public Sequence importSequence(Sequence seq) {
        if (seq == null)
            return null;

        Sequence copy = addSequence(seq.getIdentifier());
        copy.setInitialValue(seq.getInitialValue());
        copy.setIncrement(seq.getIncrement());
        copy.setAllocate(seq.getAllocate());
        return copy;
    }

    @Override
    public int compareTo(Schema other) {
        DBIdentifier name = getIdentifier();
        DBIdentifier otherName = other.getIdentifier();
        if (DBIdentifier.isNull(name) && DBIdentifier.isNull(otherName)) {
            return 0;
        }
        if (DBIdentifier.isNull(name))
            return 1;
        if (DBIdentifier.isNull(otherName))
            return -1;
        return name.compareTo(otherName);
    }

    @Override
    public String toString() {
        return getIdentifier().getName();
    }
}
