/* This file is part of VoltDB.
 * Copyright (C) 2008-2017 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.voltdb.sysprocs;

import java.util.List;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

import org.voltcore.logging.Level;
import org.voltcore.logging.VoltLogger;
import org.voltdb.DependencyPair;
import org.voltdb.DeprecatedProcedureAPIAccess;
import org.voltdb.ParameterSet;
import org.voltdb.ProcInfo;
import org.voltdb.SystemProcedureExecutionContext;
import org.voltdb.VoltDB;
import org.voltdb.VoltSystemProcedure;
import org.voltdb.VoltTable;

@ProcInfo(singlePartition = false)

/**
 * Execute the supplied XML string using org.apache.log4j.xml.DomConfigurator
 * The first parameter is the string containing the XML configuration
 */
public class UpdateLogging extends VoltSystemProcedure
{
    private static final VoltLogger hostLog = new VoltLogger("HOST");

    private final static CyclicBarrier barrier = new CyclicBarrier(VoltDB.instance().getCatalogContext().getNodeSettings().getLocalSitesCount());

    private static final VoltLogger loggers[] = new VoltLogger[] {
            new VoltLogger("HOST")
    };

    @Override
    public long[] getPlanFragmentIds() {
        return new long[]{};
    }

    @Override
    public DependencyPair executePlanFragment(
            Map<Integer, List<VoltTable>> dependencies, long fragmentId,
            ParameterSet params, SystemProcedureExecutionContext context)
    {
        throw new RuntimeException("UpdateLogging was given an " +
                                   "invalid fragment id: " + String.valueOf(fragmentId));
    }

    /**
     * Change the operational log configuration.
     * @param ctx       Internal parameter. Not user-accessible.
     * @param xmlConfig New configuration XML document.
     * @return          Standard STATUS table.
     */
    @SuppressWarnings("deprecation")
    public VoltTable[] run(SystemProcedureExecutionContext ctx,
                           String username,
                           String remoteHost,
                           String xmlConfig)
    {
        long oldLevels  = 0;
        if (ctx.isLowestSiteId()) {
            // Logger level is a global property, pick the site with lowest id to do it.
            hostLog.info(String.format("%s from %s changed the log4j settings", username, remoteHost));
            hostLog.info(xmlConfig);
            oldLevels = hostLog.getLogLevels(loggers);
        }

        try {
            // Mimic the multi-fragment semantics as scatter-gather pattern is an overkill for this simple task.
            // There are chances that some sites being interrupted and update the logging before old logger level
            // being read, but the reasons we don't care because 1) it is rare and 2) it only effects when HOST
            // logger being changed from higher than INFO level to INFO or lower level.
            barrier.await();
        } catch (InterruptedException | BrokenBarrierException dontcare) { }

        VoltDB.instance().logUpdate(xmlConfig, DeprecatedProcedureAPIAccess.getVoltPrivateRealTransactionId(this),
                ctx.getPaths().getVoltDBRoot());
        ctx.updateBackendLogLevels();

        if (ctx.isLowestSiteId()) {
            long newLevels = hostLog.getLogLevels(loggers);
            if (newLevels != oldLevels) {
                // If HOST logger wasn't able to log before and now it can, logs the setting change event.
                int index = (int)((oldLevels >> 3) & 7);
                Level before = Level.values()[index];
                index = (int)((newLevels >> 3) & 7);
                Level after = Level.values()[index];
                if (before.ordinal() > Level.INFO.ordinal() && after.ordinal() <= Level.INFO.ordinal()) {
                    hostLog.info(String.format("%s from %s changed the log4j settings", username, remoteHost));
                    hostLog.info(xmlConfig);
                }
            }
            barrier.reset();
        }

        VoltTable t = new VoltTable(VoltSystemProcedure.STATUS_SCHEMA);
        t.addRow(VoltSystemProcedure.STATUS_OK);
        return (new VoltTable[] {t});
    }
}
