/*******************************************************************************
 * Copyright (c) 2004, 2013 IBM Corporation and others.
 *
 * 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
 *******************************************************************************/
package org.eclipse.core.internal.runtime;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Bundle;

/**
 * This class can only be used if OSGi plugin is available
 */
public class DataArea {
	private static final String OPTION_DEBUG = "org.eclipse.equinox.common/debug"; //$NON-NLS-1$;
	private static final String REQUIRES_EXPLICIT_INIT = "osgi.dataAreaRequiresExplicitInit"; //$NON-NLS-1$;

	/* package */static final String F_META_AREA = ".metadata"; //$NON-NLS-1$
	/* package */static final String F_PLUGIN_DATA = ".plugins"; //$NON-NLS-1$
	/* package */static final String F_LOG = ".log"; //$NON-NLS-1$
	/* package */static final String F_TRACE = "trace.log"; //$NON-NLS-1$

	/**
	 * Internal name of the preference storage file (value <code>"pref_store.ini"</code>) in this plug-in's (read-write) state area.
	 */
	/* package */static final String PREFERENCES_FILE_NAME = "pref_store.ini"; //$NON-NLS-1$

	private IPath location; //The location of the instance data
	private boolean initialized = false;

	protected synchronized void assertLocationInitialized() throws IllegalStateException {
		if (location != null && initialized)
			return;
		Activator activator = Activator.getDefault();
		if (activator == null)
			throw new IllegalStateException(CommonMessages.activator_not_available);
		Location service = activator.getInstanceLocation();
		if (service == null)
			throw new IllegalStateException(CommonMessages.meta_noDataModeSpecified);

		boolean explicitInitRequired = Boolean.valueOf(Activator.getContext().getProperty(REQUIRES_EXPLICIT_INIT));
		if (explicitInitRequired && !service.isSet()) {
			// See bug 514333: don't allow clients to initialize instance location if the instance area is not explicitly defined yet
			throw new IllegalStateException(CommonMessages.meta_instanceDataUnspecified);
		}

		try {
			// This will try to init url either from the specified location value or from service default
			URL url = service.getURL();
			if (url == null)
				throw new IllegalStateException(CommonMessages.meta_instanceDataUnspecified);
			// TODO assume the URL is a file: 
			// Use the new File technique to ensure that the resultant string is 
			// in the right format (e.g., leading / removed from /c:/foo etc)
			location = new Path(new File(url.getFile()).toString());
			initializeLocation();
		} catch (CoreException e) {
			throw new IllegalStateException(e.getMessage());
		}
	}

	public IPath getMetadataLocation() throws IllegalStateException {
		assertLocationInitialized();
		return location.append(F_META_AREA);
	}

	public IPath getInstanceDataLocation() throws IllegalStateException {
		assertLocationInitialized();
		return location;
	}

	/**
	 * Returns the local file system path of the log file, or the default log location
	 * if the log is not in the local file system.
	 */
	public IPath getLogLocation() throws IllegalStateException {
		//make sure the log location is initialized if the instance location is known
		if (isInstanceLocationSet())
			assertLocationInitialized();
		FrameworkLog log = Activator.getDefault().getFrameworkLog();
		if (log != null) {
			java.io.File file = log.getFile();
			if (file != null)
				return new Path(file.getAbsolutePath());
		}
		if (location == null)
			throw new IllegalStateException(CommonMessages.meta_instanceDataUnspecified);
		return location.append(F_META_AREA).append(F_LOG);
	}

	public IPath getTraceLocation() throws IllegalStateException {

		DebugOptions debugOptions = Activator.getDefault().getDebugOptions();
		if (debugOptions == null) {
			return null;
		}
		File file = debugOptions.getFile();
		if (file == null) {
			return null;
		}
		return new Path(file.getAbsolutePath());
	}

	/**
	 * Returns true if the instance location has been set, and <code>false</code> otherwise.
	 */
	private boolean isInstanceLocationSet() {
		Activator activator = Activator.getDefault();
		if (activator == null)
			return false;
		Location service = activator.getInstanceLocation();
		if (service == null)
			return false;
		return service.isSet();
	}

	/**
	 * Returns the read/write location in which the given bundle can manage private state.
	 */
	public IPath getStateLocation(Bundle bundle) throws IllegalStateException {
		assertLocationInitialized();
		return getStateLocation(bundle.getSymbolicName());
	}

	public IPath getStateLocation(String bundleName) throws IllegalStateException {
		assertLocationInitialized();
		return getMetadataLocation().append(F_PLUGIN_DATA).append(bundleName);
	}

	public IPath getPreferenceLocation(String bundleName, boolean create) throws IllegalStateException {
		IPath result = getStateLocation(bundleName);
		if (create)
			result.toFile().mkdirs();
		return result.append(PREFERENCES_FILE_NAME);
	}

	private void initializeLocation() throws CoreException {
		// check if the location can be created
		if (location.toFile().exists()) {
			if (!location.toFile().isDirectory()) {
				String message = NLS.bind(CommonMessages.meta_notDir, location);
				throw new CoreException(new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IRuntimeConstants.FAILED_WRITE_METADATA, message, null));
			}
		}
		//try infer the device if there isn't one (windows)
		if (location.getDevice() == null)
			location = new Path(location.toFile().getAbsolutePath());
		createLocation();
		initialized = true;
	}

	private void createLocation() throws CoreException {
		// append on the metadata location so that the whole structure is created.  
		File file = location.append(F_META_AREA).toFile();
		try {
			file.mkdirs();
		} catch (Exception e) {
			String message = NLS.bind(CommonMessages.meta_couldNotCreate, file.getAbsolutePath());
			throw new CoreException(new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IRuntimeConstants.FAILED_WRITE_METADATA, message, e));
		}
		if (!file.canWrite()) {
			String message = NLS.bind(CommonMessages.meta_readonly, file.getAbsolutePath());
			throw new CoreException(new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IRuntimeConstants.FAILED_WRITE_METADATA, message, null));
		}
		// set the log file location now that we created the data area
		IPath logPath = location.append(F_META_AREA).append(F_LOG);
		try {
			Activator activator = Activator.getDefault();
			if (activator != null) {
				FrameworkLog log = activator.getFrameworkLog();
				if (log != null)
					log.setFile(logPath.toFile(), true);
				else if (debug())
					System.out.println("ERROR: Unable to acquire log service. Application will proceed, but logging will be disabled."); //$NON-NLS-1$
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		// set the trace file location now that we created the data area
		IPath tracePath = location.append(F_META_AREA).append(F_TRACE);
		Activator activator = Activator.getDefault();
		if (activator != null) {
			DebugOptions debugOptions = activator.getDebugOptions();
			if (debugOptions != null) {
				debugOptions.setFile(tracePath.toFile());
			} else {
				System.out.println("ERROR: Unable to acquire debug service. Application will proceed, but debugging will be disabled."); //$NON-NLS-1$
			}
		}
	}

	private boolean debug() {
		Activator activator = Activator.getDefault();
		if (activator == null)
			return false;
		DebugOptions debugOptions = activator.getDebugOptions();
		if (debugOptions == null)
			return false;
		return debugOptions.getBooleanOption(OPTION_DEBUG, false);
	}
}
