# this part formalizes the hypothesis
import os
import numpy as np
import altair as alt
import pandas as pd
from spacy.tokens import Span, Doc
from typing import List, Dict, Callable, TypeVar
from collections import defaultdict
from ..processor import spacy_annotator
from ..utils import Registrable, Store, convert_doc, load_json, dump_json, CACHE_FOLDERS, normalize_file_path

from ..targets.instance import Instance
from ..targets.interfaces import RewriteOutputMeta, InstanceKey
from ..build_blocks.wrapper import BuildBlockWrapper

import logging
logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


T = TypeVar('T')


class Rewrite(Registrable, Store):
    """The base class for rewrite rules.
        
    Parameters
    ----------
    rid : str
        The id/name of the rewrite rule.
    category : str
        If the rewrite is "manual" or "auto".
    description : str
        The description of the attribute/group.
    target_cmd : str, optional
        The target to be rewritten. It has to be a 
        member of ``Instance.instance_entries``, 
        by default 'question'
    
    Attributes
    ----------
    instance_keys : Dict[InstanceKey, bool]
        The dict that saves all the keys of rewitten instances
        generated by this rewrite rule.
    examples : List[str]
        The example strings.
    """
    def __init__(self, 
        rid: str,
        category: str, 
        description: str,
        target_cmd: str='question', **kwargs):
        self.rid = rid
        self.target = 'question'
        self.category = category
        self.target_cmd = target_cmd
        self.description = description
        self.instance_keys: Dict[InstanceKey, bool] = defaultdict(None)
        self.bbw = BuildBlockWrapper()
        self.bbw.parse_cmd_to_operator(target_cmd, 'attr')
        self._set_main_target(target_cmd)
        self.examples: List[str] = []
    
    def key(self) -> str:
        """Return the key of the rewrite rule, which is ``rid``.
        
        Returns
        -------
        str
            The key/rid.
        """
        return self.rid
    
    def retrive_instance_key(self, qid: str) -> InstanceKey:
        """Given a qid, retrive its version rewritten by this rewrite rule, 
        and return its key.
        
        Parameters
        ----------
        qid : str
            The instance qid.
        
        Returns
        -------
        InstanceKey
            The key for the rewritten instance. If not existing, return None.
        """
        qids = { k.qid: k for k in self.instance_keys }
        if qid in qids:
            return qids[qid]
        return None
    
    def add_instance(self, key: InstanceKey) -> bool:
        """Add an instance key to ``self.instance_keys``
        
        Parameters
        ----------
        key : InstanceKey
            The key to add.
        
        Returns
        -------
        bool
            If the add is successful.
        """
        if not key in self.instance_keys:
            self.instance_keys[key] = True
        return True

    def remove_instance(self, key: InstanceKey) -> bool:
        """Delete an instance key from ``self.instance_keys``
        
        Parameters
        ----------
        key : InstanceKey
            The key to delete.
        
        Returns
        -------
        bool
            If the removal is successful.
        """
        if key in self.instance_keys:
            del self.instance_keys[key]

    def get_instances(self) -> Dict[InstanceKey, bool]:
        """
        Get the instance dict.
        
        Returns
        -------
        Dict[InstanceKey, bool]
            The instance dict.
        """
        return self.instance_keys

    def _set_main_target(self, cmd: str, instance: 'Instance'=None):
        if instance:
            if 'sentence' in cmd:
                self.target = 'context'
            else:
                for target in ['groundtruth', 'prediction'] + instance.entries:
                    if target in cmd:
                        self.target = target
                        break
        else:
            if 'sentence' in cmd:
                self.target = 'context'
            else:
                for target in ['context', 'question', 'groundtruth', 'prediction']:
                    if target in cmd:
                        self.target = target
                        break

    def rewrite_instances(self, 
        instances: List[Instance]) -> List[RewriteOutputMeta]:
        """
        Rewrite a list of instances. This function calls 
        ``self.rewrite_one_instance`` for each instance.

        
        Parameters
        ----------
        instances : List[Instance]
            The list of instances to be rewritten.
        
        Returns
        -------
        List[RewriteOutputMeta]
            The list of rewritten returns.
        """
        return [
            self.rewrite_one_instance(instance)
            for instance in instances
        ]

    def _get_target(self, instance: Instance) -> Doc:
        """Get the target to be rewritten from the instance.
        
        Parameters
        ----------
        instance : Instance
            The instance to be rewritten.
        
        Returns
        -------
        Doc
            The target doc to be rewritten.
        """
        if not instance:
            return None
            
        data = self.bbw.test_instances([{ instance.rid: instance }])
        if instance.key() in data:
            key = data[instance.key()]
            if type(key) == str and instance.get_entry(key) != None:
                return convert_doc(instance.get_entry(key), strict_format='doc')
            return convert_doc(key, strict_format='doc')
        return None

    def rewrite_one_instance(self, instance: Instance) -> RewriteOutputMeta:
        """Rewrite an instance.
        
        Parameters
        ----------
        instance : Instance
            The instance to be rewritten.
        
        Returns
        -------
        RewriteOutputMeta
            A named tuple, with rid and the rewritten text of the instance target.
            If the instance cannot be rewritten, return None.
        """
        self._set_main_target(self.target_cmd, instance)
        if not self.target:
            return None
        if self.retrive_instance_key(instance.qid):
            return None
        ori_doc = instance.get_entry(self.target)
        if not ori_doc:
            return None
        ori_text = ori_doc.doc.text
        ori_text_to_rewrite = self._get_target(instance)
        if not ori_text_to_rewrite:
            return None
        if type(ori_text_to_rewrite) in [Doc, Span]:
            ori_text_to_rewrite = ori_text_to_rewrite.doc.text
        rewritten_str = self._rewrite_target(instance)
        if not rewritten_str:
            return None
        output_text = ori_text.replace(ori_text_to_rewrite, rewritten_str).strip()
        if output_text.strip() != ori_text.strip():
            return RewriteOutputMeta(rid=self.rid, text=output_text)
        return None

    def _rewrite_target(self, instance: Instance) -> str:
        """
        The rewritting function that rewrite the target and 
        return the rewritten string.
        
        Parameters
        ----------
        instance : Instance
            The instance to be rewritten.
        
        Returns
        -------
        str
            The rewritten instance string. If cannot be rewritten,
            return None
        
        Raises
        ------
        NotImplementedError
            This needs to be implemented in the sub-classes.
        """
        raise NotImplementedError

    def print_rewrite(self, prev, next, curr, curr_new):
        raise NotImplementedError
    
    def show_changed_instances(self,
        instance_hash: Dict[str, 'Instance']={}, 
        instance_hash_rewritten: Dict[str, 'Instance']={}, 
        model: str=None,
        ouput_directions: List[int]=[]) -> None:
        """Print the instances that are rewritten.
        
        Parameters
        ----------
        instance_hash : Dict[InstanceKey, Instance]
            A dict that saves all the *original* instances, by default None. 
            It denotes by the corresponding instance keys.
            If ``None``, resolve to ``Instance.instance_hash``.
        instance_hash_rewritten : Dict[InstanceKey, Instance]
            A dict that saves all the *rewritten* instances, by default None. 
            It denotes by the corresponding instance keys.
            If ``None``, resolve to ``Instance.instance_hash_rewritten``.
        model : str, optional
            The selected model, by default ``None``. 
            If ``None``, resolve to ``Instance.model``.
        ouput_directions: List[int]=[]
            It can be [-1, 0, 1], denoting flip to incorrect, unflip, and 
            flip to correct. By default [], which means no restriction on the direction.
        
        Returns
        -------
        None
        """
        flips = defaultdict(list)
        model = model or Instance.model
        instance_keys = self.instance_keys
        instance_hash = instance_hash or Instance.instance_hash
        instance_hash_rewritten = instance_hash_rewritten or Instance.instance_hash_rewritten
        for key in instance_keys:
            ori_key = InstanceKey(qid=key.qid, vid=0)
            ori_instance = Instance.get(ori_key, instance_hash, instance_hash_rewritten)
            edi_instance = Instance.get(key, instance_hash, instance_hash_rewritten)
            if ori_instance and edi_instance:
                flipped_direction = \
                    int (instance_hash_rewritten[key].get_perform(model) == 1) - \
                    int (instance_hash[ori_key].get_perform(model) == 1)
                flips[flipped_direction].append(key)
        for direction, filtered_instances in flips.items():
            if ouput_directions and not direction in ouput_directions:
                continue
            print(f'Flipped direction: {direction}')
            print ('---' * 11)
            for key in filtered_instances:
                ori_key = InstanceKey(qid=key.qid, vid=0)
                ori_instance = Instance.get(ori_key, instance_hash, instance_hash_rewritten)
                edi_instance = Instance.get(key, instance_hash, instance_hash_rewritten)
                ori_instance.show_instance()
                edi_instance.show_instance()

    def serialize(self, 
            instance_hash: Dict[InstanceKey, Instance]={},
            instance_hash_rewritten: Dict[InstanceKey, Instance]={},
            filtered_instances: List[InstanceKey]=None,
            model: str=None) -> Dict:
        """Seralize the instance into a json format, for sending over
        to the frontend.
        
        Returns
        -------
        Dict[str, Any]
            The serialized version.
        """
        model = model or Instance.model
        instance_hash = instance_hash or Instance.instance_hash
        instance_hash_rewritten = instance_hash_rewritten or Instance.instance_hash_rewritten
        if filtered_instances:
            qids = list(np.unique([i.qid for i in filtered_instances]))
        else:
            qids = None
        return {
            'rid': self.rid,
            'description': self.description,
            'target_cmd': getattr(self, 'target_cmd', None),
            'from_cmd': getattr(self, 'from_cmd', None),
            'to_cmd': getattr(self, 'to_cmd', None),
            'category': self.category,
            'target': self.target,
            'examples': getattr(self, 'examples', []),
            'counts': self.count_flips(Rewrite.get_delta_performance(self,
                qids, instance_hash, instance_hash_rewritten, model)['delta_f1s'])
        }
    
    def visualize_models(self, 
        instance_hash: Dict[InstanceKey, Instance]={},
        instance_hash_rewritten: Dict[InstanceKey, Instance]={},
        filtered_instances: List[InstanceKey]=None,
        models: str=[]):
        """
        Visualize the rewrite distribution. 
        It's a one-bar histogram that displays the count of instances rewritten, and
        the proportion of "flip_to_correct", "flip_to_incorrect", "unflip"
        Because of the flipping proportion, this historgram is different
        for each different model. 
        
        Parameters
        ----------
        instance_hash : Dict[InstanceKey, Instance]
            A dict that saves all the *original* instances, by default {}. 
            It denotes by the corresponding instance keys.
            If ``{}``, resolve to ``Instance.instance_hash``.
        instance_hash_rewritten : Dict[InstanceKey, Instance]
            A dict that saves all the *rewritten* instances, by default {}. 
            It denotes by the corresponding instance keys.
            If ``{}``, resolve to ``Instance.instance_hash_rewritten``.
        filtered_instances : List[InstanceKey], optional
            A selected list of instances. If given, only display the distribution
            of the selected instances, by default None
        models : List[str], optional
            A list of instances, with the bars for each group concated vertically.
            By default []. If [], resolve to ``[ Instance.model ]``.
        
        Returns
        -------
        alt.Chart
            An altair chart object. 
        """
        model = models or [ Instance.model ]
        instance_hash = instance_hash or Instance.instance_hash
        instance_hash_rewritten = instance_hash_rewritten or Instance.instance_hash_rewritten
        if not models:
            models = [ Instance.resolve_default_model(None) ]
        output = []
        for model in models:
            #Instance.set_default_model(model=model)
            data = self.serialize(instance_hash, instance_hash_rewritten, filtered_instances, model)
            for flip, count in data["counts"].items():
                output.append({
                    "flip": flip,
                    "count": count,
                    "model": model
                })
        df = pd.DataFrame(output)
        chart = alt.Chart(df).mark_bar().encode(
            y=alt.Y('model:N'),
            x=alt.X('count:Q', stack="zero"),
            color=alt.Color('flip:N', scale=alt.Scale(
                range=["#1f77b4", "#ff7f0e", "#c7c7c7"],
                domain=["flip_to_correct", "flip_to_incorrect", "unflip"])),
            tooltip=['model:N', 'count:Q', 'correctness:N']
        ).properties(width=100)#.configure_facet(spacing=5)#
        return chart
    

    def visualize_delta_confidence_per_model(self, 
        instance_hash: Dict[InstanceKey, Instance]={},
        instance_hash_rewritten: Dict[InstanceKey, Instance]={},
        filtered_instances: List[InstanceKey]=None,
        model: str=None):
        """
        Visualize the rewrite distribution, in terms of model confidence. 
        It's a histogram that shows the distribution of the delta confidence.
        This historgram is different
        for each different model. 
        
        Parameters
        ----------
        instance_hash : Dict[InstanceKey, Instance]
            A dict that saves all the *original* instances, by default {}. 
            It denotes by the corresponding instance keys.
            If ``{}``, resolve to ``Instance.instance_hash``.
        instance_hash_rewritten : Dict[InstanceKey, Instance]
            A dict that saves all the *rewritten* instances, by default {}. 
            It denotes by the corresponding instance keys.
            If ``{}``, resolve to ``Instance.instance_hash_rewritten``.
        filtered_instances : List[InstanceKey], optional
            A selected list of instances. If given, only display the distribution
            of the selected instances, by default None
        model : str, optional
            The selected model, by default ``None``. 
            If ``None``, resolve to ``Instance.model``.
        
        Returns
        -------
        alt.Chart
            An altair chart object. 
        """
        model = Instance.resolve_default_model(model)
        instance_hash = instance_hash or Instance.instance_hash
        instance_hash_rewritten = instance_hash_rewritten or Instance.instance_hash_rewritten
        output = []
        if filtered_instances:
            qids = list(np.unique([i.qid for i in filtered_instances]))
        else:
            qids = None
        data = Rewrite.get_delta_performance(self,
            qids, instance_hash, instance_hash_rewritten, model)['delta_confidences']
        output = [ {"delta_confidence": d} for d in data ] 
        df = pd.DataFrame(output)
        chart = alt.Chart(df).mark_bar().encode(
            y=alt.Y('count()'),
            x=alt.X('delta_confidence:Q', bin=True)
        ).properties(width=150, height=100, title=f'{self.rid} on {model}')#.configure_facet(spacing=5)#
        return chart
    
    def visualize_delta_confidence_models(self, 
        instance_hash: Dict[InstanceKey, Instance]={},
        instance_hash_rewritten: Dict[InstanceKey, Instance]={},
        filtered_instances: List[InstanceKey]=None,
        models: str=[]):
        """
        Visualize the delta confidence distribution for ALL the ``models``. 
        This function calls ``self.visualize_delta_confidence_per_model`` and concate the 
        charts returned for each model.
        """
        model = models or [ Instance.model ]
        if not models:
            models = [ Instance.resolve_default_model(None) ]
        output = []
        charts = [ self.visualize_delta_confidence_per_model(
            instance_hash=instance_hash,
            instance_hash_rewritten=instance_hash_rewritten,
            model=model,
            filtered_instances=filtered_instances,
        ) for model in models ]
        return alt.vconcat(*charts).resolve_scale(x="shared", y="shared", color="shared")

    def __repr__(self) -> str:
        """
        Override the print func by displaying the rid, descrption, and target cmd.
        """
        return '\n[rewrite]\n' + \
            '\t[rid]\t{0}\n'.format(self.rid) + \
            '\t[des]\t{0}\n'.format(self.description) + \
            '\t[target_cmd]\t{0}\n'.format(self.target_cmd) 

    def get_json(self):
        """Get the json version definition of the rewrite rule.
        
        Returns
        -------
        Dict[str, str]
            The json version definition of the built block, with:
            rid, category, description, target_cmd and class 
            in a dict.
        """
        return {
            'rid': self.rid,
            'category': self.category, 
            'description': self.description,
            'target_cmd': str(self.target_cmd),
            'class': self.__class__.__name__
        }

    @classmethod
    def create_from_json(cls, raw: Dict[str, str]) -> 'Rewrite':
        """
        Recreate the object from its seralized raw json.
        
        Parameters
        ----------
        raw : Dict[str, str]
            The json version definition of the rewrite rule, with 
            rid, category, description, target_cmd and class 
            in a dict.

        Returns
        -------
        Rewrite
            The re-created rewrite rule.
        """
        name = raw["class"]
        del raw["class"]
        if name == "SemanticRule": # route to the parent.
            name = "ReplacePattern"
        return cls.by_name(name)(**raw)

    @classmethod
    def create_with_cmd(cls, from_cmd: str, to_cmd: str, target_cmd: str, save: bool=True) -> 'Rewrite':
        if from_cmd is None or to_cmd is None or not target_cmd:
            return None
        if from_cmd and to_cmd:
            try:
                rewrite = cls.by_name("ReplacePattern")(from_cmd=from_cmd, to_cmd=to_cmd, target_cmd=target_cmd)
            except:
                rewrite = cls.by_name("ReplaceStr")(from_cmd=from_cmd, to_cmd=to_cmd, target_cmd=target_cmd)
            if rewrite.is_pure_str_replace():
                rewrite = cls.by_name("ReplaceStr")(from_cmd=from_cmd, to_cmd=to_cmd, target_cmd=target_cmd)
        else:
            rewrite = cls.by_name("ReplaceStr")(from_cmd=from_cmd, to_cmd=to_cmd, target_cmd=target_cmd)
        if save:
            Rewrite.save(rewrite)
        print(type(rewrite))
        return rewrite

    @classmethod
    def create_with_func(cls, rewrite_func: str, target_cmd: str, save: bool=True) -> 'Rewrite':
        try:
            rewrite = cls.by_name("RewriteCustomFunc")(rewrite_func=rewrite_func, target_cmd=target_cmd)
            if save:
                Rewrite.save(rewrite)
            return rewrite
        except Exception as e:
            rewrite = None
            raise(e)

    @classmethod
    def get_delta_performance(cls,
        rewrite: 'Rewrite',
        qids: List[str], 
        instance_hash: Dict[str, 'Instance']={}, 
        instance_hash_rewritten: Dict[str, 'Instance']={},
        model: str=None) -> List[float]:
        """Evaluate the current rewrite
        """
        instance_hash = instance_hash or Instance.instance_hash
        instance_hash_rewritten = instance_hash_rewritten or Instance.instance_hash_rewritten
        delta_f1s, delta_confidences, prediction_changed = [], [], 0
        if qids != None:
            instance_keys = { key: True for key in rewrite.instance_keys if key.qid in qids }
            #list(filter(lambda k: k.qid in qids, self.instance_keys))
        else: 
            instance_keys = rewrite.instance_keys
        TOTAL_SIZE = len(instance_hash)
        FILTERED_SIZE = len(qids) if qids else TOTAL_SIZE
        for key in instance_keys:
            ori_key = InstanceKey(qid=key.qid, vid=0)
            if key in instance_hash_rewritten and ori_key in instance_hash:
                delta_f1s.append(
                    int (instance_hash_rewritten[key].get_perform(model) == 1) - 
                    int (instance_hash[ori_key].get_perform(model) == 1)
                )
                delta_confidences.append(
                    float (instance_hash_rewritten[key].get_perform(model, 'confidence')) - 
                    float (instance_hash[ori_key].get_perform(model, 'confidence'))
                )
                p0 = instance_hash[ori_key].get_entry('prediction', model)
                p1 = instance_hash_rewritten[key].get_entry('prediction', model)
                if p0 and p1 and p0.label != p1.label:
                    prediction_changed += 1
        return {
            'delta_confidences': delta_confidences,
            'delta_f1s': delta_f1s,
            'prediction_changed': prediction_changed,
            'total_size': TOTAL_SIZE,
            'filtered_size': FILTERED_SIZE,
        }

    @classmethod
    def eval_stats(cls, 
        rewrite: 'Rewrite',
        qids: List[str], 
        instance_hash: Dict[str, 'Instance']={}, 
        instance_hash_rewritten: Dict[str, 'Instance']={}, 
        model: str=None) -> dict:
        """
        Evaluate the instance flipping status for a given 
        rewrite rule. 
        
        Parameters
        ----------
        rewrite : Rewrite
            A selected rewrite rule.
        qids : List[str]
            A selected list of qids to be tested.
        instance_hash : Dict[InstanceKey, Instance]
            A dict that saves all the *original* instances, by default {}. 
            It denotes by the corresponding instance keys.
            If ``{}``, resolve to ``Instance.instance_hash``.
        instance_hash_rewritten : Dict[InstanceKey, Instance]
            A dict that saves all the *rewritten* instances, by default {}. 
            It denotes by the corresponding instance keys.
            If ``{}``, resolve to ``Instance.instance_hash_rewritten``.
        model : str, optional
            A selected model to test correctness, by default ``None``. 
            If ``None``, resolve to ``Instance.model``.
        
        Returns
        -------
        dict
        
            .. code-block:: js
            
                {
                    'counts': {
                        'rewritten': The number of instances rewritten
                        'prediction_changed': The number of instances with predictions from the model changed.
                        'flips': The number of instances with their performance metric flipped.
                    },
                    'stats': {
                        'coverage': Ratio of instances rewritten.
                        'changed_coverage': The instances with predictions changed, over all instances.
                        'changed_rate': The instances with predictions changed, over all instances REWRITTEN.
                    }
                }
        """
        instance_hash = instance_hash or Instance.instance_hash
        instance_hash_rewritten = instance_hash_rewritten or Instance.instance_hash_rewritten
        info = cls.get_delta_performance(rewrite,
            qids, instance_hash, instance_hash_rewritten, model)
        return {
            'counts': {
                'rewritten': len(info['delta_f1s']),
                'prediction_changed': info['prediction_changed'],
                'flips': cls.count_flips(info['delta_f1s'])
            },
            'stats': {
                'coverage': len(info['delta_f1s']) / len(instance_hash) if instance_hash else 0,
                'changed_coverage': info['prediction_changed'] / len(instance_hash) if instance_hash else 0,
                'changed_rate': info['prediction_changed'] / len(info['delta_f1s']) if info['delta_f1s'] else 0,
            }
        }
    

    @classmethod
    def count_flips(cls, delta_performs: List[float]) -> Dict[str, float]:
        """Count the number of different flipping status, based on
        the delta performance list.
        
        Parameters
        ----------
        delta_performs : List[float]
            rewritten_instance.perform - instance.perform for each instance rewritten.
        
        Returns
        -------
        Dict[str, float]
            .. code-block:: js
            
                {
                    'flip_to_correct': a counting number,
                    'flip_to_incorrect': a counting number,
                    'unflip': a counting number
                }
        """
        return {
            'flip_to_correct': len([d for d in delta_performs if d > 0]),
            'flip_to_incorrect': len([d for d in delta_performs if d < 0]),
            'unflip': len([d for d in delta_performs if d == 0])
        }

    @classmethod
    def print_stats(self, rewrite: 'Rewrite', counts: Dict[str, int]) -> None:
        """Print the information.
        
        Parameters
        ----------
        rewrite : Rewrite
            The rewrite rule object.
        counts : Dict[str, int]
            The count output from ``self.count_flips``.
        
        Returns
        -------
        None
        """
        TOTAL_SIZE = counts['flip_to_correct'] + counts['flip_to_incorrect'] + counts['unflip']
        TOTAL_SIZE = TOTAL_SIZE if TOTAL_SIZE > 0 else 1
        output = f"NAME\t\t\t: {rewrite.rid}\n" + \
            "flip_to_correct\t\t: {} ({:.2%})\n".format(counts['flip_to_correct'], counts['flip_to_correct'] / TOTAL_SIZE) + \
            "flip_to_incorrect\t: {} ({:.2%})\n".format(counts['flip_to_incorrect'], counts['flip_to_incorrect'] / TOTAL_SIZE) + \
            "unflip\t\t\t: {} ({:.2%})\n".format(counts['unflip'], counts['unflip'] / TOTAL_SIZE)
        print('---' * 33)
        print(output)

    @classmethod
    def import_from_file(cls, file_name: str) -> Dict[str, 'Rewrite']:
        """
        Import the saved store frome a file. It recovers all the saved,
        json version of objects, and save them to the store_hash. 
        
        Parameters
        ----------
        file_name : str
            The name of the json file. It should be a file in 
            ``CACHE_FOLDERS["analysis"]``.
        
        Returns
        -------
        Dict[str, T]
            The restored hash, or ``Store._store_hash[cls]``.
        """
        try:
            rewrite_hash = {}
            if not file_name.endswith('.json'):
                file_name += '.json'
            raw_rewrites = load_json(os.path.join(CACHE_FOLDERS['analysis'], file_name))
            if raw_rewrites:
                for raw_rewrite in raw_rewrites:
                    name = raw_rewrite["class"]
                    del raw_rewrite["class"]
                    if name == "SemanticRule": # route to the parent.
                        name = "ReplacePattern"
                    rewrite = cls.by_name(name)(**raw_rewrite)
                    rewrite_hash[rewrite.rid] = rewrite
            return rewrite_hash
        except Exception as e:
            raise(e)
    
    @classmethod
    def remove_saved(cls, name: str) -> bool:
        """Remove the rewrite rule object from the store hash by querying its name.
        
        Parameters
        ----------
        name : str
            The name/rid of the rewrite rule.

        Returns
        -------
        bool
            If the removal is successful.
        """
        try:
            if Rewrite.exists(name):
                rewrite = Rewrite.get(name)
                if rewrite.__class__.__name__ == "SemanticRule":
                    rewrite._clear_rule()
                for key in rewrite.get_instances():
                    Instance.remove_saved(key)
                Rewrite._remove_by_name(name)
                return True
            return False
        except Exception as e:
            raise(e)
