package floobits.impl;

import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import floobits.common.EditorScheduler;
import floobits.common.interfaces.IDoc;
import floobits.common.interfaces.IFactory;
import floobits.common.interfaces.IFile;
import floobits.common.protocol.handlers.FlooHandler;
import floobits.utilities.Flog;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class FactoryImpl implements IFactory {

    private final ContextImpl context;
    private final EditorScheduler editor;
    private final LocalFileSystem instance = LocalFileSystem.getInstance();

    public FactoryImpl(ContextImpl context, EditorScheduler editor) {
        this.context = context;
        this.editor = editor;
    }

    private VirtualFile getVirtualFile(String relPath) {
        String absPath = context.absPath(relPath);
        if (absPath == null) {
            Flog.debug("No absPath for %s", relPath);
            return null;
        }
        VirtualFile fileByPath = instance.findFileByPath(absPath);
        if (fileByPath != null && fileByPath.isValid()) {
            return fileByPath;
        }
        Flog.debug("No fileByPath for %s %s", relPath, absPath);
        return null;
    }

    public String getPathForDoc(Document document) {
        if (document == null) {
            return null;
        }
        VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
        if (virtualFile == null) {
            return null;
        }

        String path;
        try {
            path = virtualFile.getPath();
        } catch (NullPointerException e) {
            return null;
        }
        if (!context.isShared(path)) {
            return null;
        }

        return path;
    }

    public IDoc makeVFile(FileImpl vFile) {
        Document document = FileDocumentManager.getInstance().getDocument(vFile.virtualFile);
        if (document == null) {
            return null;
        }
        return new DocImpl(context, document);
    }

    @Override
    public void clearReadOnlyState() {
        for (String path : readOnlyBufferIds) {
            VirtualFile file = getVirtualFile(path);
            if (file == null) {
                continue;
            }
            Document document = FileDocumentManager.getInstance().getDocument(file);
            if (document == null) {
                continue;
            }
            document.setReadOnly(false);
        }
        readOnlyBufferIds.clear();
    }

    @Override
    public void goToLastHighlight() {
        FlooHandler handler = context.getFlooHandler();
        if (handler == null) {
            return;
        }
        handler.editorEventHandler.goToLastHighlight();
    }

    @Override
    public void removeHighlightsForUser(int userID) {
        HashMap<String, LinkedList<RangeHighlighter>> integerRangeHighlighterHashMap = DocImpl.highlights.get(userID);
        if (integerRangeHighlighterHashMap == null) {
            return;
        }

        for (Map.Entry<String, LinkedList<RangeHighlighter>> entry : integerRangeHighlighterHashMap.entrySet()) {
            removeHighlight(userID, entry.getKey());
        }
    }

    @Override
    public boolean openFile(File file) {
        VirtualFile floorc = LocalFileSystem.getInstance().findFileByIoFile(file);
        if (floorc == null) {
            return false;
        }
        FileEditorManager.getInstance(context.project).openFile(floorc, true);
        return true;
    }

    @Override
    public void removeHighlight(final Integer userId, final String path) {
        final IFile iFile = findFileByPath(path);
        if (iFile == null) {
            return;
        }
        editor.queue(new Runnable() {
            @Override
            public void run() {
                IDoc iDoc = getDocument(iFile);
                if (iDoc == null) {
                    return;
                }
                iDoc.removeHighlight(userId, path);
            }
        });
    }

    @Override
    public void clearHighlights() {
        HashMap<Integer, HashMap<String, LinkedList<RangeHighlighter>>> highlights = DocImpl.highlights;
        if (highlights.size() <= 0) {
            return;
        }
        for (Map.Entry<Integer, HashMap<String, LinkedList<RangeHighlighter>>> entry : highlights.entrySet()) {
            HashMap<String, LinkedList<RangeHighlighter>> highlightsForUser = entry.getValue();
            if (highlightsForUser == null || highlightsForUser.size() <= 0) {
                continue;
            }
            Integer user_id = entry.getKey();
            for (Map.Entry<String, LinkedList<RangeHighlighter>> integerLinkedListEntry: highlightsForUser.entrySet()) {
                removeHighlight(user_id, integerLinkedListEntry.getKey());
            }
        }
    }

    @Override
    public IFile findFileByPath(String path) {
        String absPath = context.absPath(path);
        if (absPath == null) {
            return null;
        }
        VirtualFile fileByPath = instance.findFileByPath(absPath);
        if (fileByPath != null && fileByPath.isValid()) {
            return new FileImpl(fileByPath);
        }
        return null;
    }
    
    @Override
    public IFile getOrCreateFile(String path) {
        IFile fileByPath = findFileByPath(path);
        if (fileByPath != null) {
            return fileByPath;
        }
        return createFile(path);
    }

    @Override
    public IFile createFile(String path) {
        File file = new File(context.absPath(path));
        String name = file.getName();
        String parentPath = file.getParent();
        try {
            VfsUtil.createDirectories(parentPath);
        } catch (IOException e) {
            Flog.error("Create directories error %s", e);
            context.errorMessage("The Floobits plugin was unable to create directories for file.");
            return null;
        }
        VirtualFile parent = LocalFileSystem.getInstance().findFileByPath(parentPath);
        if (parent == null) {
            Flog.error("Virtual file is null? %s", parentPath);
            return null;
        }
        VirtualFile newFile;
        try {
            newFile = parent.findOrCreateChildData(context, name);
        } catch (Throwable e) {
            Flog.error("Create file error %s", e);
            context.errorMessage(String.format("The Floobits plugin was unable to create a file: %s.", path));
            return null;
        }
        return new FileImpl(newFile);
    }


    @Override
    public IFile findFileByIoFile(File file) {
        VirtualFile fileByIoFile = VfsUtil.findFileByIoFile(file, true);
        if (fileByIoFile == null) {
            return null;
        }
        return new FileImpl(fileByIoFile);
    }

    @Override
    public IDoc getDocument(IFile virtualFile) {
        if (virtualFile == null) {
            return null;
        }
        Document document;
        try {
            document = FileDocumentManager.getInstance().getDocument(((FileImpl) virtualFile).virtualFile);
        } catch (RuntimeException e) {
            // We've seen an java.io.EOFException here before.
            Flog.error(e);
            return null;
        }
        if (document == null) {
            return null;
        }
        return new DocImpl(context, document);
    }

    @Override
    public IDoc getDocument(String relPath) {
        IFile fileByPath = findFileByPath(context.absPath(relPath));
        return getDocument(fileByPath);
    }

    @Override
    public IFile createDirectories(String path) {
        VirtualFile directory = null;
        try {
            directory = VfsUtil.createDirectories(path);
        } catch (IOException e) {
            Flog.error(e);
        }

        if (directory == null) {
            Flog.warn("Failed to create directories %s %s", path);
            return null;
        }
        return new FileImpl(directory);
    }
}