# Arguments are:
# 1. Working directory.
# 2. Rope folder

import io
import sys
import json
import traceback
import rope

from rope.base import libutils
from rope.refactor.rename import Rename
from rope.refactor.extract import ExtractMethod, ExtractVariable
import rope.base.project
import rope.base.taskhandle

WORKSPACE_ROOT = sys.argv[1]
ROPE_PROJECT_FOLDER = '.vscode/.ropeproject'


class RefactorProgress():
    """
    Refactor progress information
    """

    def __init__(self, name='Task Name', message=None, percent=0):
        self.name = name
        self.message = message
        self.percent = percent


class ChangeType():
    """
    Change Type Enum
    """
    EDIT = 0
    NEW = 1
    DELETE = 2


class Change():
    """
    """
    EDIT = 0
    NEW = 1
    DELETE = 2

    def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""):
        self.filePath = filePath
        self.diff = diff
        self.fileMode = fileMode


class BaseRefactoring(object):
    """
    Base class for refactorings
    """

    def __init__(self, project, resource, name="Refactor", progressCallback=None):
        self._progressCallback = progressCallback
        self._handle = rope.base.taskhandle.TaskHandle(name)
        self._handle.add_observer(self._update_progress)
        self.project = project
        self.resource = resource
        self.changes = []

    def _update_progress(self):
        jobset = self._handle.current_jobset()
        if jobset and not self._progressCallback is None:
            progress = RefactorProgress()
            # getting current job set name
            if jobset.get_name() is not None:
                progress.name = jobset.get_name()
            # getting active job name
            if jobset.get_active_job_name() is not None:
                progress.message = jobset.get_active_job_name()
            # adding done percent
            percent = jobset.get_percent_done()
            if percent is not None:
                progress.percent = percent
            if not self._progressCallback is None:
                self._progressCallback(progress)

    def stop(self):
        self._handle.stop()

    def refactor(self):
        try:
            self.onRefactor()
        except rope.base.exceptions.InterruptedTaskError:
            # we can ignore this exception, as user has cancelled refactoring
            pass

    def onRefactor(self):
        """
        To be implemented by each base class
        """
        pass


class RenameRefactor(BaseRefactoring):

    def __init__(self, project, resource, name="Rename", progressCallback=None, startOffset=None, newName="new_Name"):
        BaseRefactoring.__init__(self, project, resource,
                                 name, progressCallback)
        self._newName = newName
        self.startOffset = startOffset

    def onRefactor(self):
        renamed = Rename(self.project, self.resource, self.startOffset)
        changes = renamed.get_changes(self._newName, task_handle=self._handle)
        for item in changes.changes:
            if isinstance(item, rope.base.change.ChangeContents):
                self.changes.append(
                    Change(item.resource.real_path, ChangeType.EDIT, item.get_description()))
            else:
                raise Exception('Unknown Change')


class ExtractVariableRefactor(BaseRefactoring):

    def __init__(self, project, resource, name="Extract Variable", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False):
        BaseRefactoring.__init__(self, project, resource,
                                 name, progressCallback)
        self._newName = newName
        self._startOffset = startOffset
        self._endOffset = endOffset
        self._similar = similar
        self._global = global_

    def onRefactor(self):
        renamed = ExtractVariable(
            self.project, self.resource, self._startOffset, self._endOffset)
        changes = renamed.get_changes(
            self._newName, self._similar, self._global)
        for item in changes.changes:
            if isinstance(item, rope.base.change.ChangeContents):
                self.changes.append(
                    Change(item.resource.real_path, ChangeType.EDIT, item.get_description()))
            else:
                raise Exception('Unknown Change')


class ExtractMethodRefactor(ExtractVariableRefactor):

    def __init__(self, project, resource, name="Extract Method", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False):
        ExtractVariableRefactor.__init__(self, project, resource,
                                         name, progressCallback, startOffset=startOffset, endOffset=endOffset, newName=newName, similar=similar, global_=global_)

    def onRefactor(self):
        renamed = ExtractMethod(
            self.project, self.resource, self._startOffset, self._endOffset)
        changes = renamed.get_changes(
            self._newName, self._similar, self._global)
        for item in changes.changes:
            if isinstance(item, rope.base.change.ChangeContents):
                self.changes.append(
                    Change(item.resource.real_path, ChangeType.EDIT, item.get_description()))
            else:
                raise Exception('Unknown Change')


class RopeRefactoring(object):

    def __init__(self):
        self.default_sys_path = sys.path
        self._input = io.open(sys.stdin.fileno(), encoding='utf-8')

    def _rename(self, filePath, start, newName):
        """
        Extracts a variale
        """
        project = rope.base.project.Project(
            WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False)
        resourceToRefactor = libutils.path_to_resource(project, filePath)
        refactor = RenameRefactor(
            project, resourceToRefactor, startOffset=start, newName=newName)
        refactor.refactor()
        changes = refactor.changes
        project.close()
        valueToReturn = []
        for change in changes:
            valueToReturn.append({'diff': change.diff})
        return valueToReturn

    def _extractVariable(self, filePath, start, end, newName):
        """
        Extracts a variale
        """
        project = rope.base.project.Project(
            WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False)
        resourceToRefactor = libutils.path_to_resource(project, filePath)
        refactor = ExtractVariableRefactor(
            project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName, similar=True)
        refactor.refactor()
        changes = refactor.changes
        project.close()
        valueToReturn = []
        for change in changes:
            valueToReturn.append({'diff': change.diff})
        return valueToReturn

    def _extractMethod(self, filePath, start, end, newName):
        """
        Extracts a method
        """
        project = rope.base.project.Project(
            WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False)
        resourceToRefactor = libutils.path_to_resource(project, filePath)
        refactor = ExtractMethodRefactor(
            project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName, similar=True)
        refactor.refactor()
        changes = refactor.changes
        project.close()
        valueToReturn = []
        for change in changes:
            valueToReturn.append({'diff': change.diff})
        return valueToReturn

    def _serialize(self, identifier, results):
        """
        Serializes the refactor results
        """
        return json.dumps({'id': identifier, 'results': results})

    def _deserialize(self, request):
        """Deserialize request from VSCode.

        Args:
            request: String with raw request from VSCode.

        Returns:
            Python dictionary with request data.
        """
        return json.loads(request)

    def _process_request(self, request):
        """Accept serialized request from VSCode and write response.
        """
        request = self._deserialize(request)
        lookup = request.get('lookup', '')

        if lookup == '':
            pass
        elif lookup == 'rename':
            changes = self._rename(request['file'], int(
                request['start']), request['name'])
            return self._write_response(self._serialize(request['id'], changes))
        elif lookup == 'extract_variable':
            changes = self._extractVariable(request['file'], int(
                request['start']), int(request['end']), request['name'])
            return self._write_response(self._serialize(request['id'], changes))
        elif lookup == 'extract_method':
            changes = self._extractMethod(request['file'], int(
                request['start']), int(request['end']), request['name'])
            return self._write_response(self._serialize(request['id'], changes))

    def _write_response(self, response):
        sys.stdout.write(response + '\n')
        sys.stdout.flush()

    def watch(self):
        self._write_response("STARTED")
        while True:
            try:
                self._process_request(self._input.readline())
            except Exception as ex:
                message = ""
                try:
                    message = ex.message
                except:
                    pass

                message = message + '  \n' + traceback.format_exc()
                jsonMessage = {'error': True, 'message': message,
                               'traceback': traceback.format_exc()}
                sys.stderr.write(json.dumps(jsonMessage))
                sys.stderr.flush()

if __name__ == '__main__':
    RopeRefactoring().watch()
