#
# Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:  File image handling functions for CDS script
#

import sys, pprint
from array import array

_DBG = False
def dbg(str):
    if _DBG:
        print str

TT_UNKNOWN = '?'; TT_COMMENT = '#'; TT_STRING = '"'; TT_CODE = '.'
def translate_mark_for_print(mark):
    if mark == "\n":
        return "\\n"
    elif mark == "\r":
        return "\\r"
    elif mark == "\t":
        return "\\t"
    return mark
    
    
class CCLineImage(object):
    def __init__(self, data, mask, lineno=0, startPos=-1, endPos=-1):
        self.__data = data
        self.__mask = mask
        self.lineno = lineno
        self.startPos = startPos
        self.endPos = endPos
        self.datalen = len(data)

    def data(self):
        return self.__data[:]

    def code(self, useBlanks=True):
        return self._get_line((TT_CODE,), useBlanks)

    def comments(self, useBlanks=True):    
        return self._get_line((TT_COMMENT,), useBlanks)
    
    def strings(self, useBlanks=True):    
        return self._get_line((TT_STRING,), useBlanks)

    def code_and_strings(self, useBlanks=True):    
        return self._get_line((TT_STRING, TT_CODE), useBlanks)

    def text(self):    
        return self._get_line((TT_STRING, TT_CODE, TT_COMMENT), False)
        
    def mask(self):
        return self.__mask.tostring()

    def _get_line(self, typez, useBlanks):
        line = array('c')
        for i in xrange(self.datalen):
            if self.__data[i] == '\n':
                break
            elif self.__mask[i] in typez:
                line.append(self.__data[i])
            elif useBlanks:
                line.append(' ')
        return line.tostring()
        
            
class CCTextBlockImage(object):
    delimitPairs = (('(', ')'), ('{', '}'), ('[', ']'), ('<', '>'))
    delimitChars = [e for s, e in delimitPairs] + [s for s, e in delimitPairs]
    endDelimiters = [e for s, e in delimitPairs]
    startDelimiters = [s for s, e in delimitPairs]
    
    def __init__(self):
        self.data = array('c')
        self.datamask = array('c')
        
        self.chPrev = None
        self.ch = None
        self.chNext = None
        self.pos = 0
        self.continueScan = True
        self.dataLen = 0
        self.lineFeeds = []
        self.lineFeedLen = 0

    def load(self, textblock):
        import sys
        del self.data
        self.data = array('c', textblock)
        self.datamask = array('c', TT_UNKNOWN * len(self.data))
        self.dataLen = len(textblock)
                
    def _dbg_chars(self):
        if _DBG:
            dbg("pos=%d, len=%d, prev=%s, cur=%s, next=%s" % (self.pos, self.dataLen, 
                                                            translate_mark_for_print(self.chPrev),
                                                            translate_mark_for_print(self.ch), 
                                                            translate_mark_for_print(self.chNext)))

    # moves current data pointer and marks passed chars as charType
    def _seek(self, amount, prevType=None, curType=None, nextType=None):

        if not self.dataLen > 0:
            return
        if prevType:
            self.datamask[self.pos-1] = prevType
        if curType:
            self.datamask[self.pos] = curType
        if nextType:
            self.datamask[self.pos+1] = nextType
            
        oldPos = self.pos
        self.pos += amount
        assert (oldPos <= self.pos) and (amount <= 2)

        if self.pos <= 0:
            self.pos = 0; self.chPrev = None; self.ch = self.data[0]
            if self.dataLen > 1:
                self.chNext = self.data[1]
            
            self._dbg_chars()
        elif self.pos == self.dataLen - 1:
            self.chNext = None; self.ch = self.data[-1]; self.chPrev = self.data[-2]
            self._dbg_chars()
        elif self.pos > self.dataLen - 1:
            self.pos = self.dataLen - 1
            dbg("FINISHING SCAN")
            self.continueScan = False
        else:
            self.ch = self.data[self.pos]; self.chNext = self.data[self.pos+1]; self.chPrev = self.data[self.pos-1]
            self._dbg_chars()



    def scan(self, bytePrintFmtStr='', bytePrintTreshold=20000):
        if self.dataLen == 0:
            return

        SINGLELINE = 1; MULTILINE = 2; STRING = 3; CODE = 4
        self.pos = 0; self.continueScan = True

        state = CODE
        self._seek(0)
        while self.continueScan:
            if (self.pos > 1) and (self.pos % bytePrintTreshold == 0) and bytePrintFmtStr:
                print bytePrintFmtStr % (self.pos, self.dataLen, ((self.pos * 100) / self.dataLen))
            if state == CODE:
                if (self.ch == '/') and (self.chNext == '/'):
                    dbg("FOUND // -> ENTER SINGLELINE STATE")
                    self._seek(2, curType=TT_COMMENT, nextType=TT_COMMENT)
                    state = SINGLELINE
                elif (self.ch == '/') and (self.chNext == '*'):
                    dbg("FOUND /* -> ENTER MULTILINE STATE")
                    self._seek(2, curType=TT_COMMENT, nextType=TT_COMMENT)
                    state = MULTILINE
                elif (self.ch == '"'):
                    dbg('FOUND " --> ENTER STRING STATE')
                    self._seek(1, curType=TT_STRING)
                    state = STRING
                else:
                    if (self.ch == '\n'):
                        self.lineFeeds.append(self.pos)
                    self._seek(1, curType=TT_CODE)
            elif state == STRING:
                if (self.ch == '\n'):
                    if (self.chPrev != '\\'):
                        dbg("FOUND line-end on string --> ENTER CODE STATE")
                        state = CODE
                    self.lineFeeds.append(self.pos)
                    self._seek(1, curType=TT_STRING)
                elif (self.ch == '"'):
                    dbg('FOUND " on string --> ENTER CODE STATE')
                    self._seek(1, curType=TT_STRING)                                   
                    state = CODE
                else:
                    self._seek(1, curType=TT_STRING)
            elif state == MULTILINE:
                if (self.ch == '*') and (self.chNext == '/'):
                    dbg("FOUND multiline end --> ENTER CODE STATE")
                    self._seek(2, curType=TT_COMMENT, nextType=TT_COMMENT)
                    state = CODE
                else:
                    if (self.ch == '\n'):
                        self.lineFeeds.append(self.pos)
                    self._seek(1, curType=TT_COMMENT)
            elif state == SINGLELINE:
                if (self.ch == '\n'):
                    dbg("FOUND line-end on singleline comment --> ENTER CODE STATE")
                    self.lineFeeds.append(self.pos)                    
                    self._seek(1, curType=TT_COMMENT)
                    state = CODE
                else:
                    self._seek(1, curType=TT_COMMENT)
                    
        self.lineFeedLen = len(self.lineFeeds)


    def __str__(self):
        rawdata = array('c')
        for mark in self.data:
            rawdata.append(translate_mark_for_print(mark))
        return rawdata.tostring()

    def iter_lineimages(self, startPos=-1, forward=True, lineCount=-1):
        curline = array('c')
        curmask = array('c')
        pos = 0
        linesLeft = 9999999
        datalen = len(self.data)
        
        if startPos != -1:
            pos = startPos
            assert (pos >= 0) and (pos < datalen)
        if lineCount != -1:
            assert lineCount > 0
            linesLeft = self.get_lineno(pos)
            if forward:
                linesLeft = self.get_linecount() - self.get_lineno(pos)
            if lineCount < linesLeft:
                linesLeft = lineCount
            #print "%d - %d" % (self.get_linecount(), self.get_lineno(pos))
            assert linesLeft >= 0
            
        lineStartPos = 0
        lineNo = 1
        while (pos >= 0) and (pos < datalen) and (linesLeft > 0):
            mark = self.data[pos]
            maskmark = self.datamask[pos]
            if mark == '\n':
                yield CCLineImage(curline, curmask, lineNo, lineStartPos, pos)
                linesLeft -= 1
                curline = array('c')
                curmask = array('c')
                lineStartPos = pos + 1
                lineNo += 1
            else:
                curline.append(mark)
                curmask.append(maskmark)
            if forward:
                pos += 1
            else:
                pos -= 1
        if len(curline) > 0:
            yield CCLineImage(curline, curmask, lineNo, lineStartPos, pos)

    def get_line_startpos(self, pos):
        if pos > (self.dataLen - 1):
            return -1
        if self.lineFeedLen == 0:
            return 0
        for i in xrange(lineFeedLen):
            if self.lineFeeds[i] > pos:
                if i > 0:
                    return self.lineFeeds[i-1] + 1
                return 0
        return pos - self.lineFeeds[-1]

    def get_linecount(self):
        return len(self.lineFeeds) + 1
    
    def get_lineno(self, pos):
        if pos > (self.dataLen - 1):
            return -1
        lineFeedLen = len(self.lineFeeds)
        if lineFeedLen == 0:
            return 1
        for i in xrange(lineFeedLen):
            if self.lineFeeds[i] > pos:
                #print "if %d > %d" % (self.lineFeeds[i], pos)
                return i
        return lineFeedLen
            
    def get_colno(self, pos):
        if pos > (len(self.data) - 1):
            return -1
        if len(self.lineFeeds) == 0:
            return pos
        for i in xrange(len(self.lineFeeds)):
            if self.lineFeeds[i] < pos:
                return pos - self.lineFeeds[i]

    def get_lineimages(self, startPos=-1, endPos=-1):
        lineImgs = []
        curline = array('c')
        curmask = array('c')
        lastInd = len(self.data) - 1
        if startPos < 0: startPos = 0
        if startPos > lastInd: startPos = lastInd
        if endPos < 0: endPos = 0
        if endPos > lastInd: endPos = lastInd
        pos = startPos
        while pos <= endPos:
            if self.data[pos] == '\n':
                lineImgs.append(CCLineImage(curline, curmask, self.get_lineno(pos), self.get_line_startpos(pos), pos))
                curline = array('c')
                curmask = array('c')
            else:
                curline.append(self.data[pos])
                curmask.append(self.datamask[pos])
            pos += 1
        if len(curline) > 0:
            lineImgs.append(CCLineImage(curline, curmask, self.get_lineno(pos), self.get_line_startpos(pos), pos))

        return lineImgs                        

    def find_matching_delimiter(self, pos):
        if (len(self.data) < pos) or (pos < 0):
            return -1
        
        data = self.data
        startChar = data[pos]
        if startChar not in self.delimitChars:
            return -1
        
        forward = True
        if startChar in self.endDelimiters:
            forward = False

        endChar = ''
        for s, e in self.delimitPairs:
            if startChar == s:
                endChar = e
            elif startChar == e:
                endChar = s

        curStackPos = 0
        datalen = len(data)
        while pos < datalen:
            if data[pos] == startChar:
                curStackPos += 1
            elif data[pos] == endChar:
                curStackPos -= 1
            if curStackPos <= 0:
                return pos
            if forward:
                pos += 1
            else:
                pos -= 1
        return -1

    def get_surrounding_lineimages(self, linesFw, linesBw, pos):
        if (len(self.data) < pos) or (pos < 0) or (linesFw < 0) or (linesBw < 0):
            return []
        lineImgs = []
        if linesBw and (pos > 0):
            pos -= 1
            while linesBw > 0:
                if self.data[pos] == '\n':
                    linesBw -= 1; linesFw += 1
                if pos < 0:
                    break
                pos -= 1

        for lineImg in self.iter_lineimages(pos, forward=True, lineCount=linesFw):
            lineImgs.append(lineImg)
        return lineImgs

class CCFileImage(CCTextBlockImage):
    def __init__(self):
        super(CCFileImage, self).__init__()

    def load(self, filename):
        rawdata = open(filename, 'r').read()
        self.load_from_rawdata(rawdata)
        
    def load_from_rawdata(self, rawdata):
        super(CCFileImage, self).load(rawdata)
        
if __name__=="__main__":
    f = CCFileImage()
    f.load(sys.argv[1])
    f.scan()
    for img in f.iter_lineimages():
        print img.mask()
        print img.text()
        #print img.code()
        #print img.comments()
        #print img.code_and_strings()
        #print img.strings()
        print ''