/**
 * Copyright (C) 2011-2017 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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.commonjava.indy.diag.data;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
import org.apache.commons.io.IOUtils;
import org.commonjava.indy.model.core.StoreType;
import org.commonjava.indy.subsys.datafile.DataFile;
import org.commonjava.indy.subsys.datafile.DataFileManager;
import org.slf4j.LoggerFactory;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.apache.commons.io.IOUtils.toInputStream;
import static org.apache.commons.lang.StringUtils.join;
import static org.commonjava.indy.flat.data.DataFileStoreConstants.INDY_STORE;

/**
 * Created by jdcasey on 1/11/17.
 *
 * Manages collection and packaging of diagnostic information.
 */
@ApplicationScoped
public class DiagnosticsManager
{
    org.slf4j.Logger logger = LoggerFactory.getLogger( getClass() );

    private static final String FILE_LOGGER = "FILE";

    public static final String THREAD_DUMP_FILE = "thread-dump.txt";

    public static final String LOGS_DIR = "logs";

    public static final String REPOS_DIR = "repos";

    @Inject
    private DataFileManager dataFileManager;

    public String getThreadDumpString()
    {
        StringBuilder sb = new StringBuilder();
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMXBean.getThreadInfo( threadMXBean.getAllThreadIds(), 100 );
        Stream.of( threadInfos ).forEachOrdered( ( ti ) -> {
            if ( sb.length() > 0 )
            {
                sb.append( "\n\n" );
            }

            sb.append( ti.getThreadName() )
              .append( "\n  State: " )
              .append( ti.getThreadState() )
              .append( "\n  Lock Info: " )
              .append( ti.getLockInfo() )
              .append( "\n  Monitors:" );

            MonitorInfo[] monitors = ti.getLockedMonitors();
            if ( monitors == null || monitors.length < 1 )
            {
                sb.append( "  -NONE-" );
            }
            else
            {
                sb.append( "\n  - " ).append( join( monitors, "\n  - " ) );
            }

            sb.append( "\n  Trace:\n    " ).append( join( ti.getStackTrace(), "\n    " ) );

        } );

        return sb.toString();
    }

    public File getDiagnosticBundle()
            throws IOException
    {
        File out = createTempFile( "diags" );
        Logger rootLogger = (Logger) LoggerFactory.getLogger( "ROOT" );

        logger.info( "Writing diagnostic bundle to: '{}'", out );

        Appender<ILoggingEvent> appender = rootLogger.getAppender( FILE_LOGGER );
        if ( appender != null && (appender instanceof FileAppender) )
        {
            try(ZipOutputStream zip = new ZipOutputStream( new FileOutputStream( out ) ) )
            {
                File dir = new File( ( (FileAppender) appender ).getFile() ).getParentFile();

                for ( File f : dir.listFiles( file -> file.getName().endsWith( ".log" ) ) )
                {
                    String name = LOGS_DIR + "/" + f.getName();
                    logger.info( "Adding {} to bundle zip: {}", name, out );

                    zip.putNextEntry( new ZipEntry( name ) );
                    try(InputStream in = new FileInputStream( f ) )
                    {
                        IOUtils.copy( in, zip );
                    }
                }

                logger.info( "Adding thread dump to bundle zip: {}", out );
                zip.putNextEntry( new ZipEntry( THREAD_DUMP_FILE ) );
                zip.write( getThreadDumpString().getBytes() );
                zip.flush();
                zip.close();
            }
        }
        else
        {
            return null;
        }

        return out;
    }

    public File getRepoBundle() throws IOException
    {
        File out = createTempFile( "repos" );
        logger.info( "Writing repo bundle to: '{}'", out );

        try (ZipOutputStream zip = new ZipOutputStream( new FileOutputStream( out ) ))
        {
            zipRepositoryFiles( zip );
        }
        return out;
    }

    private void zipRepositoryFiles( ZipOutputStream zip ) throws IOException
    {
        DataFile[] packageDirs = dataFileManager.getDataFile( INDY_STORE ).listFiles( ( f ) -> true );
        for ( DataFile pkgDir : packageDirs )
        {
            String pkgDirName = REPOS_DIR + "/" + pkgDir.getName();
            for ( StoreType type : StoreType.values() )
            {
                String typeDirName = pkgDirName + "/" + type.singularEndpointName();
                DataFile[] files = pkgDir.getChild( type.singularEndpointName() ).listFiles( f -> true );
                if ( files != null )
                {
                    for ( DataFile f : files )
                    {
                        final String json = f.readString();
                        String name = typeDirName + "/" + f.getName();
                        logger.debug( "Adding {} to repo zip", name );
                        zip.putNextEntry( new ZipEntry( name ) );
                        IOUtils.copy( toInputStream( json ), zip );
                    }
                }
            }
        }
    }

    private File createTempFile( String name ) throws IOException
    {
        return File.createTempFile(
                        "indy-" + name + "." + new SimpleDateFormat( "yyyy-MM-dd-hh-mm-ss.SSSZ" ).format( new Date() ),
                        ".zip" );

    }
}

@@ -5308,32 +5308,378 @@
 urn out;%0A    %7D%0A%0A
+    /**%0A     * TODO: dump the repo definitions as they exist in the StoreDataManager instead.%0A     * Currently, those are the same thing, but when we move to a cluster-enabled Indy implementation we're%0A     * going to need to escape the filesystem for things like repo definition storage, and use an ISPN cache%0A     * or similar instead.%0A     */%0A
     private void

Add TODO: get repo definition from storemanager rather than file system