/*
 * 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.jena.sdb.graph;

import static java.lang.String.format;
import static org.apache.jena.sdb.sql.SQLUtils.quoteStr ;
import static org.apache.jena.sdb.sql.SQLUtils.sqlStr ;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.jena.sdb.sql.RS ;
import org.apache.jena.sdb.sql.ResultSetJDBC ;
import org.apache.jena.sdb.sql.SDBConnection ;
import org.apache.jena.sdb.sql.SDBExceptionSQL ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.shared.impl.PrefixMappingImpl ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** A prefix mapping that is backed by a database table
 */

public class PrefixMappingSDB extends PrefixMappingImpl
{
    // Use DatasetPrefixStorage as move to Per-graph prefix mappings. (schema change)
    
    // See PrefixMappingPersistent
    static private Logger log = LoggerFactory.getLogger(PrefixMappingSDB.class) ;
    
    static public final String prefixTableName  = "Prefixes" ;
    static public final int    prefixColWidth   = 50 ;  // Minimum
    static public final int    uriColWidth      = 500 ; // Minimum
    
    // TODO Per-graph prefix mappings.
    // TODO: Encode capital letters in prefix names.
    // new design needed : safe failures of other updates.
    
    // We are an in-memory prefix mapping except the update operations
    // are also applied to the table.
    /* Roughly:
          CREATE TABLE Prefixes (,
              -- Each column should be:
              -- Case sensitive, UTF8 characters, not NULL
              -- MySQL: BINARY CHARACTER SET utf8 NOT NULL default '',",
              prefix VARCHAR , 
              uri TEXT or VARCHAR ,
              PRIMARY KEY (prefix) ,
          )
     */
    
    private SDBConnection connection = null ;
    private String graphName ; 
    
    public PrefixMappingSDB(String graphURI, SDBConnection sdb)
    {
        super() ;
        graphName = graphURI ;
        connection = sdb  ;
        readPrefixMapping() ;
    }
    
    @Override
    protected void set(String prefix, String uri)
    {
        // Delete old one if present.
        String x = get(prefix) ;
        if ( x != null )
        {
            if(x.equals(uri))
                // Already there - no-op (thanks to Eric Diaz for pointing this out)
                return;
            removeFromPrefixMap(prefix, x) ;
        }

        // Persist
        insertIntoPrefixMap(prefix, uri) ;
        // Set caches
        super.set(prefix, uri) ;
    }

    @Override
    protected String get(String prefix)
    {
        String x = super.get(prefix) ;
        if ( x != null )
            return x ;
        x = readFromPrefixMap(prefix) ;
        return x ;
    }
    
    @Override
    public PrefixMapping removeNsPrefix(String prefix)
    {
        String uri = super.getNsPrefixURI(prefix) ;
        if ( uri != null )
            removeFromPrefixMap(prefix, uri) ;
        super.removeNsPrefix(prefix) ;
        return this ; 
    }
    
    // Super class implementation loops and calls setNsPrefix calls set() for each entry.
    //@Override
    //public PrefixMapping setNsPrefixes(PrefixMapping arg0)

    // Super class implementation will suffice.
    // It (creates a map copy and) loops on each entry.
    //@Override
    //public PrefixMapping setNsPrefixes(Map arg0)
    
    // -------- Worker implementations
    
    private void readPrefixMapping()
    {
        ResultSetJDBC rsx = null ;
        try {
            String sqlStmt = "SELECT prefix, uri FROM "+prefixTableName ;
            rsx = connection.execSilent(sqlStmt) ;
            if ( rsx == null || rsx.get() == null )
                return ;
            ResultSet rs = rsx.get() ;
            while(rs.next())
            {
                String p = rs.getString("prefix") ;
                p = decode(p) ;
                String v = rs.getString("uri") ;
                // Load in-memory copy.
                super.set(p, v) ;
            }
        } catch (SQLException ex)
        { throw new SDBExceptionSQL("Failed to get prefixes", ex) ; }
        finally
        {
            RS.close(rsx) ;
        }
    }

    private String readFromPrefixMap(String prefix)
    {
        ResultSetJDBC rsx = null ;
        try {
            String sqlStmt = sqlStr(
                "SELECT uri FROM "+prefixTableName,
                "   WHERE prefix = "+quoteStr(prefix)
                ) ;
            rsx = connection.execQuery(sqlStmt) ;
            ResultSet rs = rsx.get() ;
            String uri = null ;
            while(rs.next())
            {
                String v = rs.getString("uri") ;
                uri = v ;
                if ( rs.next() )
                    log.warn("Multiple prefix mappings for '"+prefix+"'") ;
                break ;
            }
            return uri ;
        } catch (SQLException ex)
        { throw new SDBExceptionSQL(format("Failed to read prefix (%s)", prefix), ex) ; }
        finally { RS.close(rsx) ; }
    }
    
    private void insertIntoPrefixMap(String prefix, String uri)
    {
        // Only called from set()
        // Assumes not present in the persistent table.
        try {
            prefix = encode(prefix) ;
            String sqlStmt = sqlStr(
                "INSERT INTO "+prefixTableName,
                "   VALUES ("+quoteStr(prefix)+", "+quoteStr(uri)+")"
                ) ;
            connection.execUpdate(sqlStmt) ;
        } catch (SQLException ex)
        { 
            throw new SDBExceptionSQL(format("Failed to set prefix (%s,%s)", prefix, uri), ex) ; 
        }
    }
    
    private void removeFromPrefixMap(String prefix, String uri)
    {
        try {
            prefix = encode(prefix) ;
            String sqlStmt = sqlStr(
                 "DELETE FROM "+prefixTableName+" WHERE",
                 "   prefix = "+quoteStr(prefix) //+" AND uri = "+quote(uri)
                 ) ;
            connection.execUpdate(sqlStmt) ;
        } catch (SQLException ex)
        { throw new SDBExceptionSQL(format("Failed to remove prefix (%s,%s)", prefix, uri), ex) ; }
    }
    
    // Always put in a trailing ":" so the prefix is never the empty string
    // which is null on Oracle but the prefix is a primary key and can't be null. 
    
    private String encode(String prefix)
    { return prefix+":" ; }

    private String decode(String prefix)
    { return prefix.substring(0, prefix.length()-1) ; }

}
