content: /**
   *  Copyright (c) 2015-2016 Angelo ZERR.
   *  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:
   *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
   */
  package ts.resources;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.CompletableFuture;
  
  import ts.TypeScriptException;
  import ts.client.CodeEdit;
  import ts.client.CommandNames;
  import ts.client.FileSpan;
  import ts.client.ITypeScriptServiceClient;
  import ts.client.Location;
  import ts.client.ScriptKindName;
  import ts.client.codefixes.CodeAction;
  import ts.client.completions.CompletionEntry;
  import ts.client.completions.ICompletionEntryFactory;
  import ts.client.configure.ConfigureRequestArguments;
  import ts.client.diagnostics.DiagnosticEvent;
  import ts.client.diagnostics.DiagnosticEventBody;
  import ts.client.format.FormatCodeSettings;
  import ts.client.jsdoc.TextInsertion;
  import ts.client.navbar.NavigationBarItemRoot;
  import ts.client.occurrences.OccurrencesResponseItem;
  import ts.client.quickinfo.QuickInfo;
  import ts.client.refactors.ApplicableRefactorInfo;
  import ts.client.refactors.RefactorEditInfo;
  import ts.client.references.ReferencesResponseBody;
  import ts.client.rename.RenameResponseBody;
  import ts.internal.LocationReader;
  import ts.utils.CompletableFutureUtils;
  
  /**
   * Abstract TypeScript file.
   *
   */
  public abstract class AbstractTypeScriptFile implements ITypeScriptFile {
  
  	private final ITypeScriptProject tsProject;
  	private final ScriptKindName scriptKind;
  
  	private boolean dirty;
  	protected final Object synchLock = new Object();
  	private boolean opened;
  
  	private final List<INavbarListener> listeners;
  	private NavigationBarItemRoot navbar;
  	private FormatCodeSettings formatOptions;
  	private boolean configureAlreadyDone;
  	private boolean disableChanged;
  
  	private CompletableFuture navbarPromise;
  
  	public AbstractTypeScriptFile(ITypeScriptProject tsProject, ScriptKindName scriptKind) {
  		this.tsProject = tsProject;
  		this.scriptKind = scriptKind;
  		this.listeners = new ArrayList<INavbarListener>();
  		this.setDirty(false);
  		this.configureAlreadyDone = false;
  	}
  
  	@Override
  	public ITypeScriptProject getProject() {
  		return tsProject;
  	}
  
  	@Override
  	public ScriptKindName getScriptKind() {
  		return scriptKind;
  	}
  
  	public void setDirty(boolean dirty) {
  		this.dirty = dirty;
  	}
  
  	@Override
  	public boolean isDirty() {
  		return dirty;
  	}
  
  	@Override
  	public Location getLocation(int position) throws TypeScriptException {
  		return new LocationReader(getContents(), position).getLineOffset();
  	}
  
  	@Override
  	public int getPosition(int line, int offset) throws TypeScriptException {
  		// TODO: implement that
  		throw new UnsupportedOperationException();
  	}
  
  	@Override
  	public int getPosition(Location loc) throws TypeScriptException {
  		return getPosition(loc.getLine(), loc.getOffset());
  	}
  
  	@Override
  	public void open() throws TypeScriptException {
  		((TypeScriptProject) tsProject).openFile(this);
  		this.opened = true;
  	}
  
  	@Override
  	public void close() throws TypeScriptException {
  		((TypeScriptProject) tsProject).closeFile(this);
  		this.opened = false;
  	}
  
  	@Override
  	public boolean isOpened() {
  		return opened;
  	}
  
  	void setOpened(boolean opened) {
  		this.opened = opened;
  	}
  
  	@Override
  	public CompletableFuture<List<CompletionEntry>> completions(int position, ICompletionEntryFactory factory)
  			throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		String prefix = null;
  		return client.completions(this.getName(), line, offset, factory);
  	}
  
  	@Override
  	public CompletableFuture<List<FileSpan>> definition(int position) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.definition(this.getName(), line, offset);
  	}
  
  	@Override
  	public CompletableFuture<QuickInfo> quickInfo(int position) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.quickInfo(this.getName(), line, offset);
  	}
  
  	@Override
  	public CompletableFuture<List<DiagnosticEvent>> geterr() throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		return client.geterr(new String[] { getName() }, 0);
  	}
  
  	@Override
  	public CompletableFuture<List<CodeEdit>> format(int startPosition, int endPosition) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		this.ensureFormatCodeSettings(client);
  		Location start = this.getLocation(startPosition);
  		Location end = this.getLocation(endPosition);
  		return client.format(this.getName(), start.getLine(), start.getOffset(), end.getLine(), end.getOffset());
  	}
  
  	private void ensureFormatCodeSettings(ITypeScriptServiceClient client) throws TypeScriptException {
  		FormatCodeSettings oldFormatOptions = formatOptions;
  		FormatCodeSettings newFormatOptions = getFormatOptions();
  		if (!configureAlreadyDone || !newFormatOptions.equals(oldFormatOptions)) {
  			configureAlreadyDone = true;
  			client.configure(new ConfigureRequestArguments().setFile(getName()).setFormatOptions(formatOptions));
  		}
  	}
  
  	@Override
  	public CompletableFuture<DiagnosticEventBody> semanticDiagnosticsSync(Boolean includeLinePosition)
  			throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		return client.semanticDiagnosticsSync(getName(), includeLinePosition);
  	}
  
  	@Override
  	public CompletableFuture<DiagnosticEventBody> syntacticDiagnosticsSync(Boolean includeLinePosition)
  			throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		return client.syntacticDiagnosticsSync(getName(), includeLinePosition);
  	}
  
  	// @Override
  	// public CompletableFuture<DiagnosticEventBody> diagnostics(Boolean
  	// includeLinePosition) throws TypeScriptException {
  	// this.synch();
  	// ITypeScriptServiceClient client = tsProject.getClient();
  	// if (tsProject.canSupport(CommandNames.SemanticDiagnosticsSync)) {
  	// // TypeScript >=2.0.3, uses syntactic/semantic command names which
  	// // seems having better performance.
  	// return client.syntacticDiagnosticsSync(getName(), includeLinePosition)
  	// .thenApply(client.semanticDiagnosticsSync(getName(),
  	// includeLinePosition));
  	// } else {
  	// // FIXME
  	// // client.geterr(new String[] { file.getName() }, 0, collector);
  	// }
  	// }
  
  	@Override
  	public FormatCodeSettings getFormatOptions() {
  		formatOptions = tsProject.getProjectSettings().getFormatOptions();
  		return formatOptions;
  	}
  
  	public void setFormatOptions(FormatCodeSettings formatOptions) {
  		this.formatOptions = formatOptions;
  	}
  
  	@Override
  	public CompletableFuture<ReferencesResponseBody> references(int position) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.references(this.getName(), line, offset);
  	}
  
  	@Override
  	public CompletableFuture<List<OccurrencesResponseItem>> occurrences(int position) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.occurrences(this.getName(), line, offset);
  	}
  
  	@Override
  	public CompletableFuture<RenameResponseBody> rename(int position, Boolean findInComments, Boolean findInStrings)
  			throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.rename(this.getName(), line, offset, findInComments, findInStrings);
  	}
  
  	@Override
  	public CompletableFuture<List<FileSpan>> implementation(int position) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.implementation(this.getName(), line, offset);
  	}
  
  	@Override
  	public CompletableFuture<TextInsertion> docCommentTemplate(int position) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(position);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		return client.docCommentTemplate(this.getName(), line, offset);
  	}
  
  	@Override
  	public CompletableFuture<List<CodeAction>> getCodeFixes(int startPosition, int endPosition,
  			List<Integer> errorCodes) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location startLocation = this.getLocation(startPosition);
  		int startLine = startLocation.getLine();
  		int startOffset = startLocation.getOffset();
  		Location endLocation = this.getLocation(endPosition);
  		int endLine = endLocation.getLine();
  		int endOffset = endLocation.getOffset();
  		return client.getCodeFixes(this.getName(), this, startLine, startOffset, endLine, endOffset, errorCodes);
  	}
  
  	@Override
  	public void compileOnSaveEmitFile(Boolean forced) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		// FIXME client.compileOnSaveEmitFile(getName(), forced);
  	}
  
  	@Override
  	public CompletableFuture<List<ApplicableRefactorInfo>> getApplicableRefactors(int startPosition,
  			Integer endPosition) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location startLocation = this.getLocation(startPosition);
  		int line = startLocation.getLine();
  		int offset = startLocation.getOffset();
  		if (endPosition == null) {
  			return client.getApplicableRefactors(this.getName(), line, offset);
  		}
  		Location endLocation = this.getLocation(endPosition);
  		int endLine = endLocation.getLine();
  		int endOffset = endLocation.getOffset();
  		return client.getApplicableRefactors(this.getName(), line, offset, endLine, endOffset);
  	}
  
  	@Override
  	public CompletableFuture<RefactorEditInfo> getEditsForRefactor(int startPosition, Integer endPosition,
  			String refactor, String action) throws TypeScriptException {
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		Location location = this.getLocation(startPosition);
  		int line = location.getLine();
  		int offset = location.getOffset();
  		if (endPosition == null) {
  			return client.getEditsForRefactor(this.getName(), line, offset, refactor, action);
  		}
  		Location endLocation = this.getLocation(endPosition);
  		int endLine = endLocation.getLine();
  		int endOffset = endLocation.getOffset();
  		return client.getEditsForRefactor(this.getName(), line, offset, endLine, endOffset, refactor, action);
  	}
  
  	@Override
  	public void addNavbarListener(INavbarListener listener) {
  		synchronized (listeners) {
  			if (!listeners.contains(listener)) {
  				listeners.add(listener);
  			}
  		}
  		if (navbar != null) {
  			listener.navBarChanged(navbar);
  		}
  	}
  
  	private void fireNavBarListeners(NavigationBarItemRoot navbar) {
  		synchronized (listeners) {
  			for (INavbarListener listener : listeners) {
  				listener.navBarChanged(navbar);
  			}
  		}
  	}
  
  	@Override
  	public void removeNavbarListener(INavbarListener listener) {
  		synchronized (listeners) {
  			listeners.remove(listener);
  		}
  	}
  
  	@Override
  	public void refreshNavBar() throws TypeScriptException {
  		if (listeners.isEmpty()) {
  			return;
  		}
  		this.synch();
  		ITypeScriptServiceClient client = tsProject.getClient();
  		// cancel last navigation bar/tree if needed.
  		CompletableFutureUtils.cancel(navbarPromise);
  		if (tsProject.canSupport(CommandNames.NavTree)) {
  			// when TypeScript 2.0.6 is consummed, use "navtree" to fill the
  			// Outline
  			// see
  			// https://github.com/Microsoft/TypeScript/pull/11532#issuecomment-254804923
  			navbarPromise = client.navtree(this.getName(), this).thenAccept(item -> {
  				AbstractTypeScriptFile.this.navbar = new NavigationBarItemRoot(item);
  				fireNavBarListeners(navbar);
  				navbarPromise = null;
  			});
  		} else {
  			navbarPromise = client.navbar(this.getName(), this).thenAccept(item -> {
  				AbstractTypeScriptFile.this.navbar = new NavigationBarItemRoot(item);
  				fireNavBarListeners(navbar);
  				navbarPromise = null;
  			});
  		}
  	}
  
  	@Override
  	public NavigationBarItemRoot getNavBar() {
  		return navbar;
  	}
  
  	@Override
  	public synchronized void synch() throws TypeScriptException {
  		if (!isDirty()) {
  			// no need to synchronize the file content with tsserver.
  			return;
  		}
  		switch (tsProject.getProjectSettings().getSynchStrategy()) {
  		case RELOAD:
  			// reload strategy : store the content of the ts file in a temporary
  			// file and call reload command.
  			tsProject.getClient().updateFile(this.getName(), this.getContents());
  			setDirty(false);
  			break;
  		case CHANGE:
  			// change strategy: wait until "change" command is not finished.
  			while (isDirty()) {
  				try {
  					synchronized (synchLock) {
  						synchLock.wait(5);
  					}
  				} catch (InterruptedException e) {
  					throw new TypeScriptException(e);
  				}
  			}
  			break;
  		}
  
  	}
  
  	public void setDisableChanged(boolean disableChanged) {
  		this.disableChanged = disableChanged;
  	}
  
  	public boolean isDisableChanged() {
  		return disableChanged;
  	}
  }
  