/*
 * Copyright (C) 2008 Jive Software. All rights reserved.
 *
 * Licensed 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.jivesoftware.openfire.reporting.stats;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import org.jivesoftware.database.DbConnectionManager;
import org.jrobin.core.RrdBackend;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RrdSqlBackend extends RrdBackend {
    
    private static final Logger Log = LoggerFactory.getLogger(RrdSqlBackend.class);
    
    // SQL prepared statements
    static final String JDBC_SELECT = "SELECT bytes from ofRRDs where id = ?";
    static final String JDBC_INSERT = "INSERT INTO ofRRDs (id, updatedDate, bytes) VALUES (?, ?, ?)";
    static final String JDBC_UPDATE = "UPDATE ofRRDs SET bytes = ?, updatedDate=? WHERE id = ?";
    static final String JDBC_DELETE = "DELETE FROM ofRRDs WHERE id = ?";

    // this is the place where our RRD bytes will be stored
    private byte[] buffer = null;
    // When readOnly then the SQL DB is not updated
    private boolean readOnly;

    public static void importRRD(String id, File rrdFile) throws IOException {
        // Read content from file
        FileInputStream stream = null;
        byte[] bytes = null;
        try {
            stream = new FileInputStream(rrdFile);
            // Create the byte array to hold the data
            bytes = new byte[(int) rrdFile.length()];
            // Read in the bytes
            int offset = 0;
            int numRead;
            while (offset < bytes.length && (numRead = stream.read(bytes, offset, bytes.length - offset)) >= 0) {
                offset += numRead;
            }
        }
        finally {
            if (stream != null) {
                stream.close();
            }
        }
        // Save file content to the DB
        Connection con = null;
        PreparedStatement pstmt = null;
        PreparedStatement insertStmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(JDBC_SELECT);
            pstmt.setString(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                // Do not import since there is already an RRD in the DB
            }
            else {
                // RRD with the given id does not exist
                // we'll insert a new row in the table using the supplied id
                // but with no RRD bytes (null)
                insertStmt = con.prepareStatement(JDBC_INSERT);
                insertStmt.setString(1, id);
                insertStmt.setLong(2, System.currentTimeMillis());
                insertStmt.setBytes(3, bytes);
                insertStmt.executeUpdate();
            }
        }
        catch (Exception e) {
            Log.error("Error while accessing information in database: " + e);
        }
        finally {
            DbConnectionManager.closeStatement(insertStmt);
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
    }

    RrdSqlBackend(String id, boolean readOnly) throws IOException {
        super(id);
        this.readOnly = readOnly;
        Connection con = null;
        PreparedStatement pstmt = null;
        PreparedStatement insertStmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(JDBC_SELECT);
            pstmt.setString(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                // RRD with the given id already exists
                // bring RRD data to our buffer
                buffer = rs.getBytes("bytes");
            }
            else {
                // RRD with the given id does not exist
                // we'll insert a new row in the table using the supplied id
                // but with no RRD bytes (null)
                insertStmt = con.prepareStatement(JDBC_INSERT);
                insertStmt.setString(1, id);
                insertStmt.setLong(2, System.currentTimeMillis());
                insertStmt.setBytes(3, null);
                insertStmt.executeUpdate();
            }
        }
        catch (Exception e) {
            Log.error("Error while accessing information in database: " + e);
        }
        finally {
            DbConnectionManager.closeStatement(insertStmt);
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
    }

    // this method writes bytes supplied from the JRobin frontend
    // to our memory buffer
    @Override
    protected void write(long offset, byte[] b) {
        int pos = (int) offset;
        for(int i = 0; i < b.length; i++) {
            buffer[pos++] = b[i];
        }
    }

    // this method reads bytes requested from the JRobin frontend
    // and stores them in the supplied byte[] array
    @Override
    protected void read(long offset, byte[] b) {
        int pos = (int) offset;
        for(int i = 0; i < b.length; i++) {
            b[i] = buffer[pos++];
        }
    }

    // returns the RRD size (since all RRD bytes are
    // in the buffer, it is equal to the buffer length
    @Override
    public long getLength() throws IOException {
        return buffer.length;
    }

    // provides enough space in memory for the RRD
    @Override
    protected void setLength(long length) {
        buffer = new byte[(int) length];
    }


    @Override
    public void close() throws IOException {
        super.close();
        // Save data to the SQL DB only if not read-only
        if (!readOnly) {
            sync();
        }
    }
    // sends bytes in memory to the database
    protected void sync() throws IOException {
        // RRD id is here
        String id = super.getPath();
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(JDBC_UPDATE);
            pstmt.setBytes(1, buffer);
            pstmt.setLong(2, System.currentTimeMillis());
            pstmt.setString(3, id);
            pstmt.executeUpdate();
        }
        catch (Exception e) {
            Log.error("Error while updating information in database: " + e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
    }

    // checks if RRD with the given id already exists in the database
    // used from RrdSqlBackendFactory class
    static boolean exists(String id) throws IOException {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(JDBC_SELECT);
            pstmt.setString(1, id);
            rs = pstmt.executeQuery();
            return rs.next();
        }
        catch (Exception e) {
            Log.error("Error while accessing information in database: " + e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return false;
    }
}
