"""
Python interface to librclone.so using ctypes

Create an rclone object

    rclone = Rclone(shared_object="/path/to/librclone.so")

Then call rpc calls on it

    rclone.rpc("rc/noop", a=42, b="string", c=[1234])

When finished, close it

    rclone.close()
"""

__all__ = ('Rclone', 'RcloneException')

import os
import json
import subprocess
from ctypes import *

class RcloneRPCString(c_char_p):
    """
    This is a raw string from the C API

    With a plain c_char_p type, ctypes will replace it with a
    regular Python string object that cannot be used with
    RcloneFreeString. Subclassing prevents it, while the string
    can still be retrieved from attribute value.
    """
    pass

class RcloneRPCResult(Structure):
    """
    This is returned from the C API when calling RcloneRPC
    """
    _fields_ = [("Output", RcloneRPCString),
                ("Status", c_int)]

class RcloneException(Exception):
    """
    Exception raised from rclone

    This will have the attributes:

    output - a dictionary from the call
    status - a status number
    """
    def __init__(self, output, status):
        self.output = output
        self.status = status
        message = self.output.get('error', 'Unknown rclone error')
        super().__init__(message)

class Rclone():
    """
    Interface to Rclone via librclone.so

    Initialise with shared_object as the file path of librclone.so
    """
    def __init__(self, shared_object=f"./librclone{'.dll' if os.name == 'nt' else '.so'}"):
        self.rclone = CDLL(shared_object)
        self.rclone.RcloneRPC.restype = RcloneRPCResult
        self.rclone.RcloneRPC.argtypes = (c_char_p, c_char_p)
        self.rclone.RcloneFreeString.restype = None
        self.rclone.RcloneFreeString.argtypes = (c_char_p,)
        self.rclone.RcloneInitialize.restype = None
        self.rclone.RcloneInitialize.argtypes = ()
        self.rclone.RcloneFinalize.restype = None
        self.rclone.RcloneFinalize.argtypes = ()
        self.rclone.RcloneInitialize()
    def rpc(self, method, **kwargs):
        """
        Call an rclone RC API call with the kwargs given.

        The result will be a dictionary.

        If an exception is raised from rclone it will of type
        RcloneException.
        """
        method = method.encode("utf-8")
        parameters = json.dumps(kwargs).encode("utf-8")
        resp = self.rclone.RcloneRPC(method, parameters)
        output = json.loads(resp.Output.value.decode("utf-8"))
        self.rclone.RcloneFreeString(resp.Output)
        status = resp.Status
        if status != 200:
            raise RcloneException(output, status)
        return output
    def close(self):
        """
        Call to finish with the rclone connection
        """
        self.rclone.RcloneFinalize()
        self.rclone = None
    @classmethod
    def build(cls, shared_object):
        """
        Builds rclone to shared_object if it doesn't already exist

        Requires go to be installed
        """
        if os.path.exists(shared_object):
            return
        print("Building "+shared_object)
        subprocess.check_call(["go", "build", "--buildmode=c-shared", "-o", shared_object, "github.com/rclone/rclone/librclone"])