Table = require '../table'
Data = require '../../data'

class CmapTable extends Table
    parse: (data) ->
        data.pos = @offset
        
        @version = data.readUInt16()
        tableCount = data.readUInt16()
        @tables = []
        @unicode = null
        
        for i in [0...tableCount]
            entry = new CmapEntry(data, @offset)
            @tables.push entry
            @unicode ?= entry if entry.isUnicode
            
        return true
        
    @encode: (charmap, encoding = 'macroman') ->
        result = CmapEntry.encode(charmap, encoding)
        table = new Data
        
        table.writeUInt16 0 # version
        table.writeUInt16 1 # tableCount
        
        result.table = table.data.concat(result.subtable)
        return result
            
class CmapEntry
    constructor: (data, offset) ->
        @platformID = data.readUInt16()
        @encodingID = data.readShort()
        @offset = offset + data.readInt()
        
        data.pos = @offset
        @format = data.readUInt16()
        @length = data.readUInt16()
        @language = data.readUInt16()
        
        @isUnicode = (@platformID is 3 and @encodingID is 1 and @format is 4) or @platformID is 0 and @format is 4
        
        @codeMap = {}
        switch @format
            when 0
                for i in [0...256]
                    @codeMap[i] = data.readByte()
            
            when 4
                segCountX2 = data.readUInt16()
                segCount = segCountX2 / 2
                
                data.pos += 6 # skip searching hints
                endCode = (data.readUInt16() for i in [0...segCount])
                data.pos += 2 # skip reserved value
                
                startCode = (data.readUInt16() for i in [0...segCount])
                idDelta = (data.readUInt16() for i in [0...segCount])
                idRangeOffset = (data.readUInt16() for i in [0...segCount])
                
                count = @length - data.pos + @offset
                glyphIds = (data.readUInt16() for i in [0...count])
                
                for tail, i in endCode
                    start = startCode[i]
                    for code in [start..tail]
                        if idRangeOffset[i] is 0
                            glyphId = code + idDelta[i]
                        else
                            index = idRangeOffset[i] / 2 + (code - start) - (segCount - i)
                            glyphId = glyphIds[index] or 0
                            glyphId += idDelta[i] if glyphId isnt 0
                            
                        @codeMap[code] = glyphId & 0xFFFF
                        
    @encode: (charmap, encoding) ->
        subtable = new Data
        codes = Object.keys(charmap).sort (a, b) -> a - b
        
        switch encoding
            when 'macroman'
                id = 0
                indexes = (0 for i in [0...256])
                map = { 0: 0 }
                codeMap = {}
                
                for code in codes
                    map[charmap[code]] ?= ++id
                    codeMap[code] = 
                        old: charmap[code]
                        new: map[charmap[code]]
                        
                    indexes[code] = map[charmap[code]]
                    
                subtable.writeUInt16 1   # platformID
                subtable.writeUInt16 0   # encodingID
                subtable.writeUInt32 12  # offset
                subtable.writeUInt16 0   # format
                subtable.writeUInt16 262 # length
                subtable.writeUInt16 0   # language
                subtable.write indexes   # glyph indexes
                
                result = 
                    charMap: codeMap
                    subtable: subtable.data
                    maxGlyphID: id + 1
                
            when 'unicode'
                startCodes = []
                endCodes = []
                nextID = 0
                map = {}
                charMap = {}
                last = diff = null
                
                for code in codes
                    old = charmap[code]
                    map[old] ?= ++nextID
                    charMap[code] = 
                        old: old
                        new: map[old]
                    
                    delta = map[old] - code
                    if not last? or delta isnt diff
                        endCodes.push last if last
                        startCodes.push code
                        diff = delta

                    last = code
                    
                endCodes.push last if last
                endCodes.push 0xFFFF
                startCodes.push 0xFFFF
                
                segCount = startCodes.length
                segCountX2 = segCount * 2
                searchRange = 2 * Math.pow(Math.log(segCount) / Math.LN2, 2)
                entrySelector = Math.log(searchRange / 2) / Math.LN2
                rangeShift = 2 * segCount - searchRange
                
                deltas = []
                rangeOffsets = []
                glyphIDs = []
                
                for startCode, i in startCodes
                    endCode = endCodes[i]
                    
                    if startCode is 0xFFFF
                        deltas.push 0
                        rangeOffsets.push 0
                        break
                        
                    startGlyph = charMap[startCode].new
                    if startCode - startGlyph >= 0x8000
                        deltas.push 0
                        rangeOffsets.push 2 * (glyphIDs.length + segCount - i)
                        
                        for code in [startCode..endCode]
                            glyphIDs.push charMap[code].new
                            
                    else
                        deltas.push startGlyph - startCode
                        rangeOffsets.push 0
                                                
                subtable.writeUInt16 3  # platformID
                subtable.writeUInt16 1  # encodingID
                subtable.writeUInt32 12 # offset
                subtable.writeUInt16 4  # format
                subtable.writeUInt16 16 + segCount * 8 + glyphIDs.length * 2 # length
                subtable.writeUInt16 0  # language
                subtable.writeUInt16 segCountX2
                subtable.writeUInt16 searchRange
                subtable.writeUInt16 entrySelector
                subtable.writeUInt16 rangeShift
                
                subtable.writeUInt16 code for code in endCodes
                subtable.writeUInt16 0  # reserved value
                subtable.writeUInt16 code for code in startCodes
                
                subtable.writeUInt16 delta for delta in deltas
                subtable.writeUInt16 offset for offset in rangeOffsets
                subtable.writeUInt16 id for id in glyphIDs
                
                result = 
                    charMap: charMap
                    subtable: subtable.data
                    maxGlyphID: nextID + 1
        
module.exports = CmapTable