#include <textmenu.h>
#include <function.h>

#define SAVE_BUF_LEN 1000
 // Длина буфера для сохранения экрана

FUN Ext_Function;
#define between(a,x,b) (((x>=a)&&(x<=b))?YES:NO)
MODE WIN_X1=0,WIN_Y1=0,WIN_X2=24,WIN_Y2=79;
MODE FILLER=' ',
     ATTR=0x07,
     SHADOW=YES,
     EXPLODE=YES;

char Mouse=0x00; //Наличие "мыши"
char MouseX,MouseY;
char buttons;     // байт состояния/индикации кнопок

char buffer[SAVE_BUF_LEN];
static int bufpos;
static KEY Buf[ MAX_BUF ];  //Внутренний буфер клавиатуры
static KEY Start = 0,       //Указатели в буфере
	   End = 0;

unsigned char far *vid_mem=(unsigned char far *)0xb8000000L,
	      far *V;


void CURSOR_ON(void) {
 if (Mouse) {
  asm mov ax,1;
  asm int 033h;
 } return;
}

void CURSOR_OFF(void) {
 if (Mouse) {
  asm mov ax,2;
  asm int 033h;
 } return;
}

char set_attr (char col, char bk) {
 char ATTR=0x00;
 ATTR|=col;
 ATTR|=(bk<<4);
 return (ATTR);
}

void write_char (char x, char y, char ch) {
 V=vid_mem + (x<<7) + (x<<5) + (y<<1); // Сдвиг быстрее, чем умножение !
 *V++ = ch; // запись символа
 *V = ATTR; // запись атрибута
}

void write_string (char x, char y, char *str, char attr) {
 char i;
 CURSOR_OFF();
 V=vid_mem + (x<<7) + (x<<5) + (y<<1);
 for (i=0;  *(str+i); i++) {
  *V++ = *(str+i); // запись символа
  *V++ =   attr;     // запись атрибута
 }
 CURSOR_ON();
}

void clear (char x1,char y1,char x2,char y2) {
 char i,j;
 x2++; y2++; CURSOR_OFF();
 for (i=x1; i<x2; i++) {
  V = vid_mem + (i<<7) + (i<<5) + (y1<<1);
  for (j=y1; j<y2; j++) {
   *V++ = FILLER; // запись символа
   *V++ =   ATTR; // запись атрибута
  }
 } CURSOR_ON();
 return;
}

void center_string  (char x, char y1, char y2, char *str) {
 char i,y;
 y=(y1+y2-strlen_(str))>>1; CURSOR_OFF();
 for (i=0;  *(str+i); i++) write_char ( x, y+i, *(str+i));
 CURSOR_ON(); return;
}

void change_attr (char x1, char y1, char x2, char y2, char bkcolor) {
 MODE i,j,attr;
 x2++; y2++;
 bkcolor= (bkcolor&0x07)<<4; CURSOR_OFF();
 for (i=x1; i<x2; i++) {
  V = vid_mem + (i<<7) + (i<<5) + (y1<<1);
  for (j=y1; j<y2; j++) {
   V++;
   attr=((*V)&0x8f)|bkcolor;
   *V++ = attr; // запись атрибута
  }
 } CURSOR_ON();
}

void save (char x1,char y1,char x2,char y2) {
 char i,j,ch,attr;
 x2++; y2++; CURSOR_OFF();
 for (i=x1; i<x2; i++) {
  V = vid_mem + (i<<7) + (i<<5) + (y1<<1);
  for (j=y1; j<y2; j++) {
   ch =   *V++;  /* символ */
   attr = *V++;  /* атрибут */
   buffer[bufpos++]=ch;
   buffer[bufpos++]=attr;
  }
 } CURSOR_ON(); return;
}

void restore (char x1,char y1,char x2,char y2) {
 MODE ch,i,j,attr;
 int pos;
 CURSOR_OFF();
 bufpos-=(((x2-x1+1)*(y2-y1+1))<<1);
 pos=bufpos; x2++; y2++;
 for (i=x1; i<x2; i++) {
  V = vid_mem + (i<<7) + (i<<5) + (y1<<1);
  for (j=y1; j<y2; j++) {
   ch=buffer[pos++];
   attr=buffer[pos++];
   *V++ = ch; // запись символа
   *V++ = attr; // запись атрибута
  }
 } CURSOR_ON(); return;
}

void box (char x1, char y1, char x2, char y2, char sort) {
// Отрисовка рамки:
//  012 ┌─┐ ╔═╗ 
//  3 4 │1│ ║2║ 
//  567 └─┘ ╚═╝ 
 unsigned char border[3][8]={
  {' ',' ',' ',' ',' ',' ',' ',' '},
  {'┌','─','┐','│','│','└','─','┘'},
  {'╔','═','╗','║','║','╚','═','╝'}
 };
 char i;
 if (!sort) for (i=0; i<8; i++) border[0][i]=FILLER; // Задан байт-разделитель
 CURSOR_OFF();
 for(i=x1+1; i<x2; i++) { // Вниз
  write_char (i,y1,border[sort][3]);
  write_char (i,y2,border[sort][4]);
 }
 for (i=y1+1; i<y2; i++) { // Вправо
  write_char (x1,i,border[sort][1]);
  write_char (x2,i,border[sort][6]);
 }
 write_char( x1, y1, border[sort][0]);
 write_char( x1, y2, border[sort][2]);
 write_char( x2, y1, border[sort][5]);
 write_char( x2, y2, border[sort][7]);
 CURSOR_ON();
 return;
}

void window (char x1,char y1,char x2,char y2) {
 char j,cx1,cy1,cx2,cy2;
 if (EXPLODE) {
  cy1=(y2+y1)>>1;
  cy2=cy1+1;
  cx1=(x2+x1)>>1;
  cx2=cx1+1;
  j=0x0f;
  do {
   box (cx1,cy1,cx2,cy2,0x02);
   FILLER=' ';
   box (cx1,cy1,cx2,cy2,0x00);
   if (cx1>x1) cx1--;
   else j&=0x0e;
   if (cx2<x2) cx2++;
   else j&=0x0d;
   if (cy1>y1) cy1--;
   else j&=0x0b;
   if (cy2<y2) cy2++;
   else j&=0x07;
  } while (j);
 }
 else { // normal
  FILLER=' ';
  clear (x1,y1,x2,y2);
 }
 box (x1,y1,x2,y2,0x02);
 if (SHADOW) {
  j=y2+2;
  if (j<80) change_attr (x1+1, y2+1, x2, y2+2, 0x08); //Ver
  j=x2+1;
  if (j<25) change_attr (x2+1,y1+2,x2+1,y2+2, 0x08); //Hor
 } return;
}

KEY  Get_Key( void ) {
 KEY  sim;              //Символ, кторый будет содержать значение буфера
 MODE scan,ascii;
 int mod;
 if (Start != End) {     //Есть ли символ во внутреннем буфере
  sim = Buf[ End++ ];  //Есть, считать символ
  End %= MAX_BUF;    //Контроль выхода за пределы буфера
 }
 else  {
  asm MOV  AH,0x00;     //Считываем символ из буфера клавиатуры
  asm    INT  16H;
  sim=_AX; //bioskey (0);
  scan=(MODE)((sim&0xff00)>>8);  //_AH
  ascii=(MODE)(sim&0x00ff); //_AL
  if (ascii)  scan=0;
  sim=(scan<<8)+ascii;
 }
 return (sim);
}

void Set_Key( KEY kol, ... ) {
  KEY  *ptr;
  ptr = &kol;           //Указываем на первый символ в строке парам.
  for(; kol != 0; kol-- )   {
    Buf[ Start++ ] = *++ptr;  //Последовательно записываем все символы
    Start %= (MAX_BUF);     //во внутренний буфер
  }
  return;
}

void Clear_Buf( void ) {
  End = Start;
  asm{
      PUSH  ES            //Сохраняем доплнительный сегмент
      CLI                 //Запрещаем прерывания
      SUB   AX,AX
      MOV   ES,AX
      MOV   AX,ES:[41AH] //Устанавливаем указатели буфера на
      MOV   ES:[41CH],AX //одно и тоже значение
      STI                 //Разрешаем прерывания
      POP   ES            //Восстанавливаем дополнительный сегмент
     }
 return;
}

KEY Hit_Key (void) {
if (Start != End) return (1);
asm push es;
asm  mov ax,0;
asm  mov es,ax;
asm  mov ax,es:[41ah];
asm  mov cx,es:[41ch];
asm pop es;
 if (_AX==_CX) return (0);
 return (1);
}

void ms_state(void) {
 asm mov ax,3;
 asm int 033h;
 asm mov buttons,bl;
 asm shr cx,3;
 asm shr dx,3;
 asm mov MouseY,cl;
 asm mov MouseX,dl;
 return;
}

int ms_release(void) {
 asm mov ax,6;
 asm int 033h;
 asm shr cx,3;
 asm shr dx,3;
 asm mov MouseY,cl;
 asm mov MouseX,dl;
 return (_BX);
}

#define PROGRAM_MOUSE_CURSOR 0
#define HARD_MOUSE_CURSOR    1

void set_text_cursor(int type, int start, int end) {
 asm mov bx,type;
 asm mov cx,start;
 asm mov dx,end;
 asm mov ax,0ah;
 asm int 033h;
 return;
}

MODE Init_Mouse (void) {
 asm mov ax,0;
 asm int 033h;
 if(_AX==0) return NO;
 else {
  set_text_cursor(PROGRAM_MOUSE_CURSOR,0xffff,0x7700); //Обычный курсор
  asm mov ax,1;
  asm int 033h;
  return YES;
 }
}

MODE AnyClick (void) { // Любой щелчок мышью или нажатие клавиши
 Clear_Buf();
 while (!Hit_Key()) {
  ms_state();
  if (buttons) {
   buttons--;
   while (!ms_release());
   return 0x01;
  }
 } Clear_Buf();
 return 0x00;
}

MODE Mouse_In_Field (MODE X1,MODE Y1, MODE Xsize, MODE Ysize,
 MODE Xnum, MODE Ynum, MODE Xdop, MODE Ydop, MODE *I, MODE *J) {
 // X1,Y1 -  координаты левого верхнего угла
 // Xsize,Ysize - размеры выделяемого блока
 // Xnum,Ynum - количество блоков по горизонтали и вертикали
 // Xdop,Ydop - пустое место между блоками по горизонтали и вертикали
 // Color - цвет выделения
 // *I,*J - клеточные координаты выбранного блока
 // Возвращает 1, если присвоены X,Y, иначе они максимальны
 int X2,Y2,XX,YY,cx,cy;
 X2=X1+Xsize*Xnum+Xdop*(Xnum-1);
 Y2=Y1+Ysize*Ynum+Ydop*(Ynum-1);
 XX=Xsize+Xdop; YY=Ysize+Ydop;
 *I=Xnum; *J=Ynum;
 if (MouseX>=X1 && MouseX<=X2 &&
     MouseY>=Y1 && MouseY<=Y2) {
  cx=(MouseX-X1)/XX;
  if (MouseX<=X1+XX*cx+Xsize) *I=cx;
  cy=(MouseY-Y1)/YY;
  if (MouseY<=Y1+YY*cy+Ysize) *J=cy;
  if (*I<Xnum && *J<Ynum) {
   buttons=0x00; while (ms_release()<1); // до отпускания кнопки мыши
   return 0x01;
  }
 }
 return 0x00;
}

MODE Fun_Bar_Keys (char Num_Keys,KEY *Bar_Keys,MODE *Pos_Keys) {
 MODE i;
 if (MouseX==24) {
  for (i=0; i<Num_Keys; i++)
  if (MouseY>=Pos_Keys[i] && MouseY<Pos_Keys[i+1]) {
   Set_Key (1,Bar_Keys[i]);
   buttons=0x00; while (ms_release()<1); // до отпускания кнопки мыши
   return (0x01);
  }
 } return (0x00);
}

MODE Print (MODE x,MODE y,MODE Color1,MODE Color2,MODE *Str) {
 //Возвращает YES, если строка влезла в окно, иначе NO
 char c,cx,cy,Color=Color1,Outy=NO,Ret=NO,Attr;
 int i;
 cx=WIN_X1+x; cy=WIN_Y1+y;
 Attr=ATTR&0xf0; Attr|=Color;
 CURSOR_OFF();
 for (i=0; ;i++) {
  c=*(Str+i);
  switch (c) {
   case '$':
    if (*(Str+i+1)=='$') { i++; break; }
    Color=(Color==Color1?Color2:Color1);
    Attr&=0xf0; Attr|=Color;
   continue;
   case '\r': continue;
   case '\n':
    if (cx<WIN_X2) cx++;
    else goto RET;
    cy=WIN_Y1+y; Outy=NO;
   continue;
   case '\0':
    Ret=YES;
RET:
    CURSOR_ON(); return Ret;
   default: break;
  }
  if (!Outy) {
   V=vid_mem + (cx<<7) + (cx<<5) + (cy<<1);
   *V++ = c; // запись символа
   *V = Attr; // запись атрибута
  }
  if (cy<WIN_Y2) cy++;
  else Outy=YES;
 }
}

void Fun_Bar( char *str ) {
 MODE SaveByte1=ATTR;
 ATTR=0x70;
 FILLER=' ';
 clear (24,0,24,79); //Очищаем строку
 Print( 24, 0, LIGHTBLUE, LIGHTRED, str ); //Выводим строку
 ATTR=SaveByte1; return;
}

MODE Find_Key( MODE *str, MODE sim ) {
 MODE i = 0; //Смещение в строке
 //Цикл до совпадения или конца строки
 while( ( *str ) && ( *str++ != sim) )
  i++;
 if ( *str )    //Был ли конец строки
  i >>= 2;    //Нет, тогда MOD 4
 else i = NO_SIM;    //Да, возвращаем NO_SIM
 return( i );
}

char textmode (void) {
 // Устанавливает режим 3, 80*25, 16 цветов
 // Возвращает 0 в случае ошибки
 asm mov ax,0003h;
 asm int 10h;
 asm mov ah,00fh;
 asm int 10h;
 asm cmp al,003h;
 asm jnz ModeError;
 asm mov al,1;
 asm jmp Exit;
ModeError:
 asm mov al,0;
Exit:
 return (_AL);
}

void goto_xy(char xpos,char ypos) {
 asm mov ah,0x02 ;
 asm mov dl,ypos ; //Столбец
 asm mov dh,xpos ; //Строка
 asm mov bh,0x00 ;
 asm int 10h;
}

void show_cursor (MODE swit) {
 // 0 - выключить курсор
 // 1 - включить на сохраненной позиции (по умолчанию 0,0)
 static MODE SaveX,SaveY;
 if (swit) goto_xy (SaveX,SaveY);
 else {
  asm {  // Save cursor
   mov ah,3;
   mov bh,0;
   int 10h;
   mov SaveX,dh;
   mov SaveY,dl;
  }
  goto_xy (25,0);
 }
}

MODE Popup (MENU *Menu, MENUITEM *Item) {
 // Падающее меню; возвращает номер выбора (с 0) или NO_SIM по нажатию ESC
 char *Bar=
  " $ESC$ Отказ  $F1$ Помощь  $DOWN$ Вниз  $UP$ Вверх  $ENTER$ Выбор";
 KEY  Bar_Keys[]={ESC,F1,DOWN,UP,ENTER},sim;
 MODE Pos_Keys[]={0,11,22,33,43,54},
      Num_Keys=5,cursor=0x00,rev,i,j,x1,y1,x2,y2,kol;
 x1=Menu->x1; x2=Menu->x2;
 y1=Menu->y1; y2=Menu->y2;
 kol=Menu->kol - 0x01;
 if (Menu->Save) save (x1,y1,x2,y2);
 ATTR=set_attr (Menu->color1,Menu->bkcolor);
 rev=(15-Menu->bkcolor);

 FILLER=' ';
 clear (x1,y1,x2,y2);
 box (x1,y1,x2,y2,0x02);
 x1++; y1++;
 for (i=0; i<=kol; i++) {
  if ((Item+i)->zam) Print (x1+i, y1, Menu->color1, Menu->color2, (Item+i)->str);
  else Print (x1+i, y1, rev, rev, (Item+i)->str);
 }
 Fun_Bar (Bar);
 while (1) {
  change_attr (x1+cursor, y1, x1+cursor, y2-1, rev);
  if (Mouse) while (!Hit_Key()) {
   ms_state ();
   if (buttons==0x01) { //Left
    if (Fun_Bar_Keys (Num_Keys,Bar_Keys,Pos_Keys)) goto KEYBOARD;
    if (Mouse_In_Field (x1,y1, 1,y2-y1, kol+1,1, 0, 0, &i, &j)) {
     change_attr (x1+cursor, y1, x1+cursor, y2-1, Menu->bkcolor);
     cursor=i; sim=ENTER;
     change_attr (x1+cursor, y1, x1+cursor, y2-1, rev);
     goto SWITCHER;
    }
   }
  }
KEYBOARD:
  sim=Get_Key();
SWITCHER:
  change_attr (x1+cursor, y1, x1+cursor, y2-1, Menu->bkcolor);
  switch (sim) {
   case ESC: cursor=NO_SIM;  goto OUT;
   case F1:
    Help (Menu->Index+cursor);
    Fun_Bar (Bar);
   break;
   case ENTER:
    if ((Item+cursor)->zam) {
     (Item+cursor)->sf();
     Fun_Bar (Bar);
    }
   break;
   case UP:
    if (cursor>0x00) cursor--;
    else cursor=kol;
   break;
   case DOWN:
    if (cursor<kol) cursor++;
    else cursor=0x00;
   break;
   case PAGE_UP: cursor=0x00; break;
   case PAGE_DO: cursor=kol; break;
   default:
    i=Find_Key(Menu->keys,(char)sim);
    if (i!=NO_SIM) {
     cursor=i;
     Set_Key (1,ENTER);
    }
   break;
  }
 }
OUT:
 x1--; y1--;
 if (Menu->Save) restore (x1,y1,x2,y2);
 return (cursor);
}

void Close_System (void) {
 textmode();
 if (Mouse) {
  asm mov ah,0;
  asm int 33h;
  CURSOR_OFF();
 }
 asm mov ah,0x4c;
 asm mov al,0; // Код возврата=0
 asm int 21h;  //exit (0);
}
