from history.historyrecord import *
from history.commentrecord import *
from history.ascfits import *

"""
The CXCHistory class will store all of the History and Comment
keywords for one HDU in the original order they were read in or
entered.  New records can only be appended to the list, so the
order of the records cannot be changed (i.e. no way to insert
record into the python list).
"""

class CXCHistory:

    def __init__ (self, content=None):
        """
        Creates an CXCHistory object containing a list of history and comment records
        from the input content.  Records will be stored in the order in which they were 
        read in.

        Parameters
        ----------
          content      : string or list of strings, optional
                         Text information for one or more records

        Returns
        -------
          None
 
        Raises
        ------
          For HistoryRecords:
            ValueError   : if record is empty
                           if both content and individual args are set
                           if no content and no tool name 
            TypeError    : if tool name is not a string
                           if parameter names is not a List
                           if parameter values is not a List
                           if parameter names are input but no corresponding values are given
                           if parameter values are input but no corresponding names are given
                           if length or parameter names List and values List are not equal
                           if parameter names List contains a datatype other than string
                           if parameter values List contains a datatype other than string
                           if parameter stacks is not a List of Lists
                           if parameter stacks are not the same length as names and values
                           if parameter stacks list elements contain a datatype other than string

          For CommentRecords:
            TypeError    : if provided content is not string type
            ValueError   : if content provided with is_blank=True

        Examples
        --------
        Acceptable inputs for content include the following:
        a) ASC-FITS compliant History records (commands used to create file):

          ["HISTORY  TOOL  :myTool  1998-07-29T00:00:00                             ASCnnnnn",
           "HISTORY  PARM  :infile=myInputFile                                      ASCnnnnn",
           "HISTORY  PARM  :infile=IsThis                                           ASCnnnnn",
           "HISTORY  PARM  :outfile=myOutputFileyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyASCnnnnn",
           "HISTORY  CONT  :yyyyyYYYY                                               ASCnnnnn"]

        b) Non-ASC-FITS compliant History records

           "HISTORY  Random string without label, string value, or ASC sequence number"

        c) Comment records

           "COMMENT  -------- Timing Component -------"

        d) PIXLIB history records

          ["HISTORY  PARM  :PIXLIB Version: 4.0.0                                   ASC00015",
           "HISTORY  PARM  :******     PIXLIB Parameter File      *****             ASC00016",
           "HISTORY  PARM  :/home/ascds/DS.l3/param/geom.par:                       ASC00017",
           "HISTORY  PARM  :instruments  = /export/CALDB/level3/data/chandra/defaultASC00018",
           "HISTORY  CONT  :/geom/telD1999-07-23geomN0006.fits(CALDB)               ASC00019",
           "HISTORY  PARM  :aimpoints    = /export/CALDB/level3/data/chandra/defaultASC00020",
           "HISTORY  CONT  :/aimpts/telD1999-07-23aimptsN0002.fits(CALDB)           ASC00021",
           "HISTORY  PARM  :tdet         = /export/CALDB/level3/data/chandra/defaultASC00022",
           "HISTORY  CONT  :/tdet/telD1999-07-23tdetN0001.fits(CALDB)               ASC00023",
           "HISTORY  PARM  :sky          = /export/CALDB/level3/data/chandra/defaultASC00024",
           "HISTORY  CONT  :/sky/telD1999-07-23skyN0002.fits(CALDB)                 ASC00025",
           "HISTORY  PARM  :shell        = /export/CALDB/level3/data/chandra/defaultASC00026",
           "HISTORY  CONT  :/sgeom/telD1999-07-23sgeomN0001.fits(CALDB)             ASC00027",
           "HISTORY  PARM  :obsfile      = nowT00:00:00()                           ASC00028"]

        e) ASC-FITS compliant History records with file stacks:

          ["HISTORY  TOOL  :dmimgcalc   2017-07-27T14:02:18                         ASC00882",
           "HISTORY  PARM  :infile=@imgcalc_infiles2.txt                            ASC00883",
           "HISTORY  STCK  :/data/L3/rhain/L3_unit_tests/xms/ens0476/input/acisf0501ASC00884",
           "HISTORY  CONT  :7_000N022_r0001b_cmspsf3.fits                           ASC00885",
           "HISTORY  STCK  :/data/L3/rhain/L3_unit_tests/xms/ens0476/input/acisf0501ASC00886",
           "HISTORY  CONT  :7_000N022_r0001b_cmspsf3.fits                           ASC00887",
           "HISTORY  STCK  :/data/L3/rhain/L3_unit_tests/xms/ens0476/input/acisf0501ASC00888",
           "HISTORY  CONT  :7_000N022_r0001b_cmspsf3.fits                           ASC00889",
           "HISTORY  STCK  :/data/L3/rhain/L3_unit_tests/xms/ens0476/input/acisf0501ASC00890",
           "HISTORY  CONT  :7_000N022_r0001b_cmspsf3.fits                           ASC00891"]

        f) Blank line without HISTORY or COMMENT tag (this is a valid header key):

          ["                            <COMPLETELY BLANK LINE>                             "]


        g) ARDLIB history records

          ["HISTORY  PARM  :ARDLIB Version: 0.7.1-4                                 ASC00028",
           "HISTORY  PARM  :File used for CALDB info: /state/partition1/tmp.ascdsl3/ASC00029",
           "HISTORY  CONT  :L3ASRC____338268443n247/output/acisf01878_000N001_r0100_ASC00030",
           "HISTORY  CONT  :wmap3.fits                                              ASC00031",
           "HISTORY  PARM  :Note: CTI_APP = PPPPPBPBPP                              ASC00032",
           "HISTORY  PARM  :Parameter file: /state/partition1/tmp.ascdsl3/L3ASRC____ASC00033",
           "HISTORY  CONT  :338268443n247/output/acisf01878_000N001_r0100_ardlib3.paASC00034",
           "HISTORY  CONT  :r                                                       ASC00035",
           "HISTORY  PARM  :Vignetting File: /state/partition1/local/install.CAT3.0/ASC00036",
           "HISTORY  CONT  :CALDB/data/chandra/tel/hrma/bcf/vignet/hrmaD1996-12-20viASC00037",
           "HISTORY  CONT  :gnetN0003.fits[AXAF_VIGNET1]                            ASC00038",
           "HISTORY  PARM  :Onaxis Area File: /state/partition1/local/install.CAT3.0ASC00039",
           "HISTORY  CONT  :/CALDB/data/chandra/tel/hrma/bcf/effarea/hrmaD1996-12-20ASC00040",
           "HISTORY  CONT  :axeffaN0007.fits[AXAF_AXEFFA1]                          ASC00041",
           "HISTORY  PARM  :ACIS QE File: /state/partition1/local/install.CAT3.0/CALASC00042",
           "HISTORY  CONT  :DB/data/chandra/acis/bcf/qe/acisD1997-04-17qeN0006.fits[ASC00043",
           "HISTORY  CONT  :AXAF_QE3]                                               ASC00044"]

        """
        self.__clear__()

        # ASC_FITS Format class.
        self.__ascfits = ASCFITS()

        if content is not None:
            self.create_records(content)


    def __clear__(self):
        self.__ascfits = None
        self.__records = []


    def __str__(self):
        return self.__repr__()


    def __repr__(self):
        strrepr = ''
        idx = 0

        for rec in self.__records:
            if isinstance(rec, HistoryRecord):
                strrepr += "==========  Record[" + str(idx) + "]: HISTORY  ==========\n"
            else: 
                strrepr += "==========  Record[" + str(idx) + "]: COMMENT  ==========\n"

            strrepr += str(rec) + "\n"
            idx += 1

        return strrepr


    def __del__(self):
        self.__clear__()

    def __add_record(self, tag, content, after_key="" ):
        # Arguments should be vetted prior to this point.
        rec = None

        if tag == "HISTORY":
            if content is None or content == "" or len(content) == 0:
                raise ValueError("Unable to create record without contents.")

            rec = HistoryRecord(content=content)

        else:
            if content is None or content == "" or len(content) == 0:
                raise ValueError("Unable to create record without contents.")

            if tag == "COMMENT":
                rec = CommentRecord(content=content)
            elif tag == "BLANK":
                rec = CommentRecord(is_blank=True)

        if rec:
            rec._afterkey = after_key
            self.__records.append(rec)


    def _create_records(self, content, after_key=""):
        """
        This protected method takes in multiple lines of content with HISTORY or COMMENT tag 
        included, creates history or comment records, and adds them to the records list. Processing 
        ensures records are in the original sequence.
	 

        Parameters
        ----------
          content      : string or list of strings
                         Text information for one or more records
          after_key    : string, optional
                         name of header keyword listed before the content 

        Returns
        -------
          None
 
        Raises
        ------
          TypeError      : 'content' argument is not a string type or list of strings.

          For HistoryRecords:
            ValueError   : if record is empty
                           if ASC-FITS record line is not in the expected ASC-FITS format
            TypeError    : if tool name is not a string

          For CommentRecords:
            TypeError    : if provided content is not string type

        """
 
        if not isinstance(content, (list, str if sys.version_info[0] >= 3 else basestring)):
            raise TypeError("'content' argument must be string type or list of string type.")

        if isinstance(content, list):
            if all(isinstance(xx, str if sys.version_info[0] >= 3 else basestring) for xx in content) is False:
                raise TypeError("'content' argument must be string type or list of string type.")
        else:
            content = [content]

        histlist = None    # list to gather ASC-FITS compliant lines to create record.

        # loop content line by line (expected to be a python list)
        for line in content:
            if histlist is not None:
                # building ASC-FITS record block.. see if we are adding or cutting
                if line.upper().startswith("HISTORY") and self.__ascfits.is_compliant( line ) and not self.__ascfits.is_head( line ):
                    # another ASC-FITS record, but not a new block.. add to list and move to next
                    histlist.append( line )
                    continue
                else:
                    # anything else, cut the record and clean the list
                    self.__add_record( "HISTORY", histlist, after_key )
                    histlist = None
                    
            # Assess the record
            if line.upper().startswith("HISTORY"):
                # History record.. check standard or ASC-FITS
                if self.__ascfits.is_compliant( line ):
                    # ASC-FITS HISTORY line - if not 'head' this is a partial record
                    if self.__ascfits.is_head( line ):
                        histlist = [line]
                    else:
                        raise ValueError("Found partial ASC-FITS record.")
                else:
                    # Standard HISTORY line
                    self.__add_record( "HISTORY", line, after_key )

            elif line.upper().startswith("COMMENT"):
                # New COMMENT line
                self.__add_record( "COMMENT", line, after_key )

            elif len( line.strip() ) == 0:
                # New BLANK line
                self.__add_record( "BLANK", line, after_key )

            else:
                raise ValueError("Unsupported line found:"+line)
            
        # flush last history set if needed
        if histlist is not None:
            self.__add_record( "HISTORY", histlist, after_key )
            histlist = None
        

    def create_records(self, content):
        """
        Takes one or more HISTORY or COMMENT cards and generates a corresponding
        set of Records on the records list.

        Processing ensures records are in the original sequence.

        Parameters
        ----------
          content      : string or list of strings
                         Text information for one or more records
        
        Returns
        -------
          None
 
        Raises
        ------
          TypeError      : 'content' argument is not a string type or list of strings.

          For HistoryRecords:
            ValueError   : if record is empty
                           if ASC-FITS record line is not in the expected ASC-FITS format
            TypeError    : if tool name is not a string

          For CommentRecords:
            TypeError    : if provided content is not string type

        """

        self._create_records(content, "")


    def add_record(self, tag, content):
        """
        Append one HISTORY record or one COMMENT record based on tag.

        This function will parse a History record whether it is ASC-FITS compliant or not.

        Parameters
        ----------
          tag          : string, case-insensitive
                         Indicates if the record to be added is a Comment or History record
                         Acceptable inputs are: "COMMENT" and "HISTORY"
          content      : string or list of strings
                         Text information for one history or comment record

        Returns
        -------
          None
 
        Raises
        ------
          TypeError    : 'tag' argument must be string type
          TypeError    : 'content' argument must be string type or list of string type
          ValueError   : 'tag' value is invalid

                         if content is empty
                         if "COMMENT" and content is not a string 
                         if "HISTORY" and content is not a string or List of strings
                         if ASC-FITS record line is not in the expected ASC-FITS format
                         if trying to create more than one record

        Examples
        --------
           1) add_record("HISTORY", "This is a non-ASC-FITS compliant history record")

           2) add_record("COMMENT", "This is a commentary record")

           3) histlist = ["HISTORY  TOOL  :dmcopy  2016-08-11T20:27:22                             ASC00041",
                          "HISTORY  PARM  :infile=/export/proc/tmp.ascdsl3/prs_run/tmp//L3AMRF____5ASC00042",
                          "HISTORY  CONT  :87231988n944/output/acisf05017_000N022_r0001b_rayevt3.fiASC00043",
                          "HISTORY  CONT  :ts[sky=bounds(region(/export/proc/tmp.ascdsl3/prs_run/tmASC00044",
                          "HISTORY  CONT  :p//L3AMRF____587231988n944/input/acisfJ0331514m274138_00ASC00045",
                          "HISTORY  CONT  :1N021_r0001_srbreg3.fits[bkgreg]))][bin sky=1][opt type=ASC00046",
                          "HISTORY  CONT  :i4]                                                     ASC00047",
                          "HISTORY  PARM  :outfile=/export/proc/tmp.ascdsl3/prs_run/tmp//L3AMRF____ASC00048",
                          "HISTORY  CONT  :587231988n944/output/acisf05017_000N022_r0001b_fbinpsf3.ASC00049",
                          "HISTORY  CONT  :fits                                                    ASC00050",
                          "HISTORY  PARM  :kernel=default                                          ASC00051",
                          "HISTORY  PARM  :option=image                                            ASC00052",
                          "HISTORY  PARM  :verbose=0                                               ASC00053",
                          "HISTORY  PARM  :clobber=yes                                             ASC00054"
                         ]
              add_record("HISTORY", histlist) 

        """
        if isinstance(tag, str if sys.version_info[0] >= 3 else basestring) is False:
            raise TypeError("'tag' argument must be string type")
        if not isinstance(content, (list, str if sys.version_info[0] >= 3 else basestring)):
            raise TypeError("'content' argument must be string type or list of string type.")
        if isinstance(content, list):
            if all(isinstance(xx, str if sys.version_info[0] >= 3 else basestring) for xx in content) is False:
                raise TypeError("'content' argument must be string type or list of string type.")
        
        tag = tag.upper()
        if tag != "HISTORY" and  tag != "COMMENT":
            raise ValueError("'tag' value is invalid: "+ tag + " not in [HISTORY,COMMENT].")

        self.__add_record( tag=tag, content=content )


    def add_history_record(self, tool, date='now', param_names=[], param_values=[], param_stacks=None, asc_start=1):
        """
        Appends one History record to records list.

        Parameters
        ----------
          tool         : string
                         The tool name
          date         : string in format "YYYY-MM-DDThh:mm:ss", "now", "", or None;  default = "now"
                         Date and time of when the HistoryRecord was created
        
          param_names  : list of strings, inter-related with param_values and param_stacks
                         The parameter keys 
          param_values : list of values, inter-related with param_names and param_stacks
                         The parameter values
          param_stacks : list of lists containing strings, inter-related with param_names and param_values;  default = None 
                         The parameter file stacks, if any
          asc_start    : integer, optional;  default = 1
                         The starting number of the ASC sequence

             **NOTE:  There must be an equal number of param_names, param_values, and param_stacks lists.
                      For example:
                          param_names  = ["infile", "outfile", "tol"]
                          param_values = ["@in.lis", "out.fits", "tolerance"]
                          param_stacks = [ ["stkfile1.fits", "stkfile2.fits"], [], [] ]
        
        Returns
        -------
          None 
 
        Raises
        ------
          TypeError    : 'tool' argument must be string type
                         'param_names' argument must be list type
                         'param_names' list content must be string type
                         'param_values' argument must be list type
                         'param_stacks' argument must be list type
                         'param_stacks' list content must be list of string type

          ValueError   : 'tool' value is empty
                         'param_names', 'param_values', and 'param_stacks' lists must be same length.

        """
        # argument vetting done by constructor
        hr = HistoryRecord(None, tool, date, param_names, param_values, param_stacks, asc_start )

        self.__records.append(hr)


    def add_comment_record(self, content, is_blank=False):
        """
        Appends one Comment record to records list.

        Parameters
        ----------
          content      : string
                         Text information for one comment record
          is_blank     : boolean, default=False
                         Flag to control if 'COMMENT' tag is included in the output
                         Set to True if requiring a completely blank line

        Returns
        -------
          None
 
        Raises
        ------
          TypeError    : 'content' argument must be string type.

        """ 
        if is_blank:
            cr = CommentRecord( is_blank=True )
        else:
            cr = CommentRecord( content )

        self.__records.append(cr)
            

    def delete_comments(self):
        """
        Removes comment records from the records list.

        Parameters
        ----------
          None 

        Returns
        ------
          None

        Raises
        ------
          None
          
        """
        for rec in reversed(self.__records):
            if isinstance(rec, CommentRecord):
                self.__records.remove(rec)
                del rec


    def delete_history(self):
        """
        Removes history records from the records list.

        Parameters
        ----------
          None 

        Returns
        ------
          None

        Raises
        ------
          None

        """
        for rec in reversed(self.__records):
            if isinstance(rec, HistoryRecord):
                self.__records.remove(rec)
                del rec


    @property
    def history(self):
        """
        Retrieves only History records stored in records list
        """
        hrlist = []

        for rec in self.__records[:]:
            if isinstance(rec, HistoryRecord):
                hrlist.append(rec)

        return hrlist


    @property
    def comments(self):
        """
        Retrieves only Comment records stored in records list
        """
        crlist = []

        for rec in self.__records[:]:
            if isinstance(rec, CommentRecord):
                crlist.append(rec)

        return crlist


    @property
    def records(self):
        """
        Retrieves all HISTORY and COMMENT records
          ** returns a copy of the full records list

        """
        return self.__records[:]


    @property
    def nrecords(self):
        """
        Returns the total number of records in the list 
        """
        return len(self.__records)


    @property
    def nhistory(self):
        """
        Returns the number of history records in the list
        """
        return len(self.history)


    @property
    def ncomments(self):
        """
        Returns the number of comment records in the list
        """
        return len(self.comments)

    @property
    def histnum(self):
        """
        Returns the total number of ASC-FITS compliant HISTORY keywords in the header 
        """
        history = self.history
        histstr = ""
        count = 0

        for ii in range(0, self.nhistory):
            if history[ii].is_ascfits_compliant():
                histstr += history[ii].as_ASC_FITS(stk_expand=True) + "\n"

        count = len(histstr.split('\n'))
        return count



