/*******************************************************************************
 * Copyright (c) 2001, 2019 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     David Carver (STAR) - bug 297005 - Some static constants not made final.
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.validation.core;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.xml.core.internal.XMLCorePlugin;
import org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames;
import org.eclipse.wst.xml.core.internal.validation.XMLValidationMessages;

/**
 * This class handles messages from a validator. This class can handle
 * 
 * @author Lawrence Mandel, IBM
 */
public class ValidationInfo implements ValidationReport
{
  private boolean WRAPPER_ERROR_SUPPORT_ENABLED = true;
  public static final int  SEV_ERROR = 0;
  public static final int SEV_WARNING = 1;
  
  private String validating_file_uri = null;
  private URL validating_file_url = null;
  private boolean valid = true;
  private List messages = new ArrayList();
  private HashMap nestedMessages = new HashMap();
  private IScopeContext[] sameFilePreferenceContext = null;

  /**
   * Constructor.
   * 
   * @param uri
   *            The URI of the file for the validation.
   */
  public ValidationInfo(String uri)
  {
    if(uri != null)
    {
      this.validating_file_uri = uri;
      try
      {
        this.validating_file_url = new URL(uri);
      } catch (MalformedURLException e)
      {
      }
    }
  }

  public String getFileURI()
  {
    return validating_file_uri;
  }

  public boolean isValid()
  {
    return valid;
  }

  /**
   * Add an error message.
   * 
   * @param message The message to add.
   * @param line The line location of the message.
   * @param column The column location of the message.
   * @param uri The URI of the file that contains the message.
   */
  public void addError(String message, int line, int column, String uri)
  {
    addError(message, line, column, uri, null, null);
  }  
  
  /**
   * 
   * Add an error message.
   * 
   * @param message The message to add.
   * @param line The line location of the message.
   * @param column The column location of the message.
   * @param uri The URI of the file that contains the message.
   * @param key The key for the message.
   * @param messageArguments more information about the error
   */
  public void addError(String message, int line, int column, String uri, String key, Object[] messageArguments)
  {    
    if(addMessage(message, line, column, uri, SEV_ERROR, key, messageArguments))
    {
      valid = false;
    }
  }

  /**
   * Add a warning message.
   * 
   * @param message The string message of the warning.
   * @param line The line location of the warning.
   * @param column The column location of the warning.
   * @param uri The URI of the file that contains the warning.
   */
  public void addWarning(String message, int line, int column, String uri)
  {
    addWarning(message, line, column, uri, null, null);
  }
  
  /**
   * 
   * Add an error message.
   * 
   * @param message The message to add.
   * @param line The line location of the message.
   * @param column The column location of the message.
   * @param uri The URI of the file that contains the message.
   * @param key The key for the message.
   * @param messageArguments more information about the error
   */
  public void addWarning(String message, int line, int column, String uri, String key, Object[] messageArguments)
  {    
    addMessage(message, line, column, uri, SEV_WARNING, key, messageArguments);
  }
  
  /**
   * Add a message to the list. Return true if successful, false otherwise.
   * 
   * @param message The message to add to the list.
   * @param line The line location of the message.
   * @param column The column location of the message.
   * @param uri The URI of the file that contains the message.
   * @param severity The severity of the message.
   * @param key the Xerces error key for this error
   * @param messageArguments more information on the error
   * @return True if the message was successfully added, false otherwise.
   */
  private boolean addMessage(String message, int line, int column, String uri, int severity, String key, Object[] messageArguments)
  {
    boolean successfullyAdded = false;
    // If the message if null there is nothing to add.
    if(message == null)
    {
      return successfullyAdded;
    }
    String errorURI = normalize(uri);
    URL errorURL = null;
    if (errorURI != null)
    {
      try
      {
        errorURL = new URL(errorURI);
      } catch (MalformedURLException e)
      {
      }
      //errorURI = normalizeURI(errorURI);
    }
    //boolean doDialog = true;
    if (errorURL != null)
    {
      boolean isSameFile = (validating_file_url != null && validating_file_url.sameFile(errorURL));
      int validationErrorSeverity = -1;
      if( !isSameFile) {
    	  // Error is in referenced file. Pull from prefs. 
          int referencedFileSeverity = Platform.getPreferencesService().getInt(XMLCorePlugin.getDefault().getBundle().getSymbolicName(), 
        		  XMLCorePreferenceNames.INDICATE_REFERENCED_FILE_CONTAINS_ERRORS, 0, getPreferenceScopes());
          // ignore = -1,  warning = 1,  error = 2
          if( referencedFileSeverity == 1 ) {
        	  validationErrorSeverity = ValidationMessage.SEV_LOW;
          } else if( referencedFileSeverity == 2) {
        	  validationErrorSeverity = ValidationMessage.SEV_NORMAL;
          } // else leave as -1 and ignore
      } else {
    	  validationErrorSeverity = (severity == SEV_ERROR ? ValidationMessage.SEV_NORMAL : ValidationMessage.SEV_LOW);
      }
      successfullyAdded = true;
      if( validationErrorSeverity == -1 ) {
    	  // Only possible if error is in different file and user chose to ignore
    	  return successfullyAdded;
      }
      
      
      // Add to the appropriate list if nested error support is off or
      // this message is for the current file.
      if (!WRAPPER_ERROR_SUPPORT_ENABLED || isSameFile) {
    	// effectively just isSameFile since WRAPPER_ERROR_SUPPORT_ENABLED is always true?

        ValidationMessage valmes = new ValidationMessage(message, line,
            column, validating_file_uri, key, messageArguments);
        valmes.setSeverity(validationErrorSeverity);
        messages.add(valmes);
      }
      // If nested error support is enabled create a nested error.
      else if (WRAPPER_ERROR_SUPPORT_ENABLED)
      {
        String nesteduri = errorURL.toExternalForm();
        ValidationMessage nestedmess = new ValidationMessage(message, line,
            column, nesteduri, key, messageArguments);
        nestedmess.setSeverity(validationErrorSeverity);
        
        ValidationMessage container = (ValidationMessage) nestedMessages.get(nesteduri);
        if(container == null)
        {
          container = new ValidationMessage(NLS.bind(XMLValidationMessages._UI_REF_FILE_ERROR_MESSAGE, new Object [] { nesteduri }), 1, 0, nesteduri);
       
          // Initially set the nested error to a warning. This will automatically be changed
          // to an error if a nested message has a severity of error.
          container.setSeverity(ValidationMessage.SEV_LOW);
          nestedMessages.put(nesteduri, container);
          messages.add(container);
        }
        container.addNestedMessage(nestedmess);
      }
    }
    return successfullyAdded;
  }
  
  private IScopeContext[] getPreferenceScopes() {
	  if( sameFilePreferenceContext == null ) {
		  sameFilePreferenceContext = createPreferenceScopes(validating_file_url);
	  }
	  return sameFilePreferenceContext;
  }
  
	private IScopeContext[] createPreferenceScopes(URL url) {
		IProject p = null;
		// on null, fall through to non-project-specific defaults
		if (url == null) {
			try {
				URI uri = new URI(url.toString());
				IFile[] matching = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri);
				if (matching != null && matching.length > 0) {
					p = matching[0].getProject();
				}
			}
			catch (URISyntaxException urie) {
				// shouldn't happen, but ignore
			}
		}
		return createPreferenceScopesFromProject(p);
	}
  private IScopeContext[] createPreferenceScopesFromProject(IProject project) {
	  if (project != null && project.isAccessible()) {
		  final ProjectScope projectScope = new ProjectScope(project);
		  if (projectScope.getNode(XMLCorePlugin.getDefault().getBundle().getSymbolicName()).getBoolean(XMLCorePreferenceNames.USE_PROJECT_SETTINGS, false))
			return new IScopeContext[]{projectScope, new InstanceScope(), new DefaultScope()};
	  }
	  return new IScopeContext[]{ InstanceScope.INSTANCE, DefaultScope.INSTANCE};
  }

  
  /**
   * Add a nested message to the validation information.
   * 
   * @param message The string message of the validation message.
   * @param line The line location of the validation message.
   * @param column The column location of the validation message.
   * @param uri The URI of the validation message.
   * @param severity The severity of the validation message.
   */
//  public void addNestedMessage(String message, int line, int column, String uri, int severity)
//  {
//    ValidationMessage nestedmess = new ValidationMessage(message, line, column, uri);
//    if(severity == SEV_WARNING)
//    {
//      nestedmess.setSeverity(ValidationMessage.SEV_LOW);
//    }
//    else
//    {
//      nestedmess.setSeverity(ValidationMessage.SEV_NORMAL);
//    }
//    ValidationMessage container = (ValidationMessage)nestedMessages.get(uri);
//    if(container == null)
//    {
//      container = new ValidationMessage(XMLCoreValidationPlugin.getResourceString(_UI_REF_FILE_ERROR_MESSAGE, uri), 1, 0, validating_file_uri);
//      // Initially set the nested error to a warning. This will automatically be changed
//      // to an error if a nested message has a severity of error.
//      container.setSeverity(ValidationMessage.SEV_LOW);
//      nestedMessages.put(uri, container);
//      messages.add(container);
//    }
//    container.addNestedMessage(nestedmess);
//  }

  /**
   * @see org.eclipse.wsdl.validate.ValidationReport#getValidationMessages()
   */
  public ValidationMessage[] getValidationMessages()
  {
    return (ValidationMessage[])messages.toArray(new ValidationMessage[messages.size()]);
  }

  public HashMap getNestedMessages()
  {
    return nestedMessages;
  }
  
  /**
   * Put the URI in a standard format.
   * 
   * @param uri The URI to put into a standard format.
   * @return The standard format of the URI.
   */
  private String normalize(String uri)
  {
//    if(uri.startsWith("platform:"))
//    {
//      try
//      {
//        uri = Platform.resolve(new URL(uri)).toString();
//      }
//      catch(Exception e)
//      {
//      }
//    }
    uri = uri.replaceAll("%20"," "); //$NON-NLS-1$ //$NON-NLS-2$
    uri = uri.replaceAll("%5E", "^"); //$NON-NLS-1$ //$NON-NLS-2$
    uri = uri.replace('\\','/');
    
    return uri;
  }
  
}
