# A codec for ISO-2022-KR [RFC1557]
# by Hye-Shik Chang <17 February 2002>
#
# StreamReader was adopted from Tamito KAJIYAMA's iso-2022-jp codec.
#
# $Id: iso_2022_kr.py,v 1.3 2002/02/16 11:34:25 perky Exp $

import codecs
from korean.mappings import ksc5601_hangul
encmap_hangul = ksc5601_hangul.encoding_map
decmap_hangul = ksc5601_hangul.decoding_map
encmap_ideo, decmap_ideo = {}, {}
encmap_misc, decmap_misc = {}, {}

US_ASCII      = 1
KSC5601_1987  = 2

CHARSETS = {
    "\033(B": US_ASCII,
    "\033$)C": KSC5601_1987,
}
SI = '\x0f'
SO = '\x0e'
ESC = '\033'

DESIGNATIONS = {}
for k, v in CHARSETS.items():
    DESIGNATIONS[v] = k

class Codec(codecs.Codec):
    # Unicode to character buffer
    def encode(self, data, errors='strict',
               supported_errors=('strict', 'ignore', 'replace')):
        global encmap_ideo, encmap_misc

        if errors not in supported_errors:
            raise UnicodeError, "unknown error handling"
        buffer = []
        charset = US_ASCII
        shiftstate = 0
        for c in data:
            if c in ('\n', '\r'):
                shiftstate = 0

            if c < u'\u0080':
                new_charset = US_ASCII
                s = c.encode("ascii", errors)
            elif encmap_hangul.has_key(c):
                new_charset = KSC5601_1987
                s = encmap_hangul[c]
            else:
                if not encmap_misc:
                    from korean.mappings import ksc5601_misc
                    encmap_misc = ksc5601_misc.encoding_map
                if encmap_misc.has_key(c):
                    new_charset = KSC5601_1987
                    s = encmap_misc[c]
                else:
                    if not encmap_ideo:
                        from korean.mappings import ksc5601_ideograph
                        encmap_ideo = ksc5601_ideograph.encoding_map
                    if encmap_ideo.has_key(c):
                        new_charset = KSC5601_1987
                        s = encmap_ideo[c]
                    elif errors == 'replace':
                        new_charset = KSC5601_1987
                        s = '\xa0\xa0'
                    elif errors == 'strict':
                        raise UnicodeError, "cannot map \\u%04x to ISO-2022-KR" % ord(c)
                    else:
                        continue

            if charset != new_charset:
                charset = new_charset
                if charset == US_ASCII:
                    if shiftstate:
                        buffer.append(SI)
                        shiftstate = 0
                else:
                    buffer.append(DESIGNATIONS[charset])
                    if not shiftstate:
                        buffer.append(SO)
                        shiftstate = 1

            if charset == KSC5601_1987 and shiftstate:
                s = chr(ord(s[0])&0x7F) + chr(ord(s[1])&0x7F)
            buffer.append(s)
        if shiftstate:
            buffer.append(SI)
            #buffer.append(DESIGNATIONS[US_ASCII])
        return (''.join(buffer), len(data))

    # character buffer to Unicode
    def decode(self, data, errors='strict',
               supported_errors=('strict', 'ignore', 'replace')):
        global decmap_ideo, decmap_misc
        
        if errors not in supported_errors:
            raise UnicodeError, "unknown error handling"
        buffer = []
        data = str(data) # character buffer compatible object
        size = len(data)
        charset = US_ASCII
        shiftstate = 0
        escstart = -1
        p = 0
        while p < size:
            if data[p] in ('\n', '\r'):
                shiftstate = 0

            if escstart >= 0:
                if data[p].isalpha():
                    escstr = data[escstart:p+1]
                    if CHARSETS.has_key(escstr):
                        charset = CHARSETS[escstr]
                    elif errors == 'strict':
                        raise UnicodeError, "unsupported charset found"
                    escstart = -1
                p += 1
            elif data[p] == SO:
                shiftstate = 1
                p += 1
            elif data[p] == SI:
                shiftstate = 0
                p += 1
            elif data[p] == ESC:
                escstart = p
                p += 1
            else:
                if not shiftstate and (
                        charset == US_ASCII or data[p] < '\x80'): # ascii
                    buffer.append(unicode(data[p], "ascii", errors))
                    p += 1
                else:
                    c = data[p:p+2]
                    if len(c) == 2:
                        c = chr(ord(c[0])|0x80) + chr(ord(c[1])|0x80)
                        if decmap_hangul.has_key(c):
                            buffer.append(decmap_hangul[c])
                            p += 2
                            continue

                        if not decmap_misc:
                            from korean.mappings import ksc5601_misc
                            decmap_misc = ksc5601_misc.decoding_map
                        if decmap_misc.has_key(c):
                            buffer.append(decmap_misc[c])
                            p += 2
                            continue

                        if not decmap_ideo:
                            from korean.mappings import ksc5601_ideograph
                            decmap_ideo = ksc5601_ideograph.decoding_map
                        if decmap_ideo.has_key(c):
                            buffer.append(decmap_ideo[c])
                            p += 2
                            continue

                    if errors == 'replace':
                        buffer.append(u'\uFFFD') # REPLACEMENT CHARACTER
                    elif errors == 'strict':
                        raise UnicodeError, "unexpected byte 0x%02x found" % ord(data[p])
                    p += 1
        
        return (u''.join(buffer), len(data))

class StreamWriter(Codec, codecs.StreamWriter):
    pass

class StreamReader(Codec, codecs.StreamReader):
    def __init__(self, stream, errors='strict'):
        codecs.StreamReader.__init__(self, stream, errors)
        self.data = ''
        self.charset = US_ASCII
    def _read(self, func, size):
        if size == 0:
            return u''
        if size is None or size < 0:
            data = self.data + func()
        else:
            data = self.data + func(max(size, 8) - len(self.data))
        self.data = ''
        if self.charset != US_ASCII:
            data = DESIGNATIONS[self.charset] + data
        pos = data.rfind("\033")
        if pos >= 0 and pos + 3 >= len(data):
            # data ends on the way of an escape sequence
            data, self.data = data[:pos], data[pos:]
            pos = data.rfind("\033")
        if pos >= 0:
            try:
                self.charset = CHARSETS[data[pos:pos+3]]
            except KeyError:
                raise UnicodeError, "unknown designation"
            if self.charset == KSC5601_1987 and \
               (len(data) - pos - 3) % 2 == 1:
                data, self.data = data[:-1], data[-1]
            if self.charset != US_ASCII:
                data = data + DESIGNATIONS[US_ASCII]
        return self.decode(data)[0]
    def read(self, size=-1):
        return self._read(self.stream.read, size)
    def readline(self, size=-1):
        return self._read(self.stream.readline, size)
    def readlines(self, size=-1):
        data = self._read(self.stream.read, size)
        buffer = []
        end = 0
        while 1:
            pos = data.find(u'\n', end)
            if pos < 0:
                if end < len(data):
                    buffer.append(data[end:])
                break
            buffer.append(data[end:pos+1])
            end = pos+1
        return buffer
    def reset(self):
        self.data = ''

### encodings module API

def getregentry():
    return (Codec().encode,Codec().decode,StreamReader,StreamWriter)

def getaliases():
    return ('iso_2022_kr',)
