/*
 * ====================================================================
 * 
 * The ObjectStyle Group Software License, Version 1.0
 * 
 * Copyright (c) 2005 The ObjectStyle Group and individual authors of the
 * software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 1.
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. 2. Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. 3. The end-user documentation
 * included with the redistribution, if any, must include the following
 * acknowlegement: "This product includes software developed by the ObjectStyle
 * Group (http://objectstyle.org/)." Alternately, this acknowlegement may
 * appear in the software itself, if and wherever such third-party
 * acknowlegements normally appear. 4. The names "ObjectStyle Group" and
 * "Cayenne" must not be used to endorse or promote products derived from this
 * software without prior written permission. For written permission, please
 * contact andrus@objectstyle.org. 5. Products derived from this software may
 * not be called "ObjectStyle" nor may "ObjectStyle" appear in their names
 * without prior written permission of the ObjectStyle Group.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the ObjectStyle Group. For more information on the ObjectStyle
 * Group, please see <http://objectstyle.org/> .
 *  
 */
package org.objectstyle.wolips.pbserver;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;

/**
 * @author mike
 */
public class PBServer {
	public static final int DEFAULT_PB_PORT = 8547;

	ServerSocket myServerSocket;

	private Thread myServerThread;

	boolean myRunning;

	public PBServer() {
		super();
	}

	public synchronized void start(int _port) throws IOException {
		myServerSocket = new ServerSocket(_port);
		myRunning = true;
		myServerThread = new Thread(new ServerSocketAcceptor());
		myServerThread.start();
	}

	public synchronized void stop() throws IOException {
		myRunning = false;
		myServerSocket.close();
	}

	private Element appendArray(Document _document) {
		Element arrayElement = _document.createElement("array");
		_document.appendChild(arrayElement);
		return arrayElement;
	}

	private Element appendString(Element _element, String _string) {
		Element stringElement = _element.getOwnerDocument().createElement("string");
		_element.appendChild(stringElement);
		Text stringText = _element.getOwnerDocument().createTextNode(_string);
		stringElement.appendChild(stringText);
		return stringElement;
	}

	private String text(Element _element, String _tagName) {
		NodeList elementsList = _element.getElementsByTagName(_tagName);
		String text;
		if (elementsList.getLength() > 0) {
			Element textElementContainer = (Element) elementsList.item(0);
			Text textNode = (Text) textElementContainer.getChildNodes().item(0);
			text = textNode.getNodeValue();
		} else {
			text = null;
		}
		return text;
	}

	public String[] strings(Element _parentElement, String _arrayName) {
		Element arrayParentElement = (Element) _parentElement.getElementsByTagName(_arrayName).item(0);
		Element arrayElement = (Element) arrayParentElement.getElementsByTagName("array").item(0);
		NodeList stringsList = arrayElement.getChildNodes();
		String[] strings = new String[stringsList.getLength()];
		for (int i = 0; i < strings.length; i++) {
			Element textElementContainer = (Element) stringsList.item(0);
			Text textNode = (Text) textElementContainer.getChildNodes().item(0);
			strings[i] = textNode.getNodeValue();
		}
		return strings;
	}

	private IProject project(Document _requestDocument, String _cookieName) {
		String cookie = text(_requestDocument.getDocumentElement(), _cookieName);
		IContainer container = ResourcesPlugin.getWorkspace().getRoot().getContainerForLocation(new Path(cookie));
		IProject project = container.getProject();
		return project;
	}

	private String path(Document _requestDocument) {
		String path = text(_requestDocument.getDocumentElement(), "path");
		return path;
	}

	public Document openProjectsAppropriateForFile(Document _requestDocument) throws ParserConfigurationException, TransformerFactoryConfigurationError, TransformerException {
		String path = path(_requestDocument);
		Document responseDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		IContainer[] containers = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocation(new Path(path));
		Element arrayElement = appendArray(responseDocument);
		for (int i = 0; i < containers.length; i++) {
			IProject project = containers[i].getProject();
			IFile xcodeFile = project.getFile(project.getName() + ".xcode");
			if (!xcodeFile.exists()) {
				xcodeFile = project.getFile(project.getName() + ".xcodeproj");
			}
			String xcodeFilePath = xcodeFile.getLocation().toOSString();

			if (containers[i].getName().endsWith(".woa")) {
				// <?xml version="1.0" encoding="UTF-8"?>
				// <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
				// "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
				// <plist version="1.0">
				// <dict>
				// <key>PBXProjectSourcePath</key>
				// <string>/Users/mschrag/TestApp/TestApp.xcodeproj</string>
				// </dict>
				// </plist>

				IFile pbDevelopmentResourceFile = containers[i].getFile(new Path("Contents").append("pbdevelopment.plist"));
				Document pbDevelopmentDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
				Element plistElement = pbDevelopmentDocument.createElement("plist");
				plistElement.setAttribute("version", "1.0");
				pbDevelopmentDocument.appendChild(plistElement);
				Element dictElement = pbDevelopmentDocument.createElement("dict");
				plistElement.appendChild(dictElement);

				Element keyElement = pbDevelopmentDocument.createElement("key");
				dictElement.appendChild(keyElement);
				keyElement.appendChild(pbDevelopmentDocument.createTextNode("PBXProjectSourcePath"));

				Element stringElement = pbDevelopmentDocument.createElement("string");
				dictElement.appendChild(stringElement);
				stringElement.appendChild(pbDevelopmentDocument.createTextNode(xcodeFilePath));

				Transformer transformer = TransformerFactory.newInstance().newTransformer();
				transformer.setOutputProperty(OutputKeys.INDENT, "yes");
				DOMSource source = new DOMSource(pbDevelopmentDocument);
				File pbDevelopmentFile = pbDevelopmentResourceFile.getLocation().toFile();
				StreamResult result = new StreamResult(pbDevelopmentFile);
				transformer.transform(source, result);
			}
			appendString(arrayElement, xcodeFilePath);
		}
		return responseDocument;
	}

	public Document targetsInProjectContainingFile(Document _requestDocument) throws ParserConfigurationException {
		IProject project = project(_requestDocument, "cookie");
		// String path = path(_requestDocument);
		// IContainer[] containers =
		// ResourcesPlugin.getWorkspace().getRoot().findContainersForLocation(new
		// Path(path));
		Document responseDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		appendString(appendArray(responseDocument), project.getName());
		appendString(appendArray(responseDocument), "Application Server");
		appendString(appendArray(responseDocument), "Web Server");
		return responseDocument;
	}

	public Document targetsInProject(/* Document _requestDocument */) throws ParserConfigurationException {
		// IProject project = project(_requestDocument, "cookie");
		Document responseDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		appendString(appendArray(responseDocument), "mockTarget");
		return responseDocument;
	}

	public void nameOfProject(Document _requestDocument, OutputStream _os) {
		IProject project = project(_requestDocument, "projectCookie");
		new PrintStream(_os, true).print(project.getName());
	}

	public void addFilesToProject(Document _requestDocument, OutputStream _os) {
		Element documentElement = _requestDocument.getDocumentElement();
		String[] addFiles = strings(documentElement, "addFiles");
		for (int i = 0; i < addFiles.length; i++) {
			System.out.println("PBServer.addFilesToProject: add files " + addFiles[i]);
		}
		// IProject project = project(_requestDocument, "toProject");
		// String nearFile = text(documentElement, "nearFile");
		// String preferredInsertionGroupName = text(documentElement,
		// "preferredInsertionGroupName");
		// String[] addToTargets = strings(documentElement, "addToTargets");
		// boolean copyIntoGroupFolder = booleanValue(documentElement,
		// "copyIntoGroupFolder");
		// boolean createGroupsRecursively = booleanValue(documentElement,
		// "createGroupsRecursively");
		new PrintStream(_os, true).print("YES");
	}

	public Document filesOfTypesInTargetOfProject(Document _requestDocument) throws ParserConfigurationException, CoreException {
		// stringbuffer.append("<filesOfTypesInTargetOfProject>");
		// stringbuffer.append("<cookie>" + s1 + "</cookie>");
		// stringbuffer.append("<target>" + s + "</target>");
		// stringbuffer.append("<typesArray>" + _xmlStringArray(nsarray) +
		// "</typesArray>");
		// stringbuffer.append("</filesOfTypesInTargetOfProject>");
		IProject project = project(_requestDocument, "cookie");
		Element documentElement = _requestDocument.getDocumentElement();
		// String targetName = text(documentElement, "target");
		String[] typesArray = strings(documentElement, "typesArray");
		FileTypeResourceVisitor visitor = new FileTypeResourceVisitor(typesArray);
		project.accept(visitor, IResource.DEPTH_INFINITE, IContainer.EXCLUDE_DERIVED);
		Document responseDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		Element arrayElement = appendArray(responseDocument);
		Iterator matchingResourcesIter = visitor.getMatchingFiles().iterator();
		while (matchingResourcesIter.hasNext()) {
			IResource resource = (IResource) matchingResourcesIter.next();
			appendString(arrayElement, resource.getLocation().toOSString());
		}
		return responseDocument;
	}

	public void nameOfTargetInProject(Document _requestDocument, OutputStream _os) {
		// stringbuffer.append("<nameOfTarget>");
		// stringbuffer.append("<targetCookie>" + s + "</targetCookie >");
		// stringbuffer.append("<projectCookie>" + s1 + "</projectCookie >");
		// stringbuffer.append("</nameOfTarget>");
		// IProject project = project(_requestDocument, "projectCookie");
		Element documentElement = _requestDocument.getDocumentElement();
		String targetCookie = text(documentElement, "targetCookie");
		new PrintStream(_os, true).print(targetCookie);
	}

	public void openFile(Document _requestDocument, OutputStream _os) {
		// stringbuffer.append("<OpenFile><filename>");
		// stringbuffer.append(s);
		// stringbuffer.append("</filename><linenumber>");
		// stringbuffer.append(i);
		// stringbuffer.append("</linenumber><message>");
		// stringbuffer.append(s1);
		// stringbuffer.append("</message></OpenFile>");
		String filename = text(_requestDocument.getDocumentElement(), "filename");
		String lineNumberStr = text(_requestDocument.getDocumentElement(), "linenumber");
		// String message = text(_requestDocument.getDocumentElement(),
		// "message");
		final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(filename));
		final int lineNumber = Integer.parseInt(lineNumberStr);
		if (file != null) {
			IWorkbenchWindow[] workbenchWindows = PlatformUI.getWorkbench().getWorkbenchWindows();
			for (int i = 0; i < workbenchWindows.length; i++) {
				final IWorkbenchPage workbenchPage = workbenchWindows[i].getActivePage();
				if (workbenchPage != null) {
					Display.getDefault().asyncExec(new Runnable() {
						public void run() {
							try {
								IEditorPart editorPart = IDE.openEditor(workbenchPage, file, true);
								if (editorPart != null && editorPart instanceof ITextEditor) {
									ITextEditor textEditor = (ITextEditor) editorPart;
									IDocumentProvider documentProvider = textEditor.getDocumentProvider();
									IDocument document = documentProvider.getDocument(textEditor.getEditorInput());
									int lineOffset = document.getLineOffset(lineNumber - 1);
									textEditor.selectAndReveal(lineOffset, 0);
								}
							} catch (Throwable t) {
								t.printStackTrace(System.out);
							}
						}
					});
				}
			}
		}
		new PrintStream(_os, true).print("YES");
	}

	public void addGroup(Document _requestDocument, OutputStream _os) {
		// stringbuffer.append("<addGroup>");
		// stringbuffer.append("<name>" + s + "</name >");
		// if(s1 != null)
		// stringbuffer.append("<path>" + s1 + "</path >");
		// stringbuffer.append("<projectCookie>" + s2 + "</projectCookie >");
		// if(s3 != null)
		// stringbuffer.append("<nearFile>" + s3 + "</nearFile >");
		// stringbuffer.append("</addGroup>");
		String name = text(_requestDocument.getDocumentElement(), "name");
		// String path = text(_requestDocument.getDocumentElement(), "path");
		System.out.println("PBServer.addGroup: name = " + name);
		// IProject project = project(_requestDocument, "projectCookie");
		// String nearFile = text(_requestDocument.getDocumentElement(),
		// "nearFile");
		new PrintStream(_os, true).print("YES");
	}

	public void addGroupToPreferredInsertionGroup(Document _requestDocument, OutputStream _os) {
		// stringbuffer.append("<addGroupToPreferredInsertionGroup>");
		// stringbuffer.append("<name>" + s + "</name >");
		// if(s1 != null)
		// stringbuffer.append("<path>" + s1 + "</path >");
		// stringbuffer.append("<projectCookie>" + s2 + "</projectCookie >");
		// if(s3 != null)
		// stringbuffer.append("<nearFile>" + s3 + "</nearFile >");
		// if(s4 != null)
		// stringbuffer.append("<preferredInsertionGroup>" + s4 +
		// "</preferredInsertionGroup >");
		// stringbuffer.append("</addGroupToPreferredInsertionGroup>");
		String name = text(_requestDocument.getDocumentElement(), "name");
		System.out.println("PBServer.addGroupToPreferredInsertionGroup: name = " + name);
		// String path = text(_requestDocument.getDocumentElement(), "path");
		// IProject project = project(_requestDocument, "projectCookie");
		// String nearFile = text(_requestDocument.getDocumentElement(),
		// "nearFile");
		// String preferredInsertionGroup =
		// text(_requestDocument.getDocumentElement(),
		// "preferredInsertionGroup");
		new PrintStream(_os, true).print("YES");
	}

	public void handleRequestDocument(Document _requestDocument, OutputStream _os) throws Throwable {
		Document responseDocument = null;
		String nodeName = _requestDocument.getDocumentElement().getNodeName();
		if ("openProjectsAppropriateForFile".equals(nodeName)) {
			responseDocument = openProjectsAppropriateForFile(_requestDocument);
		} else if ("targetsInProjectContainingFile".equals(nodeName)) {
			responseDocument = targetsInProjectContainingFile(_requestDocument);
		} else if ("targetsInProject".equals(nodeName)) {
			responseDocument = targetsInProject(/* _requestDocument */);
		} else if ("nameOfProject".equals(nodeName)) {
			nameOfProject(_requestDocument, _os);
		} else if ("addFilesToProject".equals(nodeName)) {
			addFilesToProject(_requestDocument, _os);
		} else if ("filesOfTypesInTargetOfProject".equals(nodeName)) {
			responseDocument = filesOfTypesInTargetOfProject(_requestDocument);
		} else if ("nameOfTarget".equals(nodeName)) {
			nameOfTargetInProject(_requestDocument, _os);
		} else if ("OpenFile".equals(nodeName)) {
			openFile(_requestDocument, _os);
		} else if ("addGroup".equals(nodeName)) {
			addGroup(_requestDocument, _os);
		} else if ("addGroupToPreferredInsertionGroup".equals(nodeName)) {
			addGroupToPreferredInsertionGroup(_requestDocument, _os);
		} else {
			System.out.println("PBServer.run: Unknown request: " + nodeName);
			// responseDocument = null;
		}
		if (responseDocument != null) {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			DOMSource source = new DOMSource(responseDocument);
			StreamResult result = new StreamResult(_os);
			transformer.transform(source, result);
		}
	}

	public class FileTypeResourceVisitor implements IResourceVisitor {
		private String[] myFileTypes;

		private List myMatchingFiles;

		public FileTypeResourceVisitor(String[] _fileTypes) {
			myFileTypes = _fileTypes;
			myMatchingFiles = new LinkedList();
		}

		public List getMatchingFiles() {
			return myMatchingFiles;
		}

		public boolean visit(IResource _resource) throws CoreException {
			boolean matched = false;
			for (int i = 0; !matched && i < myFileTypes.length; i++) {
				if (myFileTypes[i].equalsIgnoreCase(_resource.getFileExtension())) {
					myMatchingFiles.add(_resource);
					matched = true;
				}
			}
			return true;
		}
	}

	public class ServerSocketAcceptor implements Runnable {
		public void run() {
			while (myRunning) {
				try {
					Socket socket = myServerSocket.accept();
					OutputStream os = socket.getOutputStream();
					try {
						InputStream is = socket.getInputStream();
						StringBuffer sb = new StringBuffer();
						sb.append((char) is.read());
						while (is.available() > 0) {
							int ch = is.read();
							sb.append((char) ch);
						}
						Document requestDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(sb.toString())));
						handleRequestDocument(requestDocument, os);
					} finally {
						os.flush();
						os.close();
					}
				} catch (Throwable t) {
					t.printStackTrace(System.out);
				}
			}
		}
	}
}
