import os
from pathlib import Path
from grapycal.extension.utils import list_to_dict
from grapycal.utils.httpResource import HttpResource
from matplotlib.style import available
from objectsync import IntTopic, SObject, StringTopic
from grapycal.utils.io import read_workspace
import logging
logger = logging.getLogger(__name__)

class FileView(SObject):
    frontend_type = 'FileView'
    def build(self, name,**kwargs):
        super().build(**kwargs)
        self.name = self.add_attribute('name',StringTopic,name)
        self.editable = self.add_attribute('editable',IntTopic,False)
        
    def init(self):
        self.register_service('ls',self.ls)
        self.register_service('get_workspace_metadata',self.get_workspace_metadata)
        self.register_service('open_workspace',self.open_workspace)
        self.register_service('is_empty',self.is_empty)
        self.metadata_cache = {}

    def ls(self,path):
        raise NotImplementedError()
    
    def is_empty(self,path):
        return len(self.ls(path)) == 0

    def get_workspace_metadata(self,path):
        raise NotImplementedError()
    
    def open_workspace(self,path):
        self._server.globals.workspace._open_workspace_callback(path)

class LocalFileView(FileView):
    def build(self, **kwargs):
        super().build(**kwargs)
        self.editable.set(True)
        self.register_service('add_file',self.add_file)
        self.register_service('add_dir',self.add_dir)
        self.register_service('delete',self.delete)
        
    def ls(self,path):
        # root is cwd
        root = os.getcwd()
        path = path.replace('./','')
        path = os.path.join(root,path)
        if not os.path.exists(path):
            return []
        if os.path.isfile(path):
            return []
        result = []
        for f in os.listdir(path):
            if os.path.isdir(os.path.join(path,f)):
                if f.startswith('.'):
                    continue
                if f == '__pycache__':
                    continue
                result.append({'name':f,'type':'dir'})
            else:
                if f.endswith('.grapycal'):
                    result.append({'name':f,'path':f,'type':'workspace'})
        return result
    
    def get_workspace_metadata(self,path):
        if path in self.metadata_cache:
            return self.metadata_cache[path]
        version, metadata, _ = read_workspace(path,metadata_only=True)
        self.metadata_cache[path] = metadata
        return metadata
    
    def add_file(self,path):
        if not path.endswith('.grapycal'):
            path += '.grapycal'
        root = os.getcwd()
        path = path.replace('./','')
        path = os.path.join(root,path)
        if os.path.exists(path):
            return False
        self._server.globals.workspace._open_workspace_callback(path,no_exist_ok=True)
    
    def add_dir(self,path):
        root = os.getcwd()
        path = path.replace('./','')
        path = os.path.join(root,path)
        if os.path.exists(path):
            return False
        os.mkdir(path)
        self._server.globals.workspace.send_status_message(f'Created directory {path}')
        return True
    
    def delete(self,path):
        '''
        Delete file or dir
        '''
        root = os.getcwd()
        path = path.replace('./','')
        path = os.path.join(root,path)
        if not os.path.exists(path):
            return False
        if os.path.isfile(path):
            os.remove(path)
        else:
            os.rmdir(path)
        return True

def path2str(path):
    return str(path).replace('\\','/')
class RemoteFileView(FileView):
    '''
    format:
    {
        "name": "workspaces",
        "files": [
            {
                "name": "Welcome.grapycal",
                "version": "0.9.0",
                "extensions": [
                    {
                        "name": "grapycal_builtin",
                        "version": "0.9.0"
                    }
                ]
            }
        ],
        "dirs": [
            {
                "name": "grapycal_torch",
                "files": [
                    {
                        "name": "ImageEdit.grapycal",
                        "version": "0.9.0",
                        "extensions": [
                            {
                                "name": "grapycal_torch",
                                "version": "0.1.2"
                            },
                            {
                                "name": "grapycal_builtin",
                                "version": "0.9.0"
                            }
                        ]
                    }
                ],
                "dirs": []
            }
        ]
    }
    '''
    def build(self, url:str, **kwargs):
        super().build(**kwargs)
        self.url = url
        self.metadata = HttpResource(f'{self.url}metadata.json',dict)
    
    async def ls(self,path):
        metadata = await self.metadata.get()
        if metadata is None:
            logger.error(f'Cannot get metadata from {self.url}')
            return []
        path = Path(path)

        dir = metadata
        subdirs = list_to_dict(dir['dirs'],'name')
        # find dir in metadata
        for p in path.parts:
            if p not in subdirs:
                return []
            dir = subdirs[p]

        result = []
        for f in dir['files']:
            if f['name'].endswith('.grapycal'):
                result.append({'type':'workspace','path':f['name']} | f)
        for d in dir['dirs']:
            result.append({'name':d['name'],'type':'dir','path':d['name']})
        return result
    
    async def get_workspace_metadata(self,path):
        metadata = await self.metadata.get()
        if metadata is None:
            return []
        path = Path(path)

        dir = metadata
        # find dir in metadata
        for p in path.parts[:-1]:
            subdirs = list_to_dict(dir['dirs'],'name')
            if p not in subdirs:
                return []
            dir = subdirs[p]

        file = list_to_dict(dir['files'],'name')[path.parts[-1]]

        return file
    
    async def open_workspace(self, path:str):
        # download workspace
        path = path.replace('./','')
        logger.info(f'Downloading workspace {path}')

        resource = HttpResource(path2str(os.path.join(self.url,'files',path)),bytes)
        
        if not await resource.is_avaliable():
            logger.error(f'Cannot get workspace from {self.url}')
            return
        
        remote_file = await resource.get()
        
        local_path = os.getcwd().replace('\\','/')+'/'+path.replace('/','_')
        for i in range(100):
            import re
            # change name.grapycal to name_1.grapycal or name_1.grapycal to name_2.grapycal
            match = re.match(r'(.+/)(.+?)(_\d+)?(\.grapycal)',local_path)
            if match is None:
                raise Exception(f'Invalid path {local_path}')
            path, name, number, ext = match.groups()
            if i==0:
                prefix = self.name.get()+'_'
                # [a-zA-Z0-9_]
                prefix = re.sub(r'[^a-zA-Z0-9_]','',prefix)
                postfix = ''
            else:
                prefix = ''
                postfix = f'_{i}'
            local_path = f'{path}{prefix}{name}{postfix}{ext}'

            if not os.path.exists(local_path):
                break

        logger.info(f'Writing workspace {local_path}')
        with open(local_path,'wb') as f:
            f.write(remote_file)

        # open workspace
        
        self._server.globals.workspace._open_workspace_callback(local_path)