-- error messages
function plt.error(s)
  tex.error("pluatex error: " .. s ); 
end

-- procedures for loading Japanese font metric
jfm={}; jfm.char_type={}; jfm.glue={}; jfm.kern={}

function jfm.define_char_type(t,lt) 
   if not jfm.char_type[t] then jfm.char_type[t]={} end
   jfm.char_type[t].chars=lt 
end
function jfm.define_type_dim(t,l,w,h,d,i)
   if not jfm.char_type[t] then jfm.char_type[t]={} end
   jfm.char_type[t].width=w; jfm.char_type[t].height=h;
   jfm.char_type[t].depth=d; jfm.char_type[t].italic=i; jfm.char_type[t].left=l
end
function jfm.define_glue(b,a,w,st,sh)
   local j=b*0x800+a
   if not jfm.glue[j] then jfm.glue[j]={} end
   jfm.glue[j].width=w; jfm.glue[j].stretch=st; 
   jfm.glue[j].shrink=sh
end
function jfm.define_kern(b,a,w)
   local j=b*0x800+a
   if not jfm.kern[j] then jfm.kern[j]=w end
end

-- procedures for \loadjfontmetric
plt.metrics={} -- this table stores all metric information

function plt.loadjfontmetric()
   if string.len(jfm.name)==0 then
      plt.error("the key of font metric is null"); return nil
   elseif plt.metrics[jfm.name] then
      plt.error("the metric '" .. jfm.name .. "' is already loaded"); return nil
   end
   plt.metrics[jfm.name]={}
   if jfm.dir~='yoko' then
      plt.error("jfm.dir must be 'yoko'"); return nil
   end
   plt.metrics[jfm.name].dir=jfm.dir
   plt.metrics[jfm.name].zw=jfm.zw
   plt.metrics[jfm.name].zh=jfm.zh
   plt.metrics[jfm.name].char_type=jfm.char_type
   plt.metrics[jfm.name].glue=jfm.glue
   plt.metrics[jfm.name].kern=jfm.kern
end

function plt.find_char_type(c,metrickey)
-- c: character code, metrickey: key
   for i, v in pairs(plt.metrics[metrickey].char_type) do
      if i~=0 then
        for j,w in pairs(v.chars) do
           if w==c then return i; end
        end
      end
   end
   return 0
end

-- procedures for \jfont command.
function plt.jfontdefA(b)
  plt.fntbki=font.current()
  plt.cstemp=token.csname_name(token.get_next())
  tex.sprint('\\csname ' .. plt.cstemp .. '\\endcsname\\csname @jfont\\endcsname')
  -- A trick to get font id associated of the argument of \jfont.
  -- font.id() does not seem to work in my environment...
end
function plt.jfontdefB(s) -- for horizontal font
   if not plt.metrics[s] then
      plt.error("metric named '" .. s .. "' didn't loaded")
      return
   end
   local i = plt.transform_jfont(font.current(), s)
   tex.sprint('\\expandafter\\def\\csname ' .. plt.cstemp .. '\\endcsname' 
	      .. '{\\csname plt@curjfnt\\endcsname=' .. i 
	      .. '\\zw=' .. font.fonts[i].parameters.quad .. 'sp' 
	      .. '\\zh=' .. font.fonts[i].parameters.x_height .. 'sp\\relax}')
   font.current(plt.fntbki); plt.fntbk = {}; plt.cstemp = {}
end

-- "transform" font according to a font metric
function plt.transform_jfont(index,metrickey)
   local f = {}
   local of=font.fonts[index]
   f.name = of.name
   f.fullname = metrickey
   f.size = of.size
   f.parameters = {}
   f.parameters.quad=tex.round(f.size*plt.metrics[metrickey].zw)
   f.parameters.x_height=tex.round(f.size*plt.metrics[metrickey].zh)
   f.designsize = of.designsize
   f.type = 'virtual'
   f.characters = {}
   f.fonts = { { id = index } }
   for i,v in pairs(of.characters) do
      f.characters[i]={}
      local ci = plt.metrics[metrickey].char_type[plt.find_char_type(i,metrickey)]
      if ci.left~=0.0 then
         f.characters[i].commands = { 
            {'right', tex.round(-ci.left*f.size)}, 
            {'char',i} 
         }
      else
         f.characters[i].commands = { {'char',i} }
      end
      f.characters[i].italic = tex.round(ci.italic*f.size)
      f.characters[i].width  = tex.round(ci.width*f.size)
      f.characters[i].height = tex.round(ci.height*f.size)
      f.characters[i].depth  = tex.round(ci.depth*f.size)
   end
   return font.define(f)
end


-- Replace font in nodes which character code is in the range of
-- Japanese characters, using two attributes
function plt.replace_ja_font(head)
   local p=head
   while p do 
      if node.type(p.id)=='glyph' then
	 if plt.is_ucs_in_japanese_char(p.char) then
	    local v = node.has_attribute(p,luatexbase.attributes['plt@curjfnt'])
	    if v then p.font=v end
	    v=node.has_attribute(p,luatexbase.attributes['plt@ykblshift'])
	    if v then 
	       node.set_attribute(p,luatexbase.attributes['plt@yablshift'],v)
	    else
	       node.unset_attribute(p,luatexbase.attributes['plt@yablshift'])
	    end
	 end
      end
      p=node.next(p)
   end
   return head
end


-- return true if and only if p is a Japanese character node
function plt.is_japanese_glyph_node(p)
   if not p then return false
   elseif node.type(p.id)~='glyph' then return false
   elseif p.font==node.has_attribute(p,luatexbase.attributes['plt@curjfnt']) then
      return true
   else return false
   end
end

---------- Kinsoku 
----------
plt.penalty_table = {}
function plt.set_penalty_table(m,c,p)
   if not plt.penalty_table[c] then plt.penalty_table[c]={} end
   if m=='pre' then
      plt.penalty_table[c].pre=p
   elseif m=='post' then
      plt.penalty_table[c].post=p
   end
end

plt.inhibit_xsp_table = {}
function plt.set_inhibit_xsp_table(c,p)
   plt.inhibit_xsp_table[c]=p
end

----------
----------
function plt.create_ihb_node()
   local g=node.new(node.id('whatsit'), node.subtype('user_defined'))
   g.user_id=30111; g.type=number; g.value=1
   node.write(g)
end

-- The fullname field of virtual font expresses its metric
function plt.find_size_metrickey(p)
   if plt.is_japanese_glyph_node(p) then
      return font.fonts[p.font].size, font.fonts[p.font].fullname
   else 
      return nil, nil
   end
end

function plt.new_jfm_glue(size,mt,bc,ac)
-- mt: metric key, bc, ac: char classes
   local g=nil
   local h
   local w=bc*0x800+ac
   if plt.metrics[mt].glue[w] then
      h=node.new(node.id('glue_spec'))
      h.width  =tex.round(size*plt.metrics[mt].glue[w].width)
      h.stretch=tex.round(size*plt.metrics[mt].glue[w].stretch)
      h.shrink =tex.round(size*plt.metrics[mt].glue[w].shrink)
      h.stretch_order=0; h.shrink_order=0
      g=node.new(node.id('glue'))
      g.subtype=0; g.spec=h; return g
   elseif plt.metrics[mt].kern[w] then
      g=node.new(node.id('kern'))
      g.subtype=0; g.kern=tex.round(size*plt.metrics[mt].kern[w]); return g
   else
      return nil
   end
end

-- The fullname field of virtual font expresses its metric
function plt.calc_between_two_jchar(q,p)
   -- q, p: node (possibly null)
   local ps,pm,qs,qm,g,h
   if not p then -- q is the last node
      qs, qm = plt.find_size_metrickey(q)
      if not qm then 
	 return nil
      else
	 g=plt.new_jfm_glue(qs,qm,
				plt.find_char_type(q.char,qm),
				plt.find_char_type('boxbdd',qm))
      end
   elseif not q then
      -- p is the first node etc.
      if q then print(node.type(q.id)) end
      ps, pm = plt.find_size_metrickey(p)
      if not pm then
	 return nil
      else
	 g=plt.new_jfm_glue(ps,pm,
				plt.find_char_type('boxbdd',pm),
				plt.find_char_type(p.char,pm))
      end
   else -- p and q are not nil
      qs, qm = plt.find_size_metrickey(q)
      ps, pm = plt.find_size_metrickey(p)
      if (not pm) and (not qm) then 
	 -- Both p and q are NOT Japanese glyph node
	 return nil
      elseif (qs==ps) and (qm==pm) then 
	 -- Both p and q are Japanese glyph node, and same metric and size
	 g=plt.new_jfm_glue(ps,pm,
				plt.find_char_type(q.char,qm),
				plt.find_char_type(p.char,pm))
      elseif not qm then
	 -- q is not Japanese glyph node
	 g=plt.new_jfm_glue(ps,pm,
				plt.find_char_type('jcharbdd',pm),
				plt.find_char_type(p.char,pm))
      elseif not pm then
	 -- p is not Japanese glyph node
	 g=plt.new_jfm_glue(qs,qm,
				plt.find_char_type(q.char,qm),
				plt.find_char_type('jcharbdd',qm))
      else
	 g=plt.new_jfm_glue(qs,qm,
				plt.find_char_type(q.char,qm),
				plt.find_char_type('diffmet',qm))
	 h=plt.new_jfm_glue(ps,pm,
				plt.find_char_type('diffmet',pm),
				plt.find_char_type(p.char,pm))
	 g=plt.calc_between_two_jchar_aux(g,h)
      end
   end
   return g
end


-- In the beginning of a hbox created by line breaking, there are the followings:
--   o a hbox by \parindent
--   o a whatsit node which contains local paragraph materials.
-- When we insert jfm glues, we ignore these nodes.
function plt.is_parindent_box(p)
   if node.type(p.id)=='hlist' then 
      return (p.subtype==3)
      -- hlist (subtype=3) is a box by \parindent
   elseif node.type(p.id)=='whatsit' then 
      return (p.subtype==node.subtype('local_par'))
   end
end

function plt.add_kinsoku_penalty(head,p)
   local c = p.char
   if not plt.penalty_table[c] then return false; end
   if plt.penalty_table[c].pre then
      local q = node.prev(p)
      if (not q) and node.type(q.id)=='penalty' then
	 q.penalty=q.penalty+plt.penalty_table[c].pre
      else 
	 q=node.new(node.id('penalty'))
	 q.penalty=plt.penalty_table[c].pre
	 node.insert_before(head,p,q)
      end
   end
   if plt.penalty_table[c].post then
      local q = node.next(p)
      if (not q) and node.type(q.id)=='penalty' then
	 q.penalty=q.penalty+plt.penalty_table[c].post
	 return false
      else 
	 q=node.new(node.id('penalty'))
	 q.penalty=plt.penalty_table[c].post
	 node.insert_after(head,p,q)
	 return true
      end
   end
end

-- Insert jfm glue: main routine

function plt.insert_jfm_glue(head)
   local p = head
   local q = nil  -- the previous node of p
   local g
   local ihb_flag = false
   if not p then 
      return head 
   end
   while p and  plt.is_parindent_box(p) do p=node.next(p) end
   while p do
      if node.type(p.id)=='whatsit' and p.subtype==44
         and p.user_id==30111 then
	 g=p; p=node.next(p); 
	 ihb_flag=true; head,p=node.remove(head, g)
      else
	 g=plt.calc_between_two_jchar(q,p)
	 if g and (not ihb_flag) then
	    h = node.insert_before(head,p,g)
	    if not q then head=h end 
	    -- If p is the first node (=head), the skip is inserted
	    -- before head. So we must change head.
	 end
	 q=p; ihb_flag=false
	 if plt.is_japanese_glyph_node(p) 
            and plt.add_kinsoku_penalty(head,p) then
	    p=node.next(p)
	 end
	 p=node.next(p)
      end
   end
   -- Insert skip after the last node
   g=plt.calc_between_two_jchar(q,nil)
   if g then
      h = node.insert_after(head,q,g)
   end
   return head
end



-- Insert \xkanjiskip at the boundaries between Japanese characters 
-- and non-Japanese characters. 
-- We also insert \kanjiskip between Kanji in this function.
plt.kanji_skip={}
plt.xkanji_skip={}
plt.insert_skip=0 
plt.cx = nil
    -- 0: ``no_skip'', 1: ``after_schar'', 2: ``after_wchar''
-- These variables are ``global'', because we want to avoid to write one large function.
function plt.insert_kanji_skip(head)
   if tex.count['plt@autospc']==0 then
      plt.kanji_skip=tex.skip['kanjiskip']
   else
      plt.kanji_skip=node.new(node.id('glue_spec'))
      plt.kanji_skip.width=0;  plt.kanji_skip.stretch=0; plt.kanji_skip.shrink=0
   end
   if tex.count['plt@autoxspc']==0 then
      plt.xkanji_skip=tex.skip['xkanjiskip']
   else
      plt.xkanji_skip=node.new(node.id('glue_spec'))
      plt.xkanji_skip.width=0;  plt.xkanji_skip.stretch=0; plt.xkanji_skip.shrink=0
   end
   local p=head -- 「現在のnode」
   local q=nil  -- pの一つ前 
   plt.insert_skip=0
   while p do
      if node.type(p.id)=='glyph' then
	 repeat 
	    plt.insks_around_char(head,q,p)
	    q=p; p=node.next(p)
	 until (not p) or node.type(p.id)~='glyph'
      else
	 if node.type(p.id) == 'hlist' then
	    plt.insks_around_hbox(head,q,p)
	 elseif node.type(p.id) == 'penalty' then
	    plt.insks_around_penalty(head,q,p)
	 elseif node.type(p.id) == 'kern' then
	    plt.insks_around_kern(head,q,p)
	 elseif node.type(p.id) == 'math' then
	    plt.insks_around_math(head,q,p)
	 elseif node.type(p.id) == 'ins' or node.type(p.id) == 'mark'
            or node.type(p.id) == 'adjust'
            or node.type(p.id) == 'whatsit' then
	    -- do nothing
	    p=p
	 else
	    -- rule, disc, glue, margin_kern
	    plt.insert_skip=0
	 end
	 q=p; p=node.next(p)
      end
   end
   return head
end

-- Insert \xkanjiskip before p, a glyph node
-- TODO; ligature
function plt.insks_around_char(head,q,p)
   local a=plt.inhibit_xsp_table[p.char]
   if plt.is_japanese_glyph_node(p) then
      plt.cx=p.char
      if plt.is_japanese_glyph_node(q)  then
	 local g = node.new(node.id('glue'))
	 g.subtype=0; g.spec=node.copy(plt.kanji_skip)
	 node.insert_before(head,p,g)
      elseif plt.insert_skip==1 then
	 plt.insert_akxsp(head,q)
      end
      plt.insert_skip=2
   else
      if not a then a=3 end
      if plt.insert_skip==2 then
	 plt.insert_kaxsp(head,q,a)
      end
      if  a>=2 then
	 plt.insert_skip=1
      else
	 plt.insert_skip=0
      end
   end
end

function plt.insert_akxsp(head,q)
   local f = plt.inhibit_xsp_table[plt.cx]
   local g
   if f then 
      if f<=1 then return end
   end
   g = node.new(node.id('glue'))
   g.subtype=0; g.spec=node.copy(plt.xkanji_skip)
   node.insert_after(head,q,g)
end

function plt.insert_kaxsp(head,q,a)
   local g=true
   local f=plt.inhibit_xsp_table[plt.cx]
   if a%2 == 1 then
      if f then 
	 if f%2==0 then g=false end
      end
   else 
      g=false
   end
   if g then
      g = node.new(node.id('glue'))
      g.subtype=0; g.spec=node.copy(plt.xkanji_skip)
      node.insert_after(head,q,g)
   end
end

-- Return first and last glyph nodes in a hbox
plt.first_char = nil
plt.last_char = nil
plt.find_first_char = nil
function plt.check_box(bp)
   local p, flag
   p=bp; flag=false
   while p do
      if node.type(p.id)=='glyph' then
	 repeat 
	    if plt.find_first_char then
	       plt.first_char=p; plt.find_first_char=false
	    end
	    plt.last_char=p; flag=true; p=node.next(p)
	    if not p then return flag end
	 until node.type(p.id)~='glyph'
      end
      if node.type(p.id)=='hlist' then
	 flag=true
	 if p.shift==0 then
	    if plt.check_box(p.head) then flag=true end
	 else if plt.find_first_char then 
	       plt.find_first_char=false
	    else 
	       plt.last_char=nil
	    end
	 end
      elseif node.type(p.id) == 'ins' or node.type(p.id) == 'mark'
         or node.type(p.id) == 'adjust' 
         or node.type(p.id) == 'whatsit' or node.type(p.id) == 'penalty' then
	 p=p
      else
	 flag=true
	 if plt.find_first_char then 
	    plt.find_first_char=false
	 else 
	    plt.last_char=nil
	 end
      end
      p=node.next(p)
   end
   return flag
end 

-- Insert \xkanjiskip around p, an hbox
function plt.insks_around_hbox(head,q,p)
   if p.shift==0 then
      plt.find_first_char=true
      if plt.check_box(p.head) then
	 -- first char
	 if plt.is_japanese_glyph_node(plt.first_char) then
	    plt.cx=plt.first_char.char
	    if plt.insert_skip==1 then 
	       plt.insert_akxsp(head,q)
	    elseif plt.insert_skip==2 then
	       local g = node.new(node.id('glue'))
	       g.subtype=0; g.spec=node.copy(plt.kanji_skip)
	       node.insert_before(head,p,g)
	    end
	    plt.insert_skip=2
	 elseif plt.first_char then
	    local a=plt.inhibit_xsp_table[plt.first_char.char]
	    if not a then a=3 end
	    if plt.insert_skip==2 then
	       local g = node.new(node.id('glue'))
	       g.subtype=0; g.spec=node.copy(plt.kanji_skip)
	       node.insert_after(head,q,g)
	    end
	    if  a>=2 then
	       plt.insert_skip=1
	    else
	       plt.insert_skip=0
	    end
	 end
	 -- last char
	 if plt.is_japanese_glyph_node(plt.last_char) then
	    if plt.is_japanese_glyph_node(node.next(p)) then
	       local g = node.new(node.id('glue'))
	       g.subtype=0; g.spec=node.copy(plt.kanji_skip)
	       node.insert_after(head,p,g)
	    end
	    plt.insert_skip=2
	 elseif plt.last_char then
	    local a=plt.inhibit_xsp_table[plt.last_char.char]
	    if not a then a=3 end
	    if a>=2 then
	       plt.insert_skip=1
	    else
	       plt.insert_skip=0
	    end
	 else plt.insert_skip=0
	 end
      else plt.insert_skip=0
      end
   else plt.insert_skip=0
   end
end

-- Insert \xkanjiskip around p, a penalty
function plt.insks_around_penalty(head,q,p)
   local r=node.next(p)
   if (not r) and node.type(r.id)=='glyph' then
      local a=plt.inhibit_xsp_table[r.char]
      if plt.is_japanese_glyph_node(r) then
	 plt.cx=r.char
	 if plt.is_japanese_glyph_node(p)  then
	    local g = node.new(node.id('glue'))
	    g.subtype=0; g.spec=node.copy(plt.kanji_skip)
	    node.insert_before(head,r,g)
	 elseif plt.insert_skip==1 then
	    plt.insert_akxsp(head,p)
	 end
	 q=p; p=node.next(p)
	 plt.insert_skip=2
      else
	 if not a then a=3 end
	 if plt.insert_skip==2 then
	    plt.insert_kaxsp(head,p,a)
	 end
	 if  a>=2 then
	    plt.insert_skip=1
	 else
	    plt.insert_skip=0
	 end
      end
   end
end

-- Insert \xkanjiskip around p, a kern
function plt.insks_around_kern(head,q,p)
   if p.subtype==1 then -- \kern or \/
      if node.has_attribute(p,luatexbase.attributes['plt@icflag']) then
	 p=p -- p is a kern from \/: do nothing
      else
	 plt.insert_skip=0
      end
   elseif p.subtype==2 then -- \accent: We ignore the accent character.
      local v = node.next(node.next(node.next(p)))
      if v and node.type(v.id)=='glyph' then
	 plt.insks_around_char(head,q,v)
      end
   end
end

-- Insert \xkanjiskip around p, a math_node
function plt.insks_around_math(head,q,p)
   local a=plt.inhibit_xsp_table['math']
   if not a then a=3 end
   if (p.subtype==0) and (plt.insert_skip==2) then
      plt.insert_kaxsp(head,q,a)
      plt.insert_skip=0
   else
      plt.insert_skip=1
   end
end

-- Shift baseline
function plt.baselineshift(head)
   local p=head
   local m=false -- is in math mode?
   while p do 
      local v=node.has_attribute(p,luatexbase.attributes['plt@yablshift'])
      if v then
	 if node.type(p.id)=='glyph' then
	    p.yoffset=p.yoffset-v
	 elseif node.type(p.id)=='math' then
	    m=(p.subtype==0)
	 end
	 if m then -- boxes and rules are shifted only in math mode
	    if node.type(p.id)=='hlist' or node.type(p.id)=='vlist' then
	       p.shift=p.shift+v
	    elseif node.type(p.id)=='rule' then
	       p.height=p.height-v; p.depth=p.depth+v 
	    end
	 end
      end
      p=node.next(p)
   end
   return head
end


-- main process
function plt.main_process(head)
   local p = head
   p = plt.insert_jfm_glue(p)
   p = plt.insert_kanji_skip(p)
   p = plt.baselineshift(p)
   return p
end

--- the following function is modified from jafontspec.lua (by K. Maeda).
--- Instead of "%", we use U+FFFFF for suppressing spaces.
utf = unicode.utf8
function plt.process_input_buffer(buffer)
--	print(utf.byte(buffer, utf.len(buffer)))
   if utf.len(buffer) > 0 
        and plt.is_ucs_in_japanese_char(utf.byte(buffer, utf.len(buffer))) then
	buffer = buffer .. string.char(0xF3,0xBF,0xBF,0xBF) -- U+FFFFF
   end
   return buffer
end


---------- Hyphenate

-- 
function plt.suppress_hyphenate_ja(head)
   local p=head
   while p do 
      if node.type(p.id)=='glyph' and  plt.is_ucs_in_japanese_char(p.char) then
	 local v = node.has_attribute(p,luatexbase.attributes['plt@curjfnt'])
	 if v then p.font=v end
	 v=node.has_attribute(p,luatexbase.attributes['plt@ykblshift'])
	 if v then 
	    node.set_attribute(p,luatexbase.attributes['plt@yablshift'],v)
	 else
	    node.unset_attribute(p,luatexbase.attributes['plt@yablshift'])
	 end
	 p.lang=plt.ja_lang_number
      end
      p=node.next(p)
   end
   lang.hyphenate(head) 
end

-- callback
luatexbase.add_to_callback('process_input_buffer', 
   function (buffer)
     return plt.process_input_buffer(buffer)
   end,'plt.process_input_buffer')
luatexbase.add_to_callback('pre_linebreak_filter', 
   function (head,groupcode)
     return plt.main_process(head)
   end,'plt.pre_linebreak_filter')
luatexbase.add_to_callback('hpack_filter', 
  function (head,groupcode,size,packtype)
     return plt.main_process(plt.replace_ja_font(head))
  end,'plt.hpack_filter')
luatexbase.add_to_callback('hyphenate', 
 function (head,tail)
    return plt.suppress_hyphenate_ja(head)
 end,'plt.hyphenate')
