import os
import sys
import subprocess
import shlex
import warnings
import time
import re
import tempfile
from pathlib import Path
import requests
import zipfile
import io
from tqdm import tqdm

class FarasaBase:
    task = None
    __base_dir = Path(__file__).parent.absolute()
    __bin_dir = f'{__base_dir}/farasa_bin'
    __bin_lib_dir = f'{__bin_dir}/lib'
    __APIs = {
        'segment':shlex.split(f'java -jar {__bin_lib_dir}/FarasaSegmenterJar.jar'),
        'stem' : shlex.split(f'java -jar {__bin_lib_dir}/FarasaSegmenterJar.jar -l true'),
        'NER' : shlex.split(f'java -jar {__bin_dir}/FarasaNERJar.jar'),
        'POS' : shlex.split(f'java -jar {__bin_dir}/FarasaPOSJar.jar'),
        'diacritize' : shlex.split(f'java -jar {__bin_dir}/FarasaDiacritizeJar.jar'),
    }
    __interactive = False
    __task_proc = None

    def __init__(self, interactive=False):
        print("perform system check...")
        print("check java version...")
        self._check_java_version()
        print("check toolkit binaries...")
        self._check_toolkit_binaries()
        Path(f'{self.__base_dir}/tmp').mkdir(exist_ok=True)
        print("Dependencies seem to be satisfied..")
        if interactive:
            self.__interactive = True
            warnings.warn("Be careful with large lines as they may break on interactive mode. You may switch to Standalone mode for such cases.")
            print(f"\033[37minitializing [{self.task.upper()}] task in \033[32mINTERACTIVE \033[37mmode...")
            self._initialize_task()
            print(f"task [{self.task.upper()}] is initialized interactively.")
        else:
            print(f"task [{self.task.upper()}] is initialized in \033[34mSTANDALONE \033[37mmode...")

    def _check_toolkit_binaries(self):
        download = False
        # check in bin folder:
        for jar in ('FarasaNERJar','FarasaPOSJar','FarasaDiacritizeJar'):
            if not Path(f'{self.__bin_dir}/{jar}.jar').is_file():
                download = True
                break
        if download or not Path(f'{self.__bin_lib_dir}/FarasaSegmenterJar.jar').is_file(): # last check for binaries in farasa_bin/lib
            print("some binaries are not existed..")
            self._download_binaries()

    def _get_content_with_progressbar(self, request):
        totalsize = int(request.headers.get('content-length',0))
        blocksize = 3413334
        bar = tqdm(total=totalsize, unit='iB',unit_scale=True, ncols=5, dynamic_ncols=True, file=sys.stdout)
        content = None
        for data in request.iter_content(blocksize):
            bar.update(len(data))
            if content is None:
                content = data
            else:
                content+=data
        bar.close()
        return content
            
    
    def _download_binaries(self):
        print("downloading zipped binaries...")
        try:
            binaries_url = 'https://github.com/MagedSaeed/farasapy/releases/download/toolkit-bins-released/farasa_bin.zip'
            binaries_request = requests.get(binaries_url, stream=True)
            # show the progress bar while getting content
            content_bytes = self._get_content_with_progressbar(binaries_request)
            print("extracting...")
            binzip = zipfile.ZipFile(io.BytesIO(content_bytes))
            binzip.extractall(path=self.__base_dir)
            print("toolkit binaries are downloaded and extracted.")
        except  Exception as e:
            print("an error occured")
            print(e)


            

    def _initialize_task(self):
        word = 'اختبار'
        word+='\n'
        bword = str.encode(word)
        self.__task_proc = subprocess.Popen(self.__APIs[self.task],
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
        return self._run_task_interactive(bword)

            

    def _check_java_version(self):
        try:
            version_proc_output = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT,encoding='utf8',text=True)
            version_pattern = r'\"(\d+\.\d+).*\"'
            java_version = float(re.search(version_pattern,version_proc_output).groups()[0])
            if java_version >= 1.7:
                print(f"Your java version is {java_version} which is compatiple with Farasa ")
            else:
                warnings.warn('You are using old version of java. Farasa is compatiple with Java 7 and above ')
        except subprocess.CalledProcessError as proc_err:
            print(proc_err)
            raise Exception("We could not check for java version on the machine. Please make sure you have installed Java 1.7+ and add it to your PATH.")


    def _run_task(self, btext):
        assert btext is not None
        with tempfile.NamedTemporaryFile(dir=f'{self.__base_dir}/tmp',) as itmp,\
                    tempfile.NamedTemporaryFile(dir=f'{self.__base_dir}/tmp',) as otmp:
            itmp.write(btext)
            itmp.flush() # https://stackoverflow.com/questions/46004774/python-namedtemporaryfile-appears-empty-even-after-data-is-written
            proc = subprocess.run(self.__APIs[self.task]+['-i',itmp.name,'-o',otmp.name],\
                                    capture_output=True)
            if proc.returncode == 0:
                return otmp.read().decode('utf8').strip()
            else:
                print("error occured!",otmp.read().decode('utf8').strip())
                print("return code:",proc.returncode)
                raise Exception('Internal Error occured!')

    
    def _run_task_interactive(self, btext):
        assert btext is not None and type(btext) == bytes
        assert self.__interactive == True 
        assert self.__task_proc is not None
        self.__task_proc.stdin.flush()
        self.__task_proc.stdin.write(btext)
        self.__task_proc.stdin.flush()
        output = self.__task_proc.stdout.readline().decode('utf8').strip()
        self.__task_proc.stdout.flush()
        return output
    
    def _do_task_interactive(self, strip_text):
        outputs = []
        for line in strip_text.split('\n'):
            newlined_line = line+'\n'
            byted_newlined_line = str.encode(newlined_line)
            output = self._run_task_interactive(byted_newlined_line)
            if output:
                outputs.append(output)
        return '\n'.join(outputs)
    
    def _do_task_standalone(self,strip_text):
        byted_strip_text = str.encode(strip_text)    
        return self._run_task(btext=byted_strip_text)

    def _do_task(self, text):
        strip_text = text.strip()
        if self.__interactive:
            return self._do_task_interactive(strip_text)        
        else:
            return self._do_task_standalone(strip_text)

    def terminate(self):
        self.__task_proc.terminate()
    
