/**
 * Copyright (c) 2004-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.common.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.runtime.IStatus;

import org.eclipse.emf.common.EMFPlugin;


/**
 * A basic implementation of a diagnostic that that also acts as a chain.
 */
public class BasicDiagnostic implements Diagnostic, DiagnosticChain
{
  /**
   * The severity.
   * @see #getSeverity
   */
  protected int severity;

  /**
   * The message.
   * @see #getMessage
   */
  protected String message;

  /**
   * The message.
   * @see #getMessage
   */
  protected List<Diagnostic> children;

  /**
   * The data.
   * @see #getData
   */
  protected List<?> data;

  /**
   * The source.
   * @see #getSource
   */
  protected String source;

  /**
   * The code.
   * @see #getCode
   */
  protected int code;
  
  /**
   * Default Constructor (no initialization for local parameters)
   */
  public BasicDiagnostic()
  {
    super();
  }

  public BasicDiagnostic(String source, int code, String message, Object[] data) 
  {
    this.source = source;
    this.code = code;
    this.message = message;
    this.data = dataAsList(data);
  }

  public BasicDiagnostic(int severity, String source, int code, String message, Object[] data)
  {
    this(source, code, message, data);
    this.severity = severity;
  }

  public BasicDiagnostic(String source, int code, List<? extends Diagnostic> children, String message, Object[] data)
  {
    this(source, code, message, data);
    if (children != null)
    {
      for (Diagnostic diagnostic : children)
      {
        add(diagnostic);
      }
    }
  }

  protected List<?> dataAsList(Object [] data)
  {
    if (data == null)
    {
      return Collections.EMPTY_LIST;
    }
    else
    {
      Object [] copy = new Object [data.length];
      System.arraycopy(data, 0, copy, 0, data.length);
      return new BasicEList.UnmodifiableEList<Object>(copy.length, copy);
    }
  }
  
  protected void setSeverity(int severity)
  {
    this.severity = severity;
  }

  public int getSeverity()
  {
    return severity;
  }

  public String getMessage()
  {
    return message;
  }

  public List<?> getData()
  {
    return data;
  }

  public List<Diagnostic> getChildren()
  {
    if (children == null)
    {
      return Collections.emptyList();
    }
    else
    {
      return Collections.unmodifiableList(children);
    }
  }

  protected void setSource(String source)
  {
    this.source = source;
  }

  public String getSource()
  {
    return source;
  }
  
  protected void setCode(int code)
  {
    this.code = code;
  }

  public int getCode()
  {
    return code;
  }

  public void add(Diagnostic diagnostic)
  {
    if (children == null)
    {
      children = new BasicEList<Diagnostic>();
    }

    children.add(diagnostic);
    int childSeverity = diagnostic.getSeverity();
    if (childSeverity > getSeverity())
    {
      severity = childSeverity;
    }
  }

  public void addAll(Diagnostic diagnostic)
  {
    for (Diagnostic child : diagnostic.getChildren())
    {
      add(child);
    }
  }

  public void merge(Diagnostic diagnostic)
  {
    if (diagnostic.getChildren().isEmpty())
    {
      add(diagnostic);
    }
    else
    {
      addAll(diagnostic);
    }
  }

  public int recomputeSeverity()
  {
    if (children != null)
    {
      severity = OK;
      for (Diagnostic child : children)
      {
        int childSeverity = child instanceof BasicDiagnostic ? ((BasicDiagnostic)child).recomputeSeverity() : child.getSeverity();
        if (childSeverity > severity)
        {
          severity = childSeverity;
        }
      }
    }

    return severity;
  }
  
  /**
   * Returns the first throwable object available in the {@link #data} list, 
   * which is set when this diagnostic is instantiated.
   */
  public Throwable getException()
  {
    List<?> data = getData();
    if (data != null)
    {
      for (Object datum : data)
      {
        if (datum instanceof Throwable)
        {
          return (Throwable)datum;
        }
      }
    }
    return null;
  }
  
  @Override
  public String toString()
  {
    StringBuilder result = new StringBuilder();
    result.append("Diagnostic ");
    switch (severity)
    {
      case OK: 
      {
        result.append("OK"); 
        break;
      }
      case INFO: 
      {
        result.append("INFO"); 
        break;
      }
      case WARNING: 
      {
        result.append("WARNING"); 
        break;
      }
      case ERROR: 
      {
        result.append("ERROR"); 
        break;
      }
      case CANCEL: 
      {
        result.append("CANCEL"); 
        break;
      }
      default:
      {
        result.append(Integer.toHexString(severity));
        break;
      }
    }

    result.append(" source="); 
    result.append(source);

    result.append(" code="); 
    result.append(code);

    result.append(' ');
    result.append(message);

    if (data != null)
    {
      result.append(" data=");
      result.append(data);
    }
    if (children != null)
    {
      result.append(' ');
      result.append(children);
    }

    return result.toString();
  }  

  private static class StatusWrapper implements IStatus
  {
    protected static final IStatus [] EMPTY_CHILDREN = new IStatus [0];

    protected Throwable throwable;
    protected Diagnostic diagnostic;
    protected IStatus [] wrappedChildren;

    public StatusWrapper(Diagnostic diagnostic)
    {
      this.diagnostic = diagnostic;
    }
    
    public StatusWrapper(DiagnosticException diagnosticException)
    {
      throwable = diagnosticException;
      diagnostic = diagnosticException.getDiagnostic();
    }

    public IStatus[] getChildren()
    {
      if (wrappedChildren == null)
      {
        List<Diagnostic> children = diagnostic.getChildren();
        if (children.isEmpty())
        {
          wrappedChildren = EMPTY_CHILDREN;
        }
        else
        {
          wrappedChildren = new IStatus [children.size()];
          for (int i = 0; i < wrappedChildren.length; ++i)
          {
            wrappedChildren[i] = toIStatus(children.get(i));
          }
        }
      }
      return wrappedChildren;
    }
    
    public int getCode()
    {
      return diagnostic.getCode();
    }
    
    public Throwable getException()
    {
      return throwable != null ? throwable : diagnostic.getException();
    }
    
    public String getMessage()
    {
      return diagnostic.getMessage();
    }
    
    public String getPlugin()
    {
      return diagnostic.getSource();
    }
    
    public int getSeverity()
    {
      return diagnostic.getSeverity();
    }
    
    public boolean isMultiStatus()
    {
      return !diagnostic.getChildren().isEmpty();
    }
    
    public boolean isOK()
    {
      return diagnostic.getSeverity() == OK;
    }
    
    public boolean matches(int severityMask)
    {
      return (diagnostic.getSeverity() & severityMask ) != 0;
    }

    @Override
    public String toString()
    {
      return diagnostic.toString();
    }

    public static IStatus convert(Diagnostic diagnostic)
    {
      return 
        diagnostic instanceof DiagnosticWrapper ?
          ((DiagnosticWrapper)diagnostic).status :
          new StatusWrapper(diagnostic);
    }

    public static IStatus create(DiagnosticException diagnosticException)
    {
      return new StatusWrapper(diagnosticException);
    }
  }

  /**
   * Returns the diagnostic viewed as an {@link IStatus}.
   */
  public static IStatus toIStatus(Diagnostic diagnostic)
  {
    return StatusWrapper.convert(diagnostic);
  }

  /**
   * Returns the diagnostic exception viewed as an {@link IStatus}.
   */
  public static IStatus toIStatus(DiagnosticException diagnosticException)
  {
    return StatusWrapper.create(diagnosticException);
  }
  
  private static class DiagnosticWrapper implements Diagnostic
  {
    protected IStatus status;
    protected List<Diagnostic> wrappedChildren;
    protected List<Diagnostic> unmodifiableWrappedChildren;
    protected List<Object> data;

    public DiagnosticWrapper(IStatus status)
    {
      this.status = status;
    }
    
    public int getCode()
    {
      return status.getCode();
    }

    public String getMessage()
    {
      return status.getMessage();
    }

    public int getSeverity()
    {
      return status.getSeverity();
    }

    public String getSource()
    {
      return status.getPlugin();
    }
    
    public Throwable getException()
    {
      return status.getException();
    }

    public List<Diagnostic> basicGetChildren()
    {
      if (wrappedChildren == null)
      {
        IStatus[] children = status.getChildren();
        if (children.length == 0)
        {
          wrappedChildren = new ArrayList<Diagnostic>();
        }
        else
        {
          wrappedChildren = new ArrayList<Diagnostic>(children.length);
          for (IStatus child : children)
          {
            wrappedChildren.add(toDiagnostic(child));
          }
        }
      }
      return wrappedChildren;      
    }

    public List<Diagnostic> getChildren()
    {
      if (unmodifiableWrappedChildren == null)
      {
        unmodifiableWrappedChildren = Collections.unmodifiableList(basicGetChildren());
      }
      return unmodifiableWrappedChildren;
    }

    public List<?> getData()
    {
      if (data == null)
      {
        List<Object> list = new ArrayList<Object>(2);
        Throwable exception = getException();
        if (exception != null)
        {
          list.add(exception);
        }
        list.add(status);
        data = Collections.unmodifiableList(list);
      }
      return data;
    }

    public static Diagnostic convert(IStatus status)
    {
      return 
        status instanceof StatusWrapper ?
          ((StatusWrapper)status).diagnostic :
          new DiagnosticWrapper(status);
    }
  }
  
  public static Diagnostic toDiagnostic(IStatus status)
  {
    return DiagnosticWrapper.convert(status);
  }

  /**
   * Returns the throwable viewed as a {@link Diagnostic}.
   * 
   * @param throwable
   * @return {@link Diagnostic}
   */
  public static Diagnostic toDiagnostic(Throwable throwable)
  {
    if (throwable instanceof DiagnosticException)
    {
      return ((DiagnosticException)throwable).getDiagnostic();
    }
    else if (throwable instanceof WrappedException)
    {
      return toDiagnostic(throwable.getCause());
    }
    
    if (EMFPlugin.IS_ECLIPSE_RUNNING)
    {
      Diagnostic diagnostic = EclipseHelper.toDiagnostic(throwable);
      if (diagnostic != null)
      {
        return diagnostic;
      }
    }
    
    String message = throwable.getClass().getName();
    int index = message.lastIndexOf('.');
    if (index >= 0)
    {
      message = message.substring(index + 1);
    }
    if (throwable.getLocalizedMessage() != null)
    {
      message = message + ": " + throwable.getLocalizedMessage();
    }
    
    BasicDiagnostic basicDiagnostic = 
      new BasicDiagnostic
        (Diagnostic.ERROR,
         "org.eclipse.emf.common", 
         0, 
         message,
         new Object[] { throwable });
    
    if (throwable.getCause() != null && throwable.getCause() != throwable)
    {
      throwable = throwable.getCause();
      basicDiagnostic.add(toDiagnostic(throwable));
    }
    
    return basicDiagnostic;
  }
  
  private static class EclipseHelper
  {
    public static Diagnostic toDiagnostic(Throwable throwable)
    {
      if (throwable instanceof org.eclipse.core.runtime.CoreException)
      {
        IStatus status = ((org.eclipse.core.runtime.CoreException)throwable).getStatus();
        DiagnosticWrapper wrapperDiagnostic = new DiagnosticWrapper(status);
        Throwable cause = throwable.getCause();
        if (cause != null && cause != throwable)
        {
          wrapperDiagnostic.basicGetChildren().add(BasicDiagnostic.toDiagnostic(cause));
        }
        return wrapperDiagnostic;
      }
      return null;
    }
  }
}
