/*
 * Copyright (C) 2011 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.impl.checker;

import org.exoplatform.commons.utils.SecurityHelper;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.PrivilegedExceptionAction;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Text-based inspection log implementation.
 * 
 * @author <a href="mailto:skarpenko@exoplatform.com">Sergiy Karpenko</a>
 * @version $Id: InspectionReport.java 34360 6.10.2011 skarpenko $
 */
public class InspectionReport
{
   private static final String COMMENT = "//";

   private static final String DELIMITER = "\n";

   private Writer writer;

   private boolean reportHasInconsistency;

   private String reportPath;

   private final ThreadLocal<InspectionReportContext> reportContext=new ThreadLocal<InspectionReportContext>();

   /**
    * InspectionReport constructor.
    */
   public InspectionReport(String forRepository) throws IOException
   {
      final File reportFile =
         new File("report-" + forRepository + "-" + new SimpleDateFormat("dd-MMM-yy-HH-mm").format(new Date()) + ".txt");

      SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<Void>()
      {
         public Void run() throws IOException
         {
            reportPath = reportFile.getAbsolutePath();
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportPath)));

            return null;
         }
      });

   }

   public void init(boolean isMulti)
   {
      if (isMulti)
      {
         reportContext.set(new InspectionReportContext());
      }
      else
      {
         reportContext.remove();
      }
   }

   public synchronized void flush() throws IOException
   {
      for (String message : reportContext.get().getComments())
      {
         writeMessage(message);
      }
      for (String brokenObject : reportContext.get().getBrokenObjects())
      {
         writeBrokenObject(brokenObject);
      }
      for (String key : reportContext.get().getLogExceptions().keySet())
      {
         writeException(key, reportContext.get().getLogExceptions().get(key));
      }
      reportContext.remove();
   }

   /**
    * Indicates if report has inconsistency info or not.
    */
   public boolean hasInconsistency()
   {
      return reportHasInconsistency;
   }

   /**
    * Adds comment to log.
    */
   public void logComment(String message) throws IOException
   {
      if (reportContext.get() != null)
      {
         reportContext.get().addComment(message);
      }
      else
      {
         writeMessage(message);
      }
   }

   /**
    * Adds description to log.
    */
   public void logDescription(String description) throws IOException
   {
      // The ThreadLocal has been initialized so we know that we are in multithreaded mode.
      if (reportContext.get() != null)
      {
         reportContext.get().addComment(description);
      }
      else
      {
         writeMessage(description);
      }
   }

   private void writeMessage(String message) throws IOException
   {
      writeLine(message);
      writer.flush();
   }

   /**
    * Adds detailed event to log.
    */
   public void logBrokenObjectAndSetInconsistency(String brokenObject) throws IOException
   {
      setInconsistency();
      // The ThreadLocal has been initialized so we know that we are in multithreaded mode.
      if (reportContext.get() != null)
      {
         reportContext.get().addBrokenObject(brokenObject);
      }
      else
      {
         writeBrokenObject(brokenObject);
      }
   }

   private void writeBrokenObject(String brokenObject) throws IOException
   {
      writer.write(brokenObject);
      writer.write(DELIMITER);
      writer.flush();
   }

   /**
    * Adds exception with full stack trace.
    */
   public void logExceptionAndSetInconsistency(String message, Throwable e) throws IOException
   {
      setInconsistency();
      // The ThreadLocal has been initialized so we know that we are in multithreaded mode.
      if (reportContext.get() != null)
      {
         reportContext.get().addLogException(message, e);
      }
      else
      {
         writeException(message, e);
      }
   }

   private void writeException(String message, Throwable e) throws IOException
   {
      writeLine(message);
      writeStackTrace(e);
      writer.flush();
   }

   /**
    * Closes report and frees all allocated resources. 
    */
   public void close() throws IOException
   {
      writer.close();
   }

   /**
    * Returns the absolute path to report file. 
    */
   public String getReportPath()
   {
      return reportPath;
   }

   private void setInconsistency()
   {
      reportHasInconsistency = true;
   }

   private void writeLine(String message) throws IOException
   {
      writer.write(COMMENT);
      writer.write(message);
      writer.write(DELIMITER);
      writer.flush();
   }

   private void writeStackTrace(Throwable e) throws IOException
   {
      writeLine(e.getMessage());
      writeLine(e.toString());
      StackTraceElement[] trace = e.getStackTrace();
      for (int i = 0; i < trace.length; i++)
      {
         writeLine("\tat " + trace[i]);
      }

      Throwable ourCause = e.getCause();
      if (ourCause != null)
      {
         writeLine("Cause:");
         writeStackTrace(ourCause);
      }
   }

   private static class InspectionReportContext
   {
      private final List<String> comments = new ArrayList<String>();
      private final List<String> logBrokenObjects = new ArrayList<String>();
      private final Map<String, Throwable> logExceptions = new HashMap<String, Throwable>();

      public void addComment(String message)
      {
         comments.add(message);
      }

      public void addBrokenObject(String brokenObject)
      {
         logBrokenObjects.add(brokenObject);
      }

      public void addLogException(String message, Throwable e)
      {
         logExceptions.put(message, e);
      }

      public List<String> getComments()
      {
         return comments;
      }

      public List<String> getBrokenObjects()
      {
         return logBrokenObjects;
      }

      public Map<String, Throwable> getLogExceptions()
      {
         return logExceptions;
      }
   }
}
