/*
 * Solve Number Place Puzzle
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

typedef struct {
    int x, y;
} xy;

int isize; /* 盤面の縦の大きさ */
int jsize; /* 盤面の横の大きさ */
int num; /* 当てはめる数値は 1 〜 num */
int nob; /* 同一数が入れない制約領域の総数 */

xy **block; /* 制約の表 */

int verbose; /* 途中経過を表示する? */

/* 表示用のテーブル */
char printtable[] = "123456789ABCDEFG";

/* 盤面を表示 */

void printboard(unsigned int **board)
{
    int i, j, k, l;

    for (j=0; j<jsize; j++) {
        printf("-----+");
    }
    printf("\n");

    for (i=0; i<isize; i++) {
        for (l=0; l<(num/5)+1; l++) {
            for (j=0; j<jsize; j++) {
                for (k=l*5; k<(l+1)*5; k++) {
                    if (k >= num) {
                        printf(" ");
                        continue;
                    }
                    if ((board[i][j] & (1 << k)) == 0) printf(" ");
                    else putchar(printtable[k]);
                }
                printf("|");
            }
            printf("\n");
        }
        for (j=0; j<jsize; j++) {
            printf("-----+");
        }
        printf("\n");
    }
}

/* 桝に入り得る数が一つだけか調べる */

int isonly(unsigned int a)
{
    int i;

    for (i=0; i<num; i++) {
        if (a == (1 << i)) return 1;
    }
    return 0;
}

/* 桝(x, y)がbn番目の制約に含まれるか調べる */

int isinblock(int bn, int x, int y)
{
    int i;

    for (i=0; i<num; i++) {
        if (block[bn][i].x == x && block[bn][i].y == y) return 1;
    }
    return 0;
}

/* エラーが起きたら終了するmalloc */

void *malloc_check(size_t size)
{
    void *r;

    if ((r = malloc(size)) == NULL) {
        printf("Not Enough memory\n");
        exit(1);
    }
    return r;
}

/*
 * 盤面を調べ、決定可能な場所を決定する
 *  board: 盤面データ
 *  cache: 制約条件毎の決定済み数データ
 *  戻値: 1 -- 変更あり
 *        0 -- 変更なし
 *       -1 -- 解無し
 */

int check(unsigned int **board, unsigned int *cache)
{
    int bn, n, bn2;
    int i, j, ii, jj, k, l, ll;
    unsigned int a, x;
    int count;
    int flag;
    static xy *tmp = NULL;

    if (tmp == NULL) tmp = (xy *)malloc_check(sizeof(xy) * num);

    flag = 0;

    for (bn = 0; bn < nob; bn++) {
        for (n = 0; n < num; n++)  {
            a = (1 << n);
            if ((cache[bn] & a) != 0) continue;
            count = 0;
            for (l=0; l<num; l++) {
                i = block[bn][l].x;
                j = block[bn][l].y;
                /* 桝に入る無ければ解無し */
                if (board[i][j] == 0) return -1;
                /* 桝に入る数が唯一なら残りの桝の
                   その数を消す */
                if (board[i][j] == a) {
                    for (ll=0; ll<num; ll++) {
                        ii = block[bn][ll].x;
                        jj = block[bn][ll].y;
                        if (i==ii && j==jj) continue;
                        x = board[ii][jj] & (~a);
                        if (x != board[ii][jj]) flag = 1;
                        board[ii][jj] = x;
                    }
                    cache[bn] |= a;
                    goto next_n;
                }
                /* 制約bn内に数nがいくつ残っているかをcount。
                   tmpに位置を覚えておく */
                if ((board[i][j] & a) != 0) {
                    tmp[count].x = i;
                    tmp[count].y = j;
                    count++;
                }
            }
            switch (count) {
            /* 制約bn内に数nが無ければ解無し */
            case 0:
                return -1;
            /* 制約bn内に数nが1つなら決定 */
            case 1:
                cache[bn] |= a;
                if (board[tmp[0].x][tmp[0].y] != a) {
                    board[tmp[0].x][tmp[0].y] = a;
                    flag = 1;
                }
                break;
            /* 制約bn内に数nが2つ or 3つならその2or3個の
               桝を全て含む別の制約bn2について、
               残りの桝の数nを消す */
            case 2:
            case 3:
                for (bn2 = 0; bn2 < nob; bn2++) {
                    if (bn2 == bn) continue;
                    for (k=0; k<count; k++) {
                        if (!isinblock(bn2, tmp[k].x, tmp[k].y)) goto next_bn;
                    }
                    for (l=0; l<num; l++) {
                        i = block[bn2][l].x;
                        j = block[bn2][l].y;
                        for (k=0; k<count; k++) {
                            if (i==tmp[k].x && j==tmp[k].y) goto next_l;
                        }
                        x = board[i][j] & (~a);
                        if (x != board[i][j]) flag = 1;
                        board[i][j] = x;
                        next_l: ;
                    }
                    next_bn: ;
                }
                break;
            default: ;
            }
            next_n: ;
        }
    }
    return flag;
}

void recur(unsigned int **board, unsigned int *cache)
{
    int r;
    int i, j, k;
    unsigned int a, a2;
    unsigned int **board2;
    unsigned int *cache2;

    board2 = (unsigned int **)malloc_check(sizeof(int *) * isize);
    for (i=0; i<isize; i++) {
        board2[i] = (unsigned int *)malloc_check(sizeof(int) * jsize);
    }

    cache2 = (unsigned int *)malloc_check(sizeof(int) * nob);

    for (i=0; i<nob; i++) {
        cache2[i] = cache[i];
    }
    for (i=0; i<isize; i++) {
        for (j=0; j<jsize; j++) board2[i][j] = board[i][j];
    }

    /* 更新されなくなるまでcheckを呼び続ける */
    while (1) {
        if (verbose == 1) printboard(board2);
        r = check(board2, cache2);
        if (r == 1) continue;
        if (r == -1) goto end;
        if (r == 0) break;
    }

    /* 未定な桝を探す */
    for (i=0; i<isize; i++) {
        for (j=0; j<jsize; j++) {
            if (!isonly(board2[i][j])) goto next;
        }
    }

    /* 解発見 */
    printf("solution found:\n");
    printboard(board2);
    goto end;

next:

    /* 未定桝を仮決定して再帰 */
    a = board2[i][j];
    for (k=0; k<num; k++) {
        a2 = (1 << k);
        if ((a & a2) != 0) {
            if (verbose == 1) {
                printf("We assume (%d,%d) is %c\n",i, j, printtable[k]);
            }
            board2[i][j] = a2;
            recur(board2, cache2);
        }
    }

end:

    for (i=0; i<isize; i++) free(board2[i]);
    free(board2);
    free(cache2);
}

unsigned int char2bit(char c)
{
    if (c == '.') return (1 << num) - 1;
    if (c > '9') return 1 << (c - 'A' +10 -1);
    return 1 << (c - '0' -1);
}

/* 先頭が#なら改行まで読み飛ばす */

void skip_comment(FILE *fp)
{
    int c;
    while (1) {
        c = getc(fp);
        if (c != '#') break;
        while (getc(fp) != '\n') ;
    }
    ungetc(c, fp);
    return;
}

/* 数値を読み込む */

int get_number(FILE *fp)
{
    int c, r;

    skip_comment(fp);
    r = 0;
    while (1) {
        c = getc(fp);
        if (!isdigit(c)) break;
        r = r * 10 + c - '0';
    }
    ungetc(c, fp);
    return r;
}

int get_tmpstring(char **s, FILE *fp)
{
    int i, j;

    for (i=0; i<isize; i++) {
        skip_comment(fp);
        for (j=0; j<jsize; j++) {
            s[i][j] = getc(fp);
        }
        if (getc(fp) != '\n') return -1;
    }
    return 0;
}


int main(int argc, char **argv)
{
    int i, j, k;
    int count;
    unsigned int **board;
    unsigned int *cache;
    char **tmpstring;
    FILE *fp;

    fp = NULL;
    verbose = 0;

    for (i=1; i<argc; i++) {
        if (argv[i][0] != '-') {
            if (fp != NULL) {
                printf("usage %s [-v] datafile\n", argv[0]);
                exit(1);
            }
            fp = fopen(argv[i], "r");
            if (fp == NULL) {
                printf("can't open %s\n", argv[1]);
                exit(1);
            }
        } else {
            switch(argv[i][1]) {
            case 'v':
                verbose = 1;
                break;
            default:
                printf("usage %s [-v] datafile\n", argv[0]);
                exit(1);
            }
        }
    }

    isize = get_number(fp);
    if (getc(fp) != '\n') { printf("data error\n"); exit(1); }
    jsize = get_number(fp);
    if (getc(fp) != '\n') { printf("data error\n"); exit(1); }
    num = get_number(fp);
    if (getc(fp) != '\n') { printf("data error\n"); exit(1); }
    nob = get_number(fp);
    if (getc(fp) != '\n') { printf("data error\n"); exit(1); }
    

    tmpstring = (char **)malloc_check(sizeof(char *) * isize);
    for (i=0; i<isize; i++) {
        tmpstring[i] = (char *)malloc_check(sizeof(char) * jsize);
    }

    board = (unsigned int **)malloc_check(sizeof(int *) * isize);
    for (i=0; i<isize; i++) {
        board[i] = (unsigned int *)malloc_check(sizeof(int) * jsize);
    }

    cache = (unsigned int *)malloc_check(sizeof(int) * nob);

    block = (xy **)malloc_check(sizeof(xy *) * nob);
    for (i=0; i<nob; i++) {
        block[i] = (xy *)malloc_check(sizeof(xy) * num);
    }


    if (get_tmpstring(tmpstring, fp) == -1) {
        printf("data error\n"); exit(1);
    }
    for (i=0; i<isize; i++) {
        for (j=0; j<jsize; j++) {
            board[i][j] = char2bit(tmpstring[i][j]);
        }
    }

    for (i=0; i<nob; i++) cache[i] = 0;

    for (k=0; k<nob; k++) {
        if (get_tmpstring(tmpstring, fp) == -1) {
            printf("data error\n"); exit(1);
        }
        count = 0;
        for (i=0; i<isize; i++) {
            for (j=0; j<jsize; j++) {
                if (tmpstring[i][j] == '+') {
                    if (count >= num) {
                        printf("data error\n"); exit(1);
                    }
                    block[k][count].x = i;
                    block[k][count].y = j;
                    count++;
                }
            }
        }
        if (count != num) printf("insufficient constraint\n");
    }

    recur(board, cache);

    for (i=0; i<isize; i++) free(tmpstring[i]);
    free(tmpstring);
    for (i=0; i<isize; i++) free(board[i]);
    free(board);
    free(cache);
    for (i=0; i<nob; i++) free(block[i]);
    free(block);

    fclose(fp);

    return 0;
}
