/*
 * 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.meta.strats;

import java.io.InputStream;
import java.io.Reader;
import java.sql.SQLException;
import java.sql.Types;

import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.JDBCStore;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.PostgresDictionary;
import org.apache.openjpa.jdbc.sql.Result;
import org.apache.openjpa.jdbc.sql.Row;
import org.apache.openjpa.jdbc.sql.RowManager;
import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.meta.JavaTypes;

/**
 * Direct mapping from a stream value to a column.
 *
 * @author Ignacio Andreu
 * @since 1.1.0
 */
public class LobFieldStrategy extends AbstractFieldStrategy {

    
    private static final long serialVersionUID = 1L;
    private int fieldType;
    private boolean isBlob;

    @Override
    public void map(boolean adapt) {
        assertNotMappedBy();
        field.mapJoin(adapt, false);
        field.getKeyMapping().getValueInfo().assertNoSchemaComponents
            (field.getKey(), !adapt);
        field.getElementMapping().getValueInfo().assertNoSchemaComponents
            (field.getElement(), !adapt);
        field.setStream(true);
        ValueMappingInfo vinfo = field.getValueInfo();
        vinfo.assertNoJoin(field, true);
        vinfo.assertNoForeignKey(field, !adapt);
        DBDictionary dict = field.getMappingRepository().getDBDictionary();
        DBIdentifier fieldName = DBIdentifier.newColumn(field.getName(), dict != null ? dict.delimitAll() : false);

        Column tmpCol = new Column();
        tmpCol.setIdentifier(fieldName);
        tmpCol.setType(fieldType);
        tmpCol.setJavaType(field.getTypeCode());

        tmpCol.setSize(-1);

        Column[] cols = vinfo.getColumns(field, fieldName,
            new Column[]{ tmpCol }, field.getTable(), adapt);

        field.setColumns(cols);
        field.setColumnIO(vinfo.getColumnIO());
        field.mapConstraints(fieldName, adapt);
        field.mapPrimaryKey(adapt);
    }

    @Override
    public Boolean isCustomInsert(OpenJPAStateManager sm, JDBCStore store) {
        return null;
    }

    @Override
    public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
        throws SQLException {
        Select sel = createSelect(sm, store);
        store.getDBDictionary().deleteStream(store, sel);
    }

    @Override
    public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
        throws SQLException {
        Object ob = toDataStoreValue(sm.fetchObjectField
            (field.getIndex()), store);
        Row row = field.getRow(sm, store, rm, Row.ACTION_INSERT);
        if (field.getColumnIO().isInsertable(0, ob == null)) {
            Select sel = createSelect(sm, store);
            if (isBlob) {
                store.getDBDictionary().insertBlobForStreamingLoad
                    (row, field.getColumns()[0], store, ob, sel);
            } else {
                store.getDBDictionary().insertClobForStreamingLoad
                    (row, field.getColumns()[0], ob);
            }
        }
    }

    @Override
    public void customInsert(OpenJPAStateManager sm, JDBCStore store)
        throws SQLException {
        Object ob = toDataStoreValue(sm.fetchObjectField
            (field.getIndex()), store);
        if (field.getColumnIO().isInsertable(0, ob == null)) {
            if (ob != null) {
                Select sel = createSelect(sm, store);
                if (isBlob) {
                    store.getDBDictionary().updateBlob
                        (sel, store, (InputStream)ob);
                } else {
                    store.getDBDictionary().updateClob
                        (sel, store, (Reader)ob);
                }
            }
        }
    }

    @Override
    public Boolean isCustomUpdate(OpenJPAStateManager sm, JDBCStore store) {
        return null;
    }

    @Override
    public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
        throws SQLException {
        Object ob = toDataStoreValue(sm.fetchObjectField
            (field.getIndex()), store);
        if (field.getColumnIO().isUpdatable(0, ob == null)) {
            Row row = field.getRow(sm, store, rm, Row.ACTION_UPDATE);
            Select sel = createSelect(sm, store);
            if (isBlob) {
                store.getDBDictionary().insertBlobForStreamingLoad
                    (row, field.getColumns()[0], store, ob, sel);
            } else {
                store.getDBDictionary().insertClobForStreamingLoad
                    (row, field.getColumns()[0], ob);
            }
        }
    }

    @Override
    public void customUpdate(OpenJPAStateManager sm, JDBCStore store)
        throws SQLException {
        Object ob = toDataStoreValue(sm.fetchObjectField
                (field.getIndex()), store);
        if (field.getColumnIO().isUpdatable(0, ob == null)) {
            if (ob != null) {
                Select sel = createSelect(sm, store);
                if (isBlob) {
                    store.getDBDictionary().updateBlob
                        (sel, store, (InputStream)ob);
                } else {
                    store.getDBDictionary().updateClob
                        (sel, store, (Reader)ob);
                }
            }
        }
    }

    @Override
    public int supportsSelect(Select sel, int type, OpenJPAStateManager sm,
        JDBCStore store, JDBCFetchConfiguration fetch) {
        if (type == Select.TYPE_JOINLESS && sel.isSelected(field.getTable()))
            return 1;
        return 0;
    }

    @Override
    public int select(Select sel, OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch, int eagerMode) {
        sel.select(field.getColumns()[0], field.join(sel));
        return 1;
    }

    @Override
    public void load(OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch, Result res) throws SQLException {
        Column col = field.getColumns()[0];
        if (res.contains(col)) {
            if (isBlob) {
                sm.storeObject(field.getIndex(), res.getLOBStream(store, col));
            } else {
                sm.storeObject(field.getIndex(), res.getCharacterStream(col));
            }
        }
    }

    @Override
    protected void assertNotMappedBy() {
        if (field != null && field.getMappedBy() != null)
            throw new UnsupportedOperationException();
    }

    @Override
    public void setFieldMapping(FieldMapping owner) {
        field = owner;
        if (owner.getElementMapping().getMappingRepository().getDBDictionary()
            instanceof PostgresDictionary) {
            fieldType = Types.INTEGER;
            isBlob = true;
            field.setTypeCode(JavaTypes.INT);
        } else {
            if (owner.getType().isAssignableFrom(InputStream.class)) {
                isBlob = true;
                fieldType = Types.BLOB;
            } else if (owner.getType().isAssignableFrom(Reader.class)) {
                isBlob = false;
                fieldType = Types.CLOB;
            }
        }
    }

    private Select createSelect(OpenJPAStateManager sm, JDBCStore store) {
        Select sel = store.getSQLFactory().newSelect();
        sel.select(field.getColumns()[0]);
        sel.selectPrimaryKey(field.getDefiningMapping());
        sel.wherePrimaryKey
            (sm.getObjectId(), field.getDefiningMapping(), store);
        sel.setLob(true);
        return sel;
    }

}
