static bool
hangul_buffer_backspace(HangulBuffer *buffer)
{
    if (buffer->index >= 0) {
	ucschar ch = hangul_buffer_pop(buffer);
	if (ch == 0)
	    return false;

	if (buffer->index >= 0) {
	    if (hangul_is_choseong(ch)) {
		ch = hangul_buffer_peek(buffer);
		buffer->choseong = hangul_is_choseong(ch) ? ch : 0;
		return true;
	    } else if (hangul_is_jungseong(ch)) {
		ch = hangul_buffer_peek(buffer);
		buffer->jungseong = hangul_is_jungseong(ch) ? ch : 0;
		return true;
	    } else if (hangul_is_jongseong(ch)) {
		ch = hangul_buffer_peek(buffer);
		buffer->jongseong = hangul_is_jongseong(ch) ? ch : 0;
		return true;
	    }
	} else {
	    buffer->choseong = 0;
	    buffer->jungseong = 0;
	    buffer->jongseong = 0;
	    return true;
	}
    }
    return false;
}
static ucschar
hangul_ic_combine(HangulInputContext* hic, ucschar first, ucschar second)
{
    if (!hic->option_combi_on_double_stroke) {
	if (hangul_keyboard_get_type(hic->keyboard) == HANGUL_KEYBOARD_TYPE_JAMO) {
	    /* 옛한글은 아래 규칙을 적용하지 않아야 입력가능한 글자가 있으므로
	     * 적용하지 않는다. */
	    if (first == second &&
		hangul_is_jamo_conjoinable(first)) {
		return 0;
	    }
	}
    }

    ucschar combined = 0;
    combined = hangul_keyboard_combine(hic->keyboard, 0, first, second);

    if (!hic->option_non_choseong_combi) {
	if (hangul_is_choseong(first) && hangul_is_choseong(second) &&
	    hangul_is_jongseong(combined)) {
	    return 0;
	}
    }

    return combined;
}
static void
hangul_buffer_push(HangulBuffer *buffer, ucschar ch)
{
    if (hangul_is_choseong(ch)) {
	buffer->choseong = ch;
    } else if (hangul_is_jungseong(ch)) {
	buffer->jungseong = ch;
    } else if (hangul_is_jongseong(ch)) {
	buffer->jongseong = ch;
    } else {
    }

    buffer->stack[++buffer->index] = ch;
}
static inline bool
hangul_ic_push(HangulInputContext *hic, ucschar c)
{
    ucschar buf[64] = { 0, };
    if (hic->on_transition != NULL) {
	ucschar cho, jung, jong;
	if (hangul_is_choseong(c)) {
	    cho  = c;
	    jung = hic->buffer.jungseong;
	    jong = hic->buffer.jongseong;
	} else if (hangul_is_jungseong(c)) {
	    cho  = hic->buffer.choseong;
	    jung = c;
	    jong = hic->buffer.jongseong;
	} else if (hangul_is_jongseong(c)) {
	    cho  = hic->buffer.choseong;
	    jung = hic->buffer.jungseong;
	    jong = c;
	} else {
	    hangul_ic_flush_internal(hic);
	    return false;
	}

	hangul_jaso_to_string(cho, jung, jong, buf, N_ELEMENTS(buf));
	if (!hic->on_transition(hic, c, buf, hic->on_transition_data)) {
	    hangul_ic_flush_internal(hic);
	    return false;
	}
    } else {
	if (!hangul_is_jamo(c)) {
	    hangul_ic_flush_internal(hic);
	    return false;
	}
    }

    hangul_buffer_push(&hic->buffer, c);
    return true;
}
static bool
hangul_ic_process_romaja(HangulInputContext *hic, int ascii, ucschar ch)
{
    ucschar jong;
    ucschar combined;

    if (!hangul_is_jamo(ch) && ch > 0) {
	hangul_ic_save_commit_string(hic);
	hangul_ic_append_commit_string(hic, ch);
	return true;
    }

    if (isupper(ascii)) {
	hangul_ic_save_commit_string(hic);
    }

    if (hic->buffer.jongseong) {
	if (ascii == 'x' || ascii == 'X') {
	    ch = 0x110c;
	    hangul_ic_save_commit_string(hic);
	    if (!hangul_ic_push(hic, ch)) {
		return false;
	    }
	} else if (hangul_is_choseong(ch) || hangul_is_jongseong(ch)) {
	    if (hangul_is_jongseong(ch))
		jong = ch;
	    else
		jong = hangul_choseong_to_jongseong(ch);
	    combined = hangul_combination_combine(hic->combination,
					      hic->buffer.jongseong, jong);
	    if (hangul_is_jongseong(combined)) {
		if (!hangul_ic_push(hic, combined)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else if (hangul_is_jungseong(ch)) {
	    if (hic->buffer.jongseong == 0x11bc) {
		hangul_ic_save_commit_string(hic);
		hic->buffer.choseong = 0x110b;
		hangul_ic_push(hic, ch);
	    } else {
		ucschar pop, peek;
		pop = hangul_ic_pop(hic);
		peek = hangul_ic_peek(hic);

		if (hangul_is_jungseong(peek)) {
		    if (pop == 0x11aa) {
			hic->buffer.jongseong = 0x11a8;
		    } else {
			hic->buffer.jongseong = 0;
		    }
		    hangul_ic_save_commit_string(hic);
		    hangul_ic_push(hic, hangul_jongseong_to_choseong(pop));
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		} else {
		    ucschar choseong = 0, jongseong = 0; 
		    hangul_jongseong_dicompose(hic->buffer.jongseong,
					       &jongseong, &choseong);
		    hic->buffer.jongseong = jongseong;
		    hangul_ic_save_commit_string(hic);
		    hangul_ic_push(hic, choseong);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	} else {
	    goto flush;
	}
    } else if (hic->buffer.jungseong) {
	if (hangul_is_choseong(ch)) {
	    if (hic->buffer.choseong) {
		jong = hangul_choseong_to_jongseong(ch);
		if (hangul_is_jongseong(jong)) {
		    if (!hangul_ic_push(hic, jong)) {
			if (!hangul_ic_push(hic, ch)) {
			    return false;
			}
		    }
		} else {
		    hangul_ic_save_commit_string(hic);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		if (!hangul_ic_push(hic, ch)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	} else if (hangul_is_jungseong(ch)) {
	    combined = hangul_combination_combine(hic->combination,
						  hic->buffer.jungseong, ch);
	    if (hangul_is_jungseong(combined)) {
		if (!hangul_ic_push(hic, combined)) {
		    return false;
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		hic->buffer.choseong = 0x110b;
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else if (hangul_is_jongseong(ch)) {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    goto flush;
	}
    } else if (hic->buffer.choseong) {
	if (hangul_is_choseong(ch)) {
	    combined = hangul_combination_combine(hic->combination,
						  hic->buffer.choseong, ch);
	    if (combined == 0) {
		hic->buffer.jungseong = 0x1173;
		hangul_ic_flush_internal(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    } else {
		if (!hangul_ic_push(hic, combined)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	} else if (hangul_is_jongseong(ch)) {
	    hic->buffer.jungseong = 0x1173;
	    hangul_ic_save_commit_string(hic);
	    if (ascii == 'x' || ascii == 'X')
		ch = 0x110c;
	    if (!hangul_ic_push(hic, ch)) {
		return false;
	    }
	} else {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	}
    } else {
	if (ascii == 'x' || ascii == 'X') {
	    ch = 0x110c;
	}

	if (!hangul_ic_push(hic, ch)) {
	    return false;
	} else {
	    if (hic->buffer.choseong == 0 && hic->buffer.jungseong != 0)
		hic->buffer.choseong = 0x110b;
	}
    }

    hangul_ic_save_preedit_string(hic);
    return true;

flush:
    hangul_ic_flush_internal(hic);
    return false;
}
static bool
hangul_ic_process_jaso(HangulInputContext *hic, ucschar ch)
{
    if (hangul_is_choseong(ch)) {
	if (hic->buffer.choseong == 0) {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    ucschar choseong = 0;
	    if (hangul_is_choseong(hangul_ic_peek(hic))) {
		choseong = hangul_combination_combine(hic->combination,
						  hic->buffer.choseong, ch);
	    }
	    if (choseong) {
		if (!hangul_ic_push(hic, choseong)) {
		    if (!hangul_ic_push(hic, choseong)) {
			return false;
		    }
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	}
    } else if (hangul_is_jungseong(ch)) {
	if (hic->buffer.jungseong == 0) {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    ucschar jungseong = 0;
	    if (hangul_is_jungseong(hangul_ic_peek(hic))) {
		jungseong = hangul_combination_combine(hic->combination,
						 hic->buffer.jungseong, ch);
	    }
	    if (jungseong) {
		if (!hangul_ic_push(hic, jungseong)) {
		    if (!hangul_ic_push(hic, jungseong)) {
			return false;
		    }
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	}
    } else if (hangul_is_jongseong(ch)) {
	if (hic->buffer.jongseong == 0) {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    ucschar jongseong = 0;
	    if (hangul_is_jongseong(hangul_ic_peek(hic))) {
		jongseong = hangul_combination_combine(hic->combination,
						   hic->buffer.jongseong, ch);
	    }
	    if (jongseong) {
		if (!hangul_ic_push(hic, jongseong)) {
		    if (!hangul_ic_push(hic, jongseong)) {
			return false;
		    }
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	}
    } else if (ch > 0) {
	hangul_ic_save_commit_string(hic);
	hangul_ic_append_commit_string(hic, ch);
    } else {
	hangul_ic_save_commit_string(hic);
	return false;
    }

    hangul_ic_save_preedit_string(hic);
    return true;
}
static bool
hangul_ic_process_jamo(HangulInputContext *hic, ucschar ch)
{
    ucschar jong;
    ucschar combined;

    if (!hangul_is_jamo(ch) && ch > 0) {
	hangul_ic_save_commit_string(hic);
	hangul_ic_append_commit_string(hic, ch);
	return true;
    }

    if (hic->buffer.jongseong) {
	if (hangul_is_choseong(ch)) {
	    jong = hangul_choseong_to_jongseong(ch);
	    combined = hangul_combination_combine(hic->combination,
					      hic->buffer.jongseong, jong);
	    if (hangul_is_jongseong(combined)) {
		if (!hangul_ic_push(hic, combined)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else if (hangul_is_jungseong(ch)) {
	    ucschar pop, peek;
	    pop = hangul_ic_pop(hic);
	    peek = hangul_ic_peek(hic);

	    if (hangul_is_jungseong(peek)) {
		hic->buffer.jongseong = 0;
		hangul_ic_save_commit_string(hic);
		hangul_ic_push(hic, hangul_jongseong_to_choseong(pop));
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    } else {
		ucschar choseong = 0, jongseong = 0; 
		hangul_jongseong_dicompose(hic->buffer.jongseong,
					   &jongseong, &choseong);
		hic->buffer.jongseong = jongseong;
		hangul_ic_save_commit_string(hic);
		hangul_ic_push(hic, choseong);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    goto flush;
	}
    } else if (hic->buffer.jungseong) {
	if (hangul_is_choseong(ch)) {
	    if (hic->buffer.choseong) {
		jong = hangul_choseong_to_jongseong(ch);
		if (hangul_is_jongseong(jong)) {
		    if (!hangul_ic_push(hic, jong)) {
			if (!hangul_ic_push(hic, ch)) {
			    return false;
			}
		    }
		} else {
		    hangul_ic_save_commit_string(hic);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		if (!hangul_ic_push(hic, ch)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	} else if (hangul_is_jungseong(ch)) {
	    combined = hangul_combination_combine(hic->combination,
						  hic->buffer.jungseong, ch);
	    if (hangul_is_jungseong(combined)) {
		if (!hangul_ic_push(hic, combined)) {
		    return false;
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    goto flush;
	}
    } else if (hic->buffer.choseong) {
	if (hangul_is_choseong(ch)) {
	    combined = hangul_combination_combine(hic->combination,
						  hic->buffer.choseong, ch);
	    if (!hangul_ic_push(hic, combined)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	}
    } else {
	if (!hangul_ic_push(hic, ch)) {
	    return false;
	}
    }

    hangul_ic_save_preedit_string(hic);
    return true;

flush:
    hangul_ic_flush_internal(hic);
    return false;
}
static bool
hangul_ic_process_jamo(HangulInputContext *hic, ucschar ch)
{
    ucschar jong;
    ucschar combined;

    if (!hangul_is_jamo(ch) && ch > 0) {
	hangul_ic_save_commit_string(hic);
	hangul_ic_append_commit_string(hic, ch);
	return true;
    }

    if (hic->buffer.jongseong) {
	if (hangul_is_choseong(ch)) {
	    jong = hangul_ic_choseong_to_jongseong(hic, ch);
	    combined = hangul_ic_combine(hic, hic->buffer.jongseong, jong);
	    if (hangul_is_jongseong(combined)) {
		if (!hangul_ic_push(hic, combined)) {
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else if (hangul_is_jungseong(ch)) {
	    ucschar pop, peek;
	    pop = hangul_ic_pop(hic);
	    peek = hangul_ic_peek(hic);

	    if (hangul_is_jongseong(peek)) {
		ucschar choseong = hangul_jongseong_get_diff(peek,
						 hic->buffer.jongseong);
		if (choseong == 0) {
		    hangul_ic_save_commit_string(hic);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		} else {
		    hic->buffer.jongseong = peek;
		    hangul_ic_save_commit_string(hic);
		    hangul_ic_push(hic, choseong);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		hic->buffer.jongseong = 0;
		hangul_ic_save_commit_string(hic);
		hangul_ic_push(hic, hangul_jongseong_to_choseong(pop));
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    goto flush;
	}
    } else if (hic->buffer.jungseong) {
	if (hangul_is_choseong(ch)) {
	    if (hic->buffer.choseong) {
		jong = hangul_ic_choseong_to_jongseong(hic, ch);
		if (hangul_is_jongseong(jong)) {
		    if (!hangul_ic_push(hic, jong)) {
			if (!hangul_ic_push(hic, ch)) {
			    return false;
			}
		    }
		} else {
		    hangul_ic_save_commit_string(hic);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    } else {
		if (hic->option_auto_reorder) {
		    /* kr 처럼 자모가 역순인 경우 처리 */
		    if (!hangul_ic_push(hic, ch)) {
			if (!hangul_ic_push(hic, ch)) {
			    return false;
			}
		    }
		} else {
		    hangul_ic_save_commit_string(hic);
		    if (!hangul_ic_push(hic, ch)) {
			return false;
		    }
		}
	    }
	} else if (hangul_is_jungseong(ch)) {
	    combined = hangul_ic_combine(hic, hic->buffer.jungseong, ch);
	    if (hangul_is_jungseong(combined)) {
		if (!hangul_ic_push(hic, combined)) {
		    return false;
		}
	    } else {
		hangul_ic_save_commit_string(hic);
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    goto flush;
	}
    } else if (hic->buffer.choseong) {
	if (hangul_is_choseong(ch)) {
	    combined = hangul_ic_combine(hic, hic->buffer.choseong, ch);
	    /* 초성을 입력한 combine 함수에서 종성이 나오게 된다면
	     * 이전 초성도 종성으로 바꿔 주는 편이 나머지 처리에 편리하다.
	     * 이 기능은 MS IME 호환기능으로 ㄳ을 입력하는데 사용한다. */
	    if (hangul_is_jongseong(combined)) {
		hic->buffer.choseong = 0;
		ucschar pop = hangul_ic_pop(hic);
		ucschar jong = hangul_choseong_to_jongseong(pop);
		hangul_ic_push(hic, jong);
	    }

	    if (!hangul_ic_push(hic, combined)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	} else {
	    if (!hangul_ic_push(hic, ch)) {
		if (!hangul_ic_push(hic, ch)) {
		    return false;
		}
	    }
	}
    } else {
	if (!hangul_ic_push(hic, ch)) {
	    return false;
	}
    }

    hangul_ic_save_preedit_string(hic);
    return true;

flush:
    hangul_ic_flush_internal(hic);
    return false;
}