/* ==========================================================================
   Файл: def_code.c
   Компилятор: Turbo C 2.0
   Описание: библиотека для автоматического определения кодировки текста
             (ALT, WIN, KOI). Функция m_def_code - для случая, когда текст
             в памяти, функция f_def_code - когда текст в файле.
   Описание алгоритма: http://ivr.webzone.ru/articles/defcod_2/
   (c) Иван Рощин, Москва, 2004.
 ========================================================================= */

#include <stdio.h>

/* Глобальные переменные */

static int len_;
static unsigned char *p_;
static FILE *f_;

/* Таблица сочетаний */

static unsigned char table_2s[128]={0xFF,0xFF,0xFF,0xC7,0xFE,0xBE,0xF7,0xFB,
 0xFD,0xBF,0xF7,0xF9,0xFC,0xBE,0xF1,0x80,0xFF,0xFF,0xF7,0xBB,0xFF,0xFF,0xFF,
 0xCF,0xDE,0xBF,0xD1,0x08,0xFF,0xBF,0xF1,0xBF,0xFF,0xFF,0xFF,0xC7,0x1D,0x3F,
 0x7F,0x81,0xA7,0xB6,0xF2,0x82,0xFF,0xFF,0x75,0xDB,0xFC,0xBF,0xD7,0x9D,0xFF,
 0xAE,0xFB,0xDF,0xFF,0xFF,0xFF,0xC7,0x84,0xB7,0xF3,0x9F,0xFF,0xFF,0xFF,0xDB,
 0xFF,0xBF,0xFF,0xFF,0xFD,0xBF,0xFF,0xFF,0xFF,0xFF,0xE7,0xC7,0x84,0x9E,0xF0,
 0x12,0xBC,0xBF,0xF0,0x84,0xA4,0xBA,0x10,0x10,0xA4,0xBE,0xB8,0x88,0xAC,0xBF,
 0xF7,0x0A,0x84,0x86,0x90,0x08,0x04,0x00,0x00,0x03,0x7F,0xFD,0xF7,0xC1,0x7D,
 0xAE,0x6F,0xCB,0x15,0x3D,0xFC,0x00,0x7F,0x7D,0xE7,0xC2,0x7F,0xFD,0xF7,0xC3};

/* =========================================================================
   Вспомогательная функция alt2num.
   Вход: a - код русской буквы в кодировке ALT.
   Выход: порядковый номер этой буквы (0-31).
 ========================================================================= */

static int alt2num (int a)
{
 if (a>=0xE0) a-=0x30;
 return (a&31);
}

/* =========================================================================
   Вспомогательная функция koi2num.
   Вход: a - код русской буквы в кодировке KOI.
   Выход: порядковый номер этой буквы (0-31).
 ========================================================================= */

static int koi2num (int a)
{
 static unsigned char t[32]={30,0,1,22,4,5,20,3,21,8,9,10,11,12,13,14,15,31,
  16,17,18,19,6,2,28,27,7,24,29,25,23,26};

 return (t[a&31]);
}

/* =========================================================================
   Вспомогательная функция work_2s - обработка двухбуквенного сочетания.
   Вход:  с1 - порядковый номер первой буквы (0-31),
          c2 - порядковый номер второй буквы (0-31),
          check - надо ли проверять, встречалось ли сочетание раньше
                  (1 - да, 0 - нет),
          buf - адрес массива с информацией о встреченных сочетаниях.
   Выход: 0 - указанное сочетание уже встречалось раньше,
          1 - сочетание не встречалось раньше и является допустимым,
          2 - сочетание не встречалось раньше и является недопустимым.
 ========================================================================= */

static int work_2s (int c1, int c2, int check, unsigned char buf[128])
{
 int i=(c1<<2)+(c2>>3); /* Номер байта в массиве. */
 int mask=0x80>>(c2&7); /* Маска, соответствующая номеру бита в байте. */

 /* Если check=1, проверяем: если соответствующий бит массива buf равен 0,
    значит, указанное сочетание уже встречалось раньше. Тогда выходим из
    функции, возвращая 0. Если же сочетание не встречалось, то помечаем, что
    оно встретилось (обнуляем соответствующий бит массива buf). */

 if (check==1)
 {
  if ((buf[i]&mask)==0) return (0);
  buf[i]&=~mask;
 }

 /* Проверяем, допустимо сочетание или нет. */

 if ((table_2s[i]&mask)!=0) return (1); /* Допустимо. */
 return (2);                            /* Недопустимо. */
}

/* =========================================================================
   Вспомогательная функция def_code - определение кодировки текста. Функции
   m_def_code и f_def_code - лишь надстройки над этой функцией.
   Вход:  get_char - указатель на функцию, которую надо вызывать для получения
                     очередного символа текста. Функция должна возвращать либо
                     код символа, либо, при достижении конца текста, -1.
          n - количество различных сочетаний русских букв (1-255), которого
              достаточно для определения кодировки.
   Выход: 0 - текст в кодировке ALT, 1 - WIN, 2 - KOI.
 ========================================================================= */

static int def_code (int (*get_char)(), int n)
{
 /* В массиве buf_1 хранится информация о том, какие сочетания руских букв
    уже встречались в варианте ALT, а в массиве buf_2 - в варианте WIN. */

 unsigned char buf_1 [128];
 unsigned char buf_2 [128];

 int bad_1=0;
 int bad_2=0;
 int bad_3=0;
 int all_1=0;
 int all_3=0;  /* all_2=all_3 */

 int c1; int c2=0; /* Символы текущего обрабатываемого сочетания. */
 int i;

 /* Инициализация buf_1 и buf_2. */

 for (i=0;i<128;i++) buf_1[i]=0xFF;
 for (i=0;i<128;i++) buf_2[i]=0xFF;

 /* Главный цикл - обработка сочетаний для каждого из трёх вариантов. Цикл
    выполняется, пока не кончится текст или в каком-либо из вариантов не
    встретится n сочетаний. */

 while (((c1=c2,c2=(*get_char)())!=-1)&&(all_1<n)&&(all_3<n))
 {
  /* Вариант ALT. Вначале проверяем, являются ли символы текущего сочетания
     кодами русских букв в кодировке ALT. */

  if ((((c1>=0x80)&&(c1<0xB0))||((c1>=0xE0)&&(c1<0xF0)))&&
      (((c2>=0x80)&&(c2<0xB0))||((c2>=0xE0)&&(c2<0xF0))))
  {
   switch (work_2s(alt2num(c1),alt2num(c2),1,buf_1)) /* Обработали. */
   {
    case 2: bad_1++;
    case 1: all_1++;
   }
  }
  /* Варианты WIN и KOI. Вначале проверяем, являются ли символы текущего
     сочетания кодами русских букв в этих кодировках (в обеих кодировках
     диапазоны кодов русских букв совпадают). */

  if ((c1&c2)>=0xC0) /* Эквивалентно условию (c1>=0xC0)&&(c2>=0xC0). */
  {
   switch (work_2s(c1&31,c2&31,1,buf_2)) /* Обработали. */
   {
    case 0: continue; /* Если сочетание букв уже встречалось в варианте WIN,
                         то оно уже встречалось и в варианте KOI, так что
                         пропускаем обработку варианта KOI и переходим
                         к следующей итерации главного цикла. */
    case 2: bad_2++;
   }

  /* Если сочетание букв ещё не встречалось в варианте WIN, то оно заведомо
     не встречалось и в варианте KOI, поэтому специально проверять это не
     надо - значит, функцию work_2s вызываем с параметром check, равным 0. */

   switch (work_2s(koi2num(c1),koi2num(c2),0,NULL)) /* Обработали. */
   {
    case 2: bad_3++;
    case 1: all_3++;
   }
  }
 }

 /* Данные собраны. Теперь, если в каком-либо из вариантов недопустимых
    сочетаний не больше 1/32 от общего их числа, то считаем, что их и не
    было. */

 if (bad_1<=(all_1>>5)) bad_1=0;
 if (bad_2<=(all_3>>5)) bad_2=0;
 if (bad_3<=(all_3>>5)) bad_3=0;

 /* Получаем результат. */

 {
  unsigned int a=((255-bad_1)<<8)+all_1;
  unsigned int b=((255-bad_2)<<8)+all_3;
  unsigned int c=((255-bad_3)<<8)+all_3;

  if ((a>=b)&&(a>=c)) return (0);
  if (b>=c) return (1); else return (2);
 }
}

/* =========================================================================
   Вспомогательная функция m_get_char вызывается из функции def_code, когда
   та вызвана из m_def_code.
   Выход: очередной символ текста или -1, если текст кончился.
 ========================================================================= */

static int m_get_char()
{
 if (len_==0) return (-1);
 len_--;
 return (*(p_++));
}

/* =========================================================================
   Функция m_def_code - определение кодировки текста, находящегося в памяти.
   Вход:  p - адрес текста,
          len - длина текста,
          n - количество различных сочетаний русских букв (1-255), которого
              достаточно для определения кодировки.
   Выход: 0 - текст в кодировке ALT, 1 - WIN, 2 - KOI.
 ========================================================================= */

int m_def_code (unsigned char *p, int len, int n)
{
 /* Присваиваем значения глобальным переменным len_ и p_, которые будут
    доступны из функции m_get_char. */
 len_=len;
 p_=p;
 /* Получаем результат. */
 return (def_code(&m_get_char,n));
}

/* =========================================================================
   Вспомогательная функция f_get_char вызывается из функции def_code, когда
   та вызвана из f_def_code.
   Выход: очередной символ текста или -1, если текст кончился.
 ========================================================================= */

static int f_get_char()
{
 int a=fgetc(f_);
 if (a==EOF) return (-1);
 return (a);
}

/* =========================================================================
   Функция f_def_code - определение кодировки текста, находящегося в файле.
   Вход:  f - указатель на структуру, связанную с файлом (указатель текущей
              позиции в файле должен указывать на начало файла),
          n - количество различных сочетаний русских букв (1-255), которого
              достаточно для определения кодировки.
   Выход: 0 - текст в кодировке ALT, 1 - WIN, 2 - KOI.
 ========================================================================= */

int f_def_code (FILE *f, int n)
{
 /* Присваиваем значение глобальной переменной f_, которая будет доступна
    из функции f_get_char. */
 f_=f;
 /* Получаем результат. */
 return (def_code(&f_get_char,n));
}
