int fft(void *src_data, int num)
{
    return 0;
}

#include "bb_freq_util.h"

#include "rscode.h"

static float frequencies[32];
static double theta = 0;

struct _bb_item_group {

	int item;
	int count;
};

/*
 static struct _freq_range {

 unsigned int start;
 unsigned int end;

 } freq_range[32];
 */

static int freq_init_flag = 0;
static int freq_init_is_high = 0;

void freq_init() {

	if (freq_init_flag) {

		return;
	}

	int i, len;

    if (freq_init_is_high) {

        for (i=0, len = strlen(BB_CHARACTERS); i<len; ++i) {

            unsigned int freq = (unsigned int)(BB_BASEFREQUENCY_H + (i * 64));
            frequencies[i] = freq;
        }

    } else {

        for (i=0, len = strlen(BB_CHARACTERS); i<len; ++i) {

            unsigned int freq = (unsigned int)floor(BB_BASEFREQUENCY * pow(BB_SEMITONE, i));
            frequencies[i] = freq;

        }
    }

    freq_init_flag = 1;
}

void switch_freq(int is_high) {

    if (is_high == 0 || is_high == 1) {

        freq_init_flag = 0;
        freq_init_is_high = is_high;

        freq_init();
    }
}

int freq_to_num(unsigned int f, int *n) {

    /*
     frequencies[0] = (unsigned int)floor(BB_BASEFREQUENCY * pow(BB_SEMITONE, 0));
     frequencies[31] = (unsigned int)floor(BB_BASEFREQUENCY * pow(BB_SEMITONE, 31));

     if (n != NULL &&
     f >= frequencies[0]-BB_THRESHOLD*pow(BB_SEMITONE, 0) &&
     f <= frequencies[31]+BB_THRESHOLD*pow(BB_SEMITONE, 31)) {

     unsigned int i;

     for (i=0; i<32; i++) {

     unsigned int freq = (unsigned int)floor(BB_BASEFREQUENCY * pow(BB_SEMITONE, i));
     frequencies[i] = freq;

     if (abs(frequencies[i] - f) <= BB_THRESHOLD*pow(BB_SEMITONE, i)) {
     *n = i;
     return 0;
     }
     }
     }
     */

    freq_init();

    if (n != NULL &&
        f >= frequencies[0]-BB_THRESHOLD &&
        f <= frequencies[31]+BB_THRESHOLD) {

        unsigned int i;

        for (i=0; i<32; i++) {

            if (abs(frequencies[i] - f) <= BB_THRESHOLD) {

                *n = i;
                return 0;
            }
        }
    }

    /*
     if (n!=NULL && f>freq_range[0].start && f<freq_range[31].end) {

     unsigned int i;

     for (i=0; i<32; i++) {

     if (f>freq_range[i].start && f<freq_range[i].end) {

     *n = i;
     return 0;
     }
     }
     }
     */

	return -1;
}

int num_to_char(int n, char *c) {

	if (c != NULL && n>=0 && n<32) {

		*c = BB_CHARACTERS[n];

		return 0;
	}

	return -1;
}

int char_to_num(char c, unsigned int *n) {

	if (n == NULL) return -1;

	*n = 0;

	if (c>=48 && c<=57) {

		*n = c - 48;

		return 0;

	} else if (c>=97 && c<=118) {

		*n = c - 87;

		return 0;
	}

	return -1;
}

int num_to_freq(int n, unsigned int *f) {

    freq_init();

	if (f != NULL && n>=0 && n<32) {

		*f =  (unsigned int)floor(frequencies[n]);

		return 0;
	}

	return -1;
}

int char_to_freq(char c, unsigned int *f) {

	unsigned int n;

	if (f != NULL && char_to_num(c, &n) == 0) {

		unsigned int ff;

		if (num_to_freq(n, &ff) == 0) {

			*f = ff;
			return 0;
		}
	}

	return -1;
}

int vote(int *src, int src_len, int *result) {

	if (src==NULL || src_len==0 || result==NULL) {

		return -1;
	}

	int i;
	int map[32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	int max_value = 0;
	int max_key   = 0;
	int temp      = 0;

	for (i=0; i<src_len; i++) {

		if (src[i] >= 0 && src[i] < 32) {

            temp = ++map[src[i]];

            if (temp > max_value) {

                max_value = temp;
                max_key   = src[i];
            }

		} else if (src[i]==-1) {

        } else {
			return -1;
		}
	}

	*result = max_key;

	return max_value;
}

int multi_vote(int *src, int src_len, int *result, int res_len, int vote_res) {

	if (src==NULL || result==NULL || src_len==0 || res_len==0 || src_len<=res_len || vote_res==0) {

		return -1;
	}

	int step_len = src_len / res_len;

    if (step_len<vote_res) {

        return -1;
    }

	int i;
	int j = 0;

	for (i=0; i<src_len; i+=step_len) {

        int vote_count = vote(&src[i], step_len, &result[j++]);

        if (vote_count == -1) {

            return -1;
        }
        if (vote_res>0 && vote_count<vote_res) {

            return 1;
        }
	}

	return 0;
}

int multi_vote_accurate(int *src, int src_len, int *result, int res_len, int vote_res) {

	if (src==NULL || result==NULL || src_len==0 || res_len==0 || src_len<=res_len || vote_res==0) {

		return -1;
	}

	int step_len = src_len / res_len;

    if (step_len<vote_res) {

        return -1;
    }

	int i;
	int j = 0;

	for (i=0; i<src_len; i+=step_len) {

        int vote_count = vote(&src[i], step_len+1, &result[j++]);

        if (vote_count == -1) {

            return -1;
        }
        if (vote_res>0 && vote_count<vote_res) {

            return 1;
        }
	}

	return 0;
}

int statistics(int *src, int src_len, int *result, int res_len) {

    if (src==NULL || result==NULL || src_len==0 || res_len==0) {

        return -1;
    }

    // Littlebox-XXOO

    bb_item_group a[32*3]; // include -1
    bb_item_group b[32*3]; // without -1
    bb_item_group c[32*3];

    int i;
    int step = 0;

	int stat = set_group(src, src_len, a, 32) == 0 &&
    ++step &&
    process_group(a, 32) == 0 &&
    ++step &&
    get_group_data(a, 32, src, src_len) == 0 &&
    ++step &&
    set_group(src, src_len, b, 32) == 0 &&
    ++step &&
    post_process(b, 32, c, 32) == 0 &&
    ++step;

    /*
     printf("a:\n");

     for (i=0; i<32; i++) {

     printf("%d->%d\n", a[i].item, a[i].count);
     }

     printf("b:\n");

     for (i=0; i<32; i++) {

     printf("%d->%d\n", b[i].item, b[i].count);
     }

     printf("c:\n");

     for (i=0; i<32; i++) {

     printf("%d->%d\n", b[i].item, b[i].count);
     }
     */

    if (stat) {

        result[0] = c[0].item;
        result[1] = c[1].item;

        if (c[res_len].item != -2 && c[2].item == BB_HEADER_1 && c[res_len].item != c[res_len-1].item) {

            for (i=2; i<res_len; i++) {

                result[i] = c[i+1].item;
            }

        } else {

            for (i=2; i<res_len; i++) {

                if (c[i].item == -2) {

                    result[i] = 0;

                } else {

                    result[i] = c[i].item;
                }
            }
        }

        printf("77777777777777777777777\n");
    }

    printf("step: %d\n", step);
    printf("888888888888888888888888\n");

    return stat ? -1 : 0;
}

int compose_statistics(int *src_vote, int *src_statics, int src_len, int *result, int res_len) {

	if (src_vote==NULL || src_statics==NULL || result==NULL || src_len==0 || res_len==0) {
        return -1;
	}

	int i;

	if (src_vote[0]==BB_HEADER_1) {

		if (src_statics[0]!=BB_HEADER_1) {

			for (i=0; i<src_len-1; i++) {
				src_vote[i] = src_vote[i+1];
			}
			src_vote[src_len-1] = 0;
		}
	}

	for (i=0; i<src_len; i++) {

		if (i==0) {

			result[0] = src_statics[0];

		} else {

			if (src_vote[i] != src_statics[i]) {

				if (src_vote[i] != result[i-1] && src_statics[i] != result[i-1]) {
                    result[i] = src_statics[i];
                } else if (src_vote[i] != result[i-1] && src_statics[i] == result[i-1]) {
					result[i] = src_vote[i];
				} else {
					result[i] = src_statics[i];
				}
			} else {
				result[i] = src_vote[i];
			}
		}
	}

	return 0;
}

int set_group(int *src, int src_len, bb_item_group *result, int res_len) {

	if (src==NULL || result==NULL || src_len <= 0 || res_len <= 0) {

		return -1;
	}

	int index = 0;
	int i;

	for (i=0; i<res_len; i++) {

		result[i].item = -2;
		result[i].count = 0;
	}

    src[0] = src[1] = src[2] = BB_HEADER_0;
    src[3] = src[4] = src[5] = BB_HEADER_1;

	result[0].item = src[0];
	result[0].count++;

	for (i=1; i<src_len; i++) {

		if (src[i] == src[i-1] && result[index].count < 4) {

			result[index].count++;

		} else {

			index++;
			result[index].item = src[i];
			result[index].count++;
		}
	}

	return 0;
}

int process_group(bb_item_group *src, int src_len) {

	if (src==NULL || src_len <= 0) {
		return -1;
	}

	int i;
	int twice = 1;

	while (twice<3) {

		for (i=0; i<src_len; i++) {

			if (src[i].count != 0 && src[i].item == -1 && src[i].count < 3) {

				if (i == 0) {

					if (src[i+1].count<4) {

						src[i+1].count++;
						src[i].count--;
					}

				} else if (src[i+1].item == -2) {

					if (src[i-1].count<4) {

						src[i+1].count++;
						src[i].count--;
					}

				} else {

					if (src[i-1].count == 4 && src[i+1].count == 4) {

					} else if (src[i-1].count > src[i+1].count) {

						src[i+1].count++;
						src[i].count--;

					} else {

						src[i-1].count++;
						src[i].count--;
					}
				}
			}
		}

		twice++;
	}

	return 0;
}

int get_group_data(bb_item_group *src, int src_len, int *result, int res_len) {

	if (src==NULL || result==NULL || res_len <= 0 || src_len <= 0) {

		return -1;
	}

	int i, j;
	int index = 0;

	for (i=0; i<src_len; i++) {

		for (j=0; j<src[i].count; j++) {

            if (index > res_len) {

                break;
            }

			result[index] = src[i].item;
			index++;
		}
	}

	return 0;
}

int post_process(bb_item_group *src, int src_len, bb_item_group *result, int res_len) {

	if (src==NULL || result==NULL || src_len <= 0 || res_len <= 0) {
		return -1;
	}

	int i;
	int index = 1;
	int x;

	for (i=0; i<res_len; i++) {
		result[i].item = -2;
		result[i].count = 0;
	}

	result[0].item = src[0].item;
	result[0].count = src[0].count;

	for (i=1; i<src_len-1; i++) {

		if (src[i].item == -1) {

            if (src[i].count == 4 && src[i+1].item == -1) {

				result[index].item = src[i].item = 0;
				index++;
				continue;
			}

			if (src[i-1].count + src[i].count + src[i+1].count >= 9) {

				result[index].item = 0;
				result[index].count = 4;
				result[index-1].count = src[i-1].count = 4;
				src[i+1].count = 4;
				index++;

			} else {

				x = src[i+1].count + src[i].count;
				src[i+1].count = x > 4 ? 4 : x;
			}
		} else {

			result[index].item = src[i].item;
			result[index].count = src[i].count;
			index++;
		}
	}

	return 0;
}

// 一个频率对应的一组PCM的buffer
int encode_sound(unsigned int freq, float buffer[], size_t buffer_length) {

    const double amplitude = 0.25;
	double theta_increment = 2.0 * PI * freq / SAMPLE_RATE;
	int frame;

	for (frame = 0; frame < buffer_length; frame++) {

		buffer[frame] = sin(theta) * amplitude;
		theta += theta_increment;

        if (theta > 2.0 * PI) {

			theta -= 2.0 * PI;
		}
	}

    return 1;
}

int create_sending_code(unsigned char *src, unsigned char *result, int res_len) {

    if (src==NULL || result==NULL || res_len <= 2) {
        return -1;
    }

    int i;

    unsigned char data[RS_TOTAL_LEN];

    for (i=0; i<RS_TOTAL_LEN; i++) {

        if (i<RS_DATA_LEN) {
            char_to_num(src[i], (unsigned int *)(data+i));
        }else {
            data[i] = 0;
        }
    }

    unsigned char *code = data + RS_DATA_LEN;

    RS *rs = init_rs(RS_SYMSIZE, RS_GFPOLY, RS_FCR, RS_PRIM, RS_NROOTS, RS_PAD);
    encode_rs_char(rs, data, code);

    result[0] = BB_HEADER_0;
    result[1] = BB_HEADER_1;

    for (i=2; i<res_len; i++) {
        result[i] = data[i-2];
    }

    printf("code sending :  ");
    for (i=0; i<res_len; i++) {
        printf("%u-", result[i]);
    }
    printf("\n");

    return 0;
}

// 解码音频数据，返回所对应字符
int decode_sound(short *src, int fft_number)
{
    if (!src) {
        return 0;
    }

    // 计算fft

    int length_buff_per_turn = fft_number / 2;
    int res_temp[5] = {-1,-1,-1,-1,-1};
    int freq = 0;

    freq = fft((src+fft_number/8 + fft_number *3 / 4 / 8 * 0), length_buff_per_turn);
    freq_to_num(freq, res_temp + 0);

    freq = fft((src+fft_number/8 + fft_number *3 / 4 / 8 * 1), length_buff_per_turn);
    freq_to_num(freq, res_temp + 1);

    freq = fft((src+fft_number/8 + fft_number *3 / 4 / 8 * 2), length_buff_per_turn);
    freq_to_num(freq, res_temp + 2);

    freq = fft((src+fft_number/8 + fft_number *3 / 4 / 8 * 3), length_buff_per_turn);
    freq_to_num(freq, res_temp + 3);

    freq = fft((src+fft_number/8 + fft_number *3 / 4 / 8 * 4), length_buff_per_turn);
    freq_to_num(freq, res_temp + 4);

    int sound_freq = 0;

    if (vote(res_temp, 5, &sound_freq) <= 0) {

        return 0;
    }

    /*
     if (sound_freq > 0) {

     int a = -1;
     freq_to_num(sound_freq, &a);
     printf("----%d---  %d\n", sound_freq, a);
     }
     */

    return sound_freq;
}

// fft计算

int statistics_2(int *src, int src_len, int *result, int res_len) {

    if (src==NULL || result==NULL || src_len==0 || res_len==0) {

        return -1;
    }

    // Littlebox-XXOO

    bb_item_group a[32*3];
    bb_item_group c[32*3];

    int i;
    int step = 0;

	int stat = set_group(src, src_len, a, 32) == 0 &&
    ++step &&
    process_group_2(a, 32) == 0 &&
    ++step &&
    post_process_2(a, 32, c, 32) == 0 &&
    ++step;

    if (stat) {

        result[0] = c[0].item;
        result[1] = c[1].item;

        if (c[res_len].item != -2 && c[2].item == BB_HEADER_1 && c[res_len].item != c[res_len-1].item) {

            for (i=2; i<res_len; i++) {

                result[i] = c[i+1].item;
            }

        } else {

            for (i=2; i<res_len; i++) {

                if (c[i].item == -2) {

                    result[i] = 0;

                } else {

                    result[i] = c[i].item;
                }
            }
        }
    }

    printf("step: %d\n", step);

    return stat ? -1 : 0;
}

int process_group_2(bb_item_group *src, int src_len) {

	if (src==NULL || src_len <= 0) {
		return -1;
	}

	int i;

    for (i=1; i<src_len-2; i++) {

        if (src[i].count != 0 && src[i].count == 1 && src[i+1].count == 1 && src[i+2].count == 1) {

            if (src[i-1].count > src[i+3].count) {

                src[i].count = 0;
                src[i+1].count = 0;
                src[i+2].count = 2;

            } else {

                src[i].count = 2;
                src[i+1].count = 0;
                src[i+2].count = 0;
            }

        } else if (src[i].count != 0 && src[i].count == 1 && src[i+1].count == 1) {

            if (src[i-1].count + src[i+1].count + src[i].count <= 8) {

                src[i].count = 0;
                src[i+1].count = 0;

            } else {
                src[i].count = 0;
                src[i+1].count = 2;
            }

        } else if (src[i].count != 0 && src[i].count == 1) {

            if (src[i-1].count + src[i+1].count + src[i].count <= 8) {
                src[i].count = 0;
            } else {
                src[i].count = 2;
            }
        }

    }

	return 0;
}

int post_process_2(bb_item_group *src, int src_len, bb_item_group *result, int res_len) {

	if (src==NULL || result==NULL || src_len <= 0 || res_len <= 0) {
		return -1;
	}

	int i;
	int index = 1;

	for (i=0; i<res_len; i++) {
		result[i].item = -2;
		result[i].count = 0;
	}

	result[0].item = src[0].item;
	result[0].count = src[0].count;

	for (i=1; i<src_len-1; i++) {

		if (src[i].count == 0) {

            continue;

		} else {

			result[index].item = src[i].item;
			result[index].count = src[i].count;
			index++;
		}
	}

	return 0;
}

void _medianfilter(const element* signal, element* result, int N)
{
    //   Move window through all elements of the signal
    for (int i = 1; i < N - 1; ++i)
    {
        //   Pick up window elements
        element window[3];
        for (int j = 0; j < 3; ++j)
            window[j] = signal[i - 1 + j];
        //   Order elements (only half of them)
        for (int j = 0; j < 2; ++j)
        {
            //   Find position of minimum element
            int min = j;
            for (int k = j + 1; k < 3; ++k)
                if (window[k] < window[min])
                    min = k;
            //   Put found minimum element in its place
            const element temp = window[j];
            window[j] = window[min];
            window[min] = temp;
        }
        //   Get result - the middle element
        result[i] = window[1];
    }

	result[0] = signal[0];
}

int array_search(int num, int a[], int array_length) {

	if (array_length <= 0 || a == NULL) {
		return -2; // 数据异常
	}

	for (int i=0; i<array_length; i++) {

		if (num == a[i]) {
			return i;
		}
	}

	return -1; // 没找到
}

int isset_struct(struct_tmp tmp_x) {

	if (tmp_x.num != -1) {
		return 1;
	}else {
		return 0;
	}
}

int isset_num(int num) {

	if (num != -1) {
		return 1;
	}else {
		return 0;
	}
}

void unset(struct_tmp *const tmp_x) {

	tmp_x->num = -1;
	tmp_x->m = -1;
	tmp_x->r = -1;
	tmp_x->r_ = -1;
	tmp_x->l = -1;
	tmp_x->l_ = -1;
}

int partions(float l[],int low,int high)
{
	int prvotkey=l[low];
	l[0]=l[low];
	while (low<high)
	{
        while (low<high&&l[high]>=prvotkey)
            --high;
        l[low]=l[high];
        while (low<high&&l[low]<=prvotkey)
            ++low;
        l[high]=l[low];
	}

	l[low]=l[0];
	return low;
}

void k_qsort(float l[],int low,int high)
{
	int prvotloc;
	if(low<high)
	{
        prvotloc=partions(l,low,high);    //将第一次排序的结果作为枢轴
        k_qsort(l,low,prvotloc-1); //递归调用排序 由low 到prvotloc-1
        k_qsort(l,prvotloc+1,high); //递归调用排序 由 prvotloc+1到 high

	}
}

void quicksort(float l[],int n)
{
	k_qsort(l,1,n); //第一个作为枢轴 ，从第一个排到第n个
}

void generate_data(queue *que, int que_length, int *res, int *rrr, int res_length, float minValue, float maxValue) {

    float data[20][32] = {0};

    int type = 0;

    if (type == 1) {

        int counter = 0;
        int while_counter = 0;

        float step;

        if (minValue >= 0.5) {
            step = 1 - minValue;
        }else {
            step = minValue;
        }

        float thresh_hold = minValue;

        while ((counter > 70 || counter < 40) && while_counter < 10) {

            if (counter > 70) {

                step = step / 2;
                thresh_hold = thresh_hold + step;

            }else if (counter < 40 && thresh_hold < 0.1){

                step = step / 2;
                thresh_hold = thresh_hold - step;
            }

            counter = 0;
            while_counter++;

            for (int i = 0; i<32; i++) {

                for (int k = 0; k<20; k++) {

                    queue *q = &que[i];
                    float currentValue = queue_item_at_index(q, k);

                    if (currentValue <= thresh_hold) {
                        currentValue = 0.0;
                    }else {
                        counter++;
                    }

                    data[k][i] = currentValue;

                }
            }
        }
    }else if (type == 0) {

        for (int i = 0; i<32; i++) {

            for (int k = 0; k<20; k++) {

                queue *q = &que[i];
                float currentValue = queue_item_at_index(q, k);

                if (currentValue < 0 || currentValue > 1) {
                    currentValue = 0.0;
                }

                data[k][i] = currentValue;

            }
        }
    }else if (type == 2) {

        int counter = 0;

        float step = 0.01;
        float thresh_hold = minValue;

        while ((counter > 70 || counter < 40) && thresh_hold < 1) {

            if (counter > 70) {
                thresh_hold += step;
            }else {
                thresh_hold -= step;
            }

            counter = 0;

            for (int i = 0; i<32; i++) {

                for (int k = 0; k<20; k++) {

                    queue *q = &que[i];
                    float currentValue = queue_item_at_index(q, k);

                    if (currentValue < thresh_hold) {
                        currentValue = 0.0;
                    }else {
                        counter++;
                    }

                    data[k][i] = currentValue;
                }
            }
        }
    }

    float tempValue[80];
    for (int i=0; i<80; i++) {
        tempValue[i] = -1;
    }

    int sortData[20][4];
	float sortValue[20][4];

	for (int i=0; i<4; i++) {
		for (int j=0; j<20; j++) {
			sortData[j][i] = -1;
			sortValue[j][i] = -1;
		}
	}

	for (int i=0; i<20; i++) {
		for (int k=0; k<4; k++) {

			int tmp = -1;
			float tmpData = 0;
			int aa = 0;

			for (int j=0; j<32; j++) {

				if (data[i][j] > tmpData && data[i][j] != 0) {
					tmp = j;
					tmpData = data[i][j];
					aa = 1;
				}
			}

			if	(aa == 1) {

				sortValue[i][k] = data[i][tmp];
				data[i][tmp] = 0;
				sortData[i][k] = tmp;
				aa = 0;
			}
		}
	}

    // 加阈值
    /*
     for (int i=0; i<20; i++) {
     for (int j=0; j<4; j++) {

     tempValue[j * 20 + i] = sortValue[i][j];
     }
     }

     quicksort(tempValue, 80);

     float thresh_hold = fmin(tempValue[39], minValue);

     for (int i=0; i<20; i++) {
     for (int j=0; j<4; j++) {

     if (sortValue[i][j] < thresh_hold) {
     sortValue[i][j] = -1;
     sortData[i][j] = -1;
     }

     }
     }
     */

    if (minValue < 0.3) {

    }else if (minValue > 0.6) {
        for (int i=0; i<20; i++) {
            for (int j=2; j<4; j++) {

                sortValue[i][j] = -1;
                sortData[i][j] = -1;

            }
        }
    }else {
        for (int i=0; i<20; i++) {
            for (int j=3; j<4; j++) {

                sortValue[i][j] = -1;
                sortData[i][j] = -1;

            }
        }
    }

    for (int i=0; i<res_length; i++) {
        res[i] = -1;
    }

	for (int i=0; i<20; i++) {

		struct_tmp tmp_1; tmp_1.num = -1; tmp_1.m = -1; tmp_1.r = -1; tmp_1.r_ = -1; tmp_1.l = -1; tmp_1.l_ = -1;
		struct_tmp tmp_2; tmp_2.num = -1; tmp_2.m = -1; tmp_2.r = -1; tmp_2.r_ = -1; tmp_2.l = -1; tmp_2.l_ = -1;
		struct_tmp tmp_3; tmp_3.num = -1; tmp_3.m = -1; tmp_3.r = -1; tmp_3.r_ = -1; tmp_3.l = -1; tmp_3.l_ = -1;

		for (int j=0; j<4; j++) {

			int key = -1;
			int key_ = -1;

			if (sortData[i][j] != -1 && i<19 && (key = array_search(sortData[i][j], sortData[i+1], 4)) >= 0) {

				if (i < 18 && (key_ = array_search(sortData[i][j], sortData[i+2], 4)) >= 0) {

					if (!isset_struct(tmp_3)) {

						if (i > 0 && res[i-1] == sortData[i][j]) {} else {

							tmp_3.num = sortData[i][j];
							tmp_3.m = j;
							tmp_3.r = key;
							tmp_3.r_ = key_;
						}

					}else {

						int count_1 = tmp_3.m + tmp_3.r + tmp_3.r_;
						int count_2 = j + key + key_;

						if (count_2 < count_1) {

							if (i > 0 && res[i-1] == sortData[i][j]) {} else {

								tmp_3.num = sortData[i][j];
								tmp_3.m = j;
								tmp_3.r = key;
								tmp_3.r_ = key_;
							}
						}
					}
				}else {

					if (!isset_struct(tmp_2)) {

						if (i > 0 && res[i-1] == sortData[i][j]) {} else {

							tmp_2.num = sortData[i][j];
							tmp_2.m = j;
							tmp_2.r = key;
						}

					}else {

						int count_1 = tmp_2.m + tmp_2.r;
						int count_2 = j + key;

						if (count_2 < count_1) {

							if (i > 0 && res[i-1] == sortData[i][j]) {} else {

								tmp_2.num = sortData[i][j];
								tmp_2.m = j;
								tmp_2.r = key;
							}
						}
					}
				}

			}else if (sortData[i][j] != -1) {

				if (i == 0 || (i > 0 && res[i-1] != sortData[i][j])) {

					if (!isset_struct(tmp_1)) {

						tmp_1.num = sortData[i][j];
						tmp_1.m = j;
					}
				}
			}
		}

		if (isset_struct(tmp_1) && isset_struct(tmp_2)) {

			if ((tmp_1.m < tmp_2.m) && (tmp_1.m < tmp_2.r)) {

				float quotient_m = sortValue[i][tmp_1.m] / sortValue[i][tmp_2.m];
				float quotient_r = sortValue[i][tmp_1.m] / sortValue[i+1][tmp_2.r];

				if (quotient_m > 1.6 && quotient_r > 1.6) {

					unset(&tmp_2);
				}
			}
		}

		if (isset_struct(tmp_2) && !isset_num(res[i])) {

			res[i] = tmp_2.num;
		}

		if (isset_struct(tmp_3) && !isset_num(res[i])) {

			res[i] = tmp_3.num;
		}

		if (isset_struct(tmp_1) && !isset_num(res[i])) {

			res[i] = tmp_1.num;
		}

		if (!isset_num(res[i])) {

			res[i] = sortData[i][0];

			if (res[i] == -1) {
				res[i] = 0;
			}
		}

	}

    for (int i=0; i<res_length; i++) {
        rrr[i] = -1;
    }

    int sortData2[20][4];
    float sortValue2[20][4];

    for (int i=0; i<20; i++) {
        for (int j=0; j<4; j++) {

            sortValue2[i][j] = sortValue[19-i][j];
            sortData2[i][j] = sortData[19-i][j];

        }
    }

    for (int i=19; i>-1; i--) {

		struct_tmp tmp_1; tmp_1.num = -1; tmp_1.m = -1; tmp_1.r = -1; tmp_1.r_ = -1; tmp_1.l = -1; tmp_1.l_ = -1;
		struct_tmp tmp_2; tmp_2.num = -1; tmp_2.m = -1; tmp_2.r = -1; tmp_2.r_ = -1; tmp_2.l = -1; tmp_2.l_ = -1;
		struct_tmp tmp_3; tmp_3.num = -1; tmp_3.m = -1; tmp_3.r = -1; tmp_3.r_ = -1; tmp_3.l = -1; tmp_3.l_ = -1;

		for (int j=0; j<4; j++) {

			int key = -1;
			int key_ = -1;

			if (sortData[i][j] != -1 && i > 0 && (key = array_search(sortData[i][j], sortData[i-1], 4)) >= 0) {

				if (i > 1 && (key_ = array_search(sortData[i][j], sortData[i-2], 4)) >= 0) {

					if (!isset_struct(tmp_3)) {

						if (i < 19 && rrr[i+1] == sortData[i][j]) {} else {

							tmp_3.num = sortData[i][j];
							tmp_3.m = j;
							tmp_3.l = key;
							tmp_3.l_ = key_;
						}

					}else {

						int count_1 = tmp_3.m + tmp_3.l + tmp_3.l_;
						int count_2 = j + key + key_;

						if (count_2 < count_1) {

							if (i < 19 && rrr[i+1] == sortData[i][j]) {} else {

								tmp_3.num = sortData[i][j];
								tmp_3.m = j;
								tmp_3.l = key;
								tmp_3.l_ = key_;
							}
						}
					}
				}else {

					if (!isset_struct(tmp_2)) {

						if (i < 19 && rrr[i+1] == sortData[i][j]) {} else {

							tmp_2.num = sortData[i][j];
							tmp_2.m = j;
							tmp_2.l = key;
						}

					}else {

						int count_1 = tmp_2.m + tmp_2.l;
						int count_2 = j + key;

						if (count_2 < count_1) {

							if (i < 19 && rrr[i+1] == sortData[i][j]) {} else {

								tmp_2.num = sortData[i][j];
								tmp_2.m = j;
								tmp_2.l = key;
							}
						}
					}
				}

			}else if (sortData[i][j] != -1) {

				if (i == 19 || (i < 19 && rrr[i+1] != sortData[i][j])) {

					if (!isset_struct(tmp_1)) {

						tmp_1.num = sortData[i][j];
						tmp_1.m = j;
					}
				}
			}
		}

		if (isset_struct(tmp_1) && isset_struct(tmp_2)) {

			if ((tmp_1.m < tmp_2.m) && (tmp_1.m < tmp_2.l)) {

				float quotient_m = sortValue[i][tmp_1.m] / sortValue[i][tmp_2.m];
				float quotient_l = sortValue[i][tmp_1.m] / sortValue[i-1][tmp_2.l];

				if (quotient_m > 1.6 && quotient_l > 1.6) {

					unset(&tmp_2);
				}
			}
		}

		if (isset_struct(tmp_2) && !isset_num(rrr[i])) {

			rrr[i] = tmp_2.num;
		}

		if (isset_struct(tmp_3) && !isset_num(rrr[i])) {

			rrr[i] = tmp_3.num;
		}

		if (isset_struct(tmp_1) && !isset_num(rrr[i])) {

			rrr[i] = tmp_1.num;
		}

		if (!isset_num(rrr[i])) {

			rrr[i] = sortData[i][0];

			if (rrr[i] == -1) {
				rrr[i] = 0;
			}
		}
	}

    for (int i=0; i<10; i++) {
        int t;
        t = rrr[i];
        rrr[i] = rrr[19-i];
        rrr[19-i] = t;
    }

    // 打印排序数据

    /*
     for (int i=0; i<4; i++) {

     printf("%d: ", i);
     for (int j=0; j<20; j++) {
     if (sortData[j][i] != -1) {
     printf("%2d ", sortData[j][i]);
     }else {
     printf("   ");
     }
     }
     printf("\n");
     }
     printf("\n");

     for (int i=0; i<4; i++) {

     printf("%d: ", i);
     for (int j=0; j<20; j++) {
     if (sortValue[j][i] != -1) {
     printf("%.4f  ", sortValue[j][i]);
     }else {
     printf("        ");
     }
     }
     printf("\n");
     }

     printf("\n");
     */
}


TEST_P(BufferSetSubDataTests, SmallDataAtOffset) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 4000;
    descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    constexpr uint32_t kOffset = 2000;
    uint8_t value = 231;
    buffer.SetSubData(kOffset, sizeof(value), &value);

    EXPECT_BUFFER_U8_EQ(value, buffer, kOffset);
}

#include "tests/DawnTest.h"

#include <cstring>

class BufferMapReadTests : public DawnTest {
    protected:
        static void MapReadCallback(dawnBufferMapAsyncStatus status, const void* data, dawnCallbackUserdata userdata) {
            ASSERT_EQ(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, status);
            ASSERT_NE(nullptr, data);

            auto test = reinterpret_cast<BufferMapReadTests*>(static_cast<uintptr_t>(userdata));
            test->mappedData = data;
        }

        const void* MapReadAsyncAndWait(const dawn::Buffer& buffer, uint32_t start, uint32_t offset) {
            buffer.MapReadAsync(start, offset, MapReadCallback, static_cast<dawn::CallbackUserdata>(reinterpret_cast<uintptr_t>(this)));

            while (mappedData == nullptr) {
                WaitABit();
            }

            return mappedData;
        }

    private:
        const void* mappedData = nullptr;
};

// Test that the simplest map read (one u8 at offset 0) works.
TEST_P(BufferMapReadTests, SmallReadAtZero) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 1;
    descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    uint8_t myData = 187;
    buffer.SetSubData(0, sizeof(myData), &myData);

    const void* mappedData = MapReadAsyncAndWait(buffer, 0, 1);
    ASSERT_EQ(myData, *reinterpret_cast<const uint8_t*>(mappedData));

    buffer.Unmap();
}

// Test mapping a buffer at an offset.
TEST_P(BufferMapReadTests, SmallReadAtOffset) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 4000;
    descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    uint8_t myData = 234;
    buffer.SetSubData(2048, sizeof(myData), &myData);

    const void* mappedData = MapReadAsyncAndWait(buffer, 2048, 4);
    ASSERT_EQ(myData, *reinterpret_cast<const uint8_t*>(mappedData));

    buffer.Unmap();
}

// Test mapping a buffer at an offset that's not uint32-aligned.
TEST_P(BufferMapReadTests, SmallReadAtUnalignedOffset) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 4000;
    descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    uint8_t myData = 213;
    buffer.SetSubData(3, 1, &myData);

    const void* mappedData = MapReadAsyncAndWait(buffer, 3, 1);
    ASSERT_EQ(myData, *reinterpret_cast<const uint8_t*>(mappedData));

    buffer.Unmap();
}

// Test mapping large ranges of a buffer.
TEST_P(BufferMapReadTests, LargeRead) {
    constexpr uint32_t kDataSize = 1000 * 1000;
    std::vector<uint32_t> myData;
    for (uint32_t i = 0; i < kDataSize; ++i) {
        myData.push_back(i);
    }

    dawn::BufferDescriptor descriptor;
    descriptor.size = static_cast<uint32_t>(kDataSize * sizeof(uint32_t));
    descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    buffer.SetSubData(0, kDataSize * sizeof(uint32_t), reinterpret_cast<uint8_t*>(myData.data()));

    const void* mappedData = MapReadAsyncAndWait(buffer, 0, static_cast<uint32_t>(kDataSize * sizeof(uint32_t)));
    ASSERT_EQ(0, memcmp(mappedData, myData.data(), kDataSize * sizeof(uint32_t)));

    buffer.Unmap();
}

DAWN_INSTANTIATE_TEST(BufferMapReadTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)

class BufferMapWriteTests : public DawnTest {
    protected:

        static void MapWriteCallback(dawnBufferMapAsyncStatus status, void* data, dawnCallbackUserdata userdata) {
            ASSERT_EQ(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, status);
            ASSERT_NE(nullptr, data);

            auto test = reinterpret_cast<BufferMapWriteTests*>(static_cast<uintptr_t>(userdata));
            test->mappedData = data;
        }

        void* MapWriteAsyncAndWait(const dawn::Buffer& buffer, uint32_t start, uint32_t offset) {
            buffer.MapWriteAsync(start, offset, MapWriteCallback, static_cast<dawn::CallbackUserdata>(reinterpret_cast<uintptr_t>(this)));

            while (mappedData == nullptr) {
                WaitABit();
            }

            return mappedData;
        }

    private:
        void* mappedData = nullptr;
};

// Test that the simplest map write (one u32 at offset 0) works.
TEST_P(BufferMapWriteTests, SmallWriteAtZero) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 4;
    descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    uint32_t myData = 2934875;
    void* mappedData = MapWriteAsyncAndWait(buffer, 0, 4);
    memcpy(mappedData, &myData, sizeof(myData));
    buffer.Unmap();

    EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}

// Test mapping a buffer at an offset.
TEST_P(BufferMapWriteTests, SmallWriteAtOffset) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 4000;
    descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    uint32_t myData = 2934875;
    void* mappedData = MapWriteAsyncAndWait(buffer, 2048, 4);
    memcpy(mappedData, &myData, sizeof(myData));
    buffer.Unmap();

    EXPECT_BUFFER_U32_EQ(myData, buffer, 2048);
}

// Test mapping large ranges of a buffer.
TEST_P(BufferMapWriteTests, LargeWrite) {
    constexpr uint32_t kDataSize = 1000 * 1000;
    std::vector<uint32_t> myData;
    for (uint32_t i = 0; i < kDataSize; ++i) {
        myData.push_back(i);
    }

    dawn::BufferDescriptor descriptor;
    descriptor.size = static_cast<uint32_t>(kDataSize * sizeof(uint32_t));
    descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    void* mappedData = MapWriteAsyncAndWait(buffer, 0, kDataSize * sizeof(uint32_t));
    memcpy(mappedData, myData.data(), kDataSize * sizeof(uint32_t));
    buffer.Unmap();

    EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 0, kDataSize);
}

DAWN_INSTANTIATE_TEST(BufferMapWriteTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)

class BufferSetSubDataTests : public DawnTest {
};

// Test the simplest set sub data: setting one u8 at offset 0.
TEST_P(BufferSetSubDataTests, SmallDataAtZero) {
    dawn::BufferDescriptor descriptor;
    descriptor.size = 1;
    descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    uint8_t value = 171;
    buffer.SetSubData(0, sizeof(value), &value);

    EXPECT_BUFFER_U8_EQ(value, buffer, 0);
}

// Test that SetSubData offset works.

// Stress test for many calls to SetSubData
TEST_P(BufferSetSubDataTests, ManySetSubData) {
    // TODO(example@nnthink.com): Use ringbuffers for SetSubData on explicit APIs.
    // otherwise this creates too many resources and can take freeze the driver(?)
    DAWN_SKIP_TEST_IF(IsD3D12() || IsMetal() || IsVulkan());

    constexpr uint32_t kSize = 4000 * 1000;
    constexpr uint32_t kElements = 1000 * 1000;
    dawn::BufferDescriptor descriptor;
    descriptor.size = kSize;
    descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    std::vector<uint32_t> expectedData;
    for (uint32_t i = 0; i < kElements; ++i) {
        buffer.SetSubData(i * sizeof(uint32_t), sizeof(i), reinterpret_cast<uint8_t*>(&i));
        expectedData.push_back(i);
    }

    EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), buffer, 0, kElements);
}

// Test using SetSubData for lots of data
TEST_P(BufferSetSubDataTests, LargeSetSubData) {
    constexpr uint32_t kSize = 4000 * 1000;
    constexpr uint32_t kElements = 1000 * 1000;
    dawn::BufferDescriptor descriptor;
    descriptor.size = kSize;
    descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
    dawn::Buffer buffer = device.CreateBuffer(&descriptor);

    std::vector<uint32_t> expectedData;
    for (uint32_t i = 0; i < kElements; ++i) {
        expectedData.push_back(i);
    }

    buffer.SetSubData(0, kElements * sizeof(uint32_t), reinterpret_cast<uint8_t*>(expectedData.data()));

    EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), buffer, 0, kElements);
}

DAWN_INSTANTIATE_TEST(BufferSetSubDataTests,
                     D3D12Backend,
                     MetalBackend,
                     OpenGLBackend,
                     VulkanBackend)


void merge(int st, int en){
  int mid = (st+en)/2;
  int lidx = st; // arr[st:mid]에서 값을 보기 위해 사용하는 index
  int ridx = mid; // arr[mid:en]에서 값을 보기 위해 사용하는 index
  for(int i = st; i < en; i++){
    if(ridx == en) tmp[i] = arr[lidx++];
    else if(lidx == mid) tmp[i] = arr[ridx++];
    else if(arr[lidx] <= arr[ridx]) tmp[i] = arr[lidx++];
    else tmp[i] = arr[ridx++];
  }
  for(int i = st; i < en; i++) arr[i] = tmp[i]; // tmp에 임시저장해둔 값을 a로 다시 옮김
}

#include <bits/stdc++.h>
using namespace std;

int n = 10;
int arr[1000001] = {15, 25, 22, 357, 16, 23, -53, 12, 46, 3};
int tmp[1000001]; // merge 함수에서 리스트 2개를 합친 결과를 임시로 저장하고 있을 변수

void merge_sort(int st, int en){
  if(en == st+1) return; // 리스트의 길이가 1인 경우
  int mid = (st+en)/2;
  merge_sort(st, mid); // arr[st:mid]을 정렬한다.
  merge_sort(mid, en); // arr[mid:en]을 정렬한다.
  merge(st, en); // arr[st:mid]와 arr[mid:en]을 합친다.
}

int main(void){
  ios::sync_with_stdio(0);
  cin.tie(0);
  merge_sort(0, n);
  for(int i = 0; i < n; i++) cout << arr[i] << ' '; // -53 3 12 15 16 22 23 25 46 357
}


bool
TTabbedView::VnIsWindow(HWND hWnd)
{
  return hWnd == GetHandle() || IsChild(hWnd);
}

//  ObjectWindows
/// \file
///  Source file for implementation of TTabbedBase.
///  Added dynamic Add/DeletePage. Contributed by David Linch.
//@doc TTabbedBase
#include <owl/pch.h>

#include <owl/defs.h>
#include <owl/docmanag.h>
#include <owl/layoutwi.h>
#include <owl/tabbed.h>

OWL_DIAGINFO;

namespace owl {

static void setWindowLayoutMetrics(TLayoutMetrics& lm, TNoteTab* tab,
                             TTabbedBase::TTabPosition pos)
{
  if(pos == TTabbedBase::tpLeft){
    lm.X.LeftOf(tab, 0);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.SameAs(lmParent, lmWidth);
    lm.Height.SameAs(lmParent, lmHeight);
  }
  else if(pos == TTabbedBase::tpRight){
    lm.X.SameAs(lmParent, lmLeft);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.RightOf(tab);
    lm.Height.SameAs(lmParent, lmHeight);
  }
  else if(pos == TTabbedBase::tpTop){
    lm.X.Below(tab);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.SameAs(lmParent, lmWidth);
    lm.Height.SameAs(lmParent, lmHeight);
  }
  else if(pos == TTabbedBase::tpBottom){
    lm.X.SameAs(lmParent, lmLeft);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.SameAs(lmParent, lmRight);
    lm.Height.Above(tab);
  }
}

// TTabbedBase
DEFINE_RESPONSE_TABLE1(TTabbedBase,   TEventHandler)
  EV_TCN_SELCHANGE(IDC_TABCONTROL,     EvTabControlSelChange),
  EV_TCN_SELCHANGING(IDC_TABCONTROL,  EvTabControlSelChanging),
END_RESPONSE_TABLE;

TTabbedBase::TTabbedBase(TWindow* self, int x, int y, int w, int h, TTabPosition pos, TNoteTab* tab)
:
  Position(pos)
{
   NoteTab = tab;
  if(!NoteTab)
    NoteTab = new TNoteTab(self, IDC_TABCONTROL, x,y,w,h);
}
TTabbedBase::TTabbedBase(TWindow* self, TTabPosition pos, TNoteTab* tab)
:
  Position(pos)
{
   NoteTab = tab;
  if(!NoteTab)
    NoteTab = new TNoteTab(self, IDC_TABCONTROL);
}
/// Add a new tab page.
/// \note Use before the Tab window is created!
int TTabbedBase::Add(TWindow& wnd, LPCTSTR titles)
{
  LPCTSTR text = titles ? titles : wnd.GetCaption();
  if (!text)
    text = _T("");

  int index = NoteTab->Add(text, INT_PTR((TWindow*)&wnd));
#if 0  // this code now in AddPage()
  if(NoteTab->GetHandle() && index >=0 ){
    TLayoutWindow* layoutWnd = TYPESAFE_DOWNCAST(this, TLayoutWindow);
    if(layoutWnd){
      TLayoutMetrics lmWindow;
      setWindowLayoutMetrics(lmWindow, NoteTab, Position);
      if(!wnd.GetHandle())
        wnd.Create();

      layoutWnd->SetChildLayoutMetrics(wnd, lmWindow);
      layoutWnd->Layout();
    }
    else{
      if(!wnd.GetHandle())
        wnd.Create();
      AdjustPage(wnd);
    }
    wnd.ShowWindow(SW_SHOWNORMAL);

  }
#endif
  return index;
}
void TTabbedBase::AdjustPage(TWindow& page)
{
  // assumes that note tab positioning
  TWindow* parent = NoteTab->GetParentO();

  // Retrieve area of tab (in terms of tabbed window)
  TRect tRect;
  NoteTab->GetWindowRect(tRect);
  ::MapWindowPoints(HWND_DESKTOP, *parent, LPPOINT(&tRect), 2);
  TRect pgRect;
  page.GetWindowRect(pgRect);
  ::MapWindowPoints(HWND_DESKTOP, *parent, LPPOINT(&pgRect), 2);
  pgRect.left = pgRect.top   = 0;

  switch(Position){
    case tpLeft:
      pgRect += TPoint(tRect.Width(), 0);
      break;
    case tpRight:
       if(pgRect.right > tRect.left)
         pgRect.right = tRect.left;
      break;
    case tpTop:
       pgRect += TPoint(0, tRect.Height());
      break;
    case tpBottom:
       if(pgRect.bottom > tRect.top)
         pgRect.bottom = tRect.top;
      break;
  }
  page.SetWindowPos(HWND_TOP, pgRect, SWP_NOACTIVATE|SWP_NOREDRAW|SWP_NOZORDER);
}
void TTabbedBase::SetupPages()
{
  // first set all titles
  TWindow* wnd;
  TNoteTabItem Item;
  int count = NoteTab->GetCount();
  for(int i = 0; i< count; i++){
    NoteTab->GetItem(i, Item);
    wnd = ((TWindow*)Item.ClientData);
    if (!wnd->IsWindow())
    {
      wnd->Create();
    wnd->ShowWindow(SW_HIDE);
    }

    // it will be here only for dialog ????
    if(Item.Label.empty()){
      tchar title[MAX_PATH];
       wnd->GetWindowText(title, MAX_PATH);
      Item.Label = title;
      NoteTab->SetItem(i, Item);
    }
    // Adjust child position
    AdjustPage(*wnd);
  }
  if(count){
    int Selected = NoteTab->GetSel();
    NoteTab->GetItem(Selected, Item);
    wnd = (TWindow*)Item.ClientData;
    // Show the newly selected tab, if any.
    if(wnd)
      wnd->ShowWindow(SW_SHOWNORMAL);
  }
}
TWindow* TTabbedBase::GetPage(int index)
{
  if(index == -1)
    index = NoteTab->GetSel();
  TNoteTabItem Item;
  NoteTab->GetItem(index, Item);

  return (TWindow*)Item.ClientData;
}
void TTabbedBase::EvTabControlSelChange(TNotify&)
{
  int Selected = NoteTab->GetSel();
  TNoteTabItem Item;
  NoteTab->GetItem(Selected, Item);

  // Set the pointer to currently selected item
  TWindow* wnd = (TWindow*)Item.ClientData;
  if(wnd)
    wnd->ShowWindow(SW_SHOWNORMAL);
}
bool TTabbedBase::EvTabControlSelChanging(TNotify &)
{
  int Selected = NoteTab->GetSel();
  TNoteTabItem Item;
  NoteTab->GetItem(Selected, Item);
  TWindow* wnd = (TWindow*)Item.ClientData;

  if (wnd)
    wnd->ShowWindow(SW_HIDE);

  return false;
}

DEFINE_RESPONSE_TABLE2(TTabbedWindow, TLayoutWindow, TTabbedBase)
END_RESPONSE_TABLE;

TTabbedWindow::TTabbedWindow(TWindow* parent, LPCTSTR title, TModule* module)
:
  TLayoutWindow(parent, title, module),
  TTabbedBase(this,0,0,0,0)
{
  Init();
}

TTabbedWindow::TTabbedWindow(TWindow* parent, const tstring& title, TModule* module)
  : TLayoutWindow(parent, title, module),
  TTabbedBase(this,0,0,0,0)
{
  Init();
}

void TTabbedWindow::Init()
{
  SetBkgndColor(TColor::Transparent); // No erase; see TWindow::EvEraseBkgnd.
  ModifyStyle(0, WS_CLIPCHILDREN | WS_CLIPSIBLINGS); // Clipping reduces resize flicker.
}

bool TTabbedWindow::Create()
{
  uint count = GetTabControl()->GetCount();
  uint i;
  for(i = 0; i < count; i++){
    TWindow* wnd = GetPage(i);
    wnd->ModifyStyle(WS_VISIBLE, 0);
  }

  // Calculate tab control height and layout.
  uint height = GetTabControl()->GetMinimalHeight() + 1;
  TLayoutMetrics lmWindow;
  TLayoutMetrics lmNoteTab;
  setWindowLayoutMetrics(lmWindow, GetTabControl(), Position);
  switch(Position){
    case tpLeft:
      lmNoteTab.X.SameAs(lmParent, lmLeft);
      lmNoteTab.Y.SameAs(lmParent, lmTop);
      lmNoteTab.Width.Absolute(lmRight, height);
      lmNoteTab.Width.Units = lmPixels;
      lmNoteTab.Height.SameAs(lmParent, lmHeight);
      break;
    case tpRight:
      lmNoteTab.X.SameAs(lmParent, lmRight);
      lmNoteTab.Y.SameAs(lmParent, lmTop);
      lmNoteTab.Height.SameAs(lmParent, lmHeight);
      lmNoteTab.Width.Absolute(lmLeft, height);
      lmNoteTab.Width.Units = lmPixels;
      break;
    case tpTop:
      lmNoteTab.X.SameAs(lmParent, lmLeft);
      lmNoteTab.Y.SameAs(lmParent, lmTop);
      lmNoteTab.Width.SameAs(lmParent, lmWidth);
      lmNoteTab.Height.Absolute(lmBottom, height);
      lmNoteTab.Height.Units = lmPixels;
      break;
    case tpBottom:
      lmNoteTab.X.SameAs(lmParent, lmLeft);

      // Needed an adjustment of -1 here, otherwise there is a 1 pixel gap
      // below the tab control.
      // TODO: Determine if this is a bug in TLayoutWindow,  or whether this
      // bottom pixel exclusion is indeed correct.
      lmNoteTab.Y.Set(lmBottom, lmAbove, lmParent, lmBottom, -1);
      lmNoteTab.Height.Absolute(height);
      lmNoteTab.Height.Units = lmPixels;
      lmNoteTab.Width.SameAs(lmParent, lmRight);
      break;
  }
  SetChildLayoutMetrics(*GetTabControl(), lmNoteTab);

  for(i = 0; i < count; i++)
    SetChildLayoutMetrics(*GetPage(i), lmWindow);

  TLayoutWindow::Create();

  SetupPages();

  return true;
}
/// Add a new tab page.
///\note Need at least one page in order to add a page. The initial page must be added before creating the window.
int TTabbedWindow::AddPage(TWindow& wnd, LPCTSTR titles)
{
   TWindow* wnd1 = (TWindow*)&wnd;
   //  If the layout window hasn't been created, just Add the tab
   if(!GetHandle())
      return Add(*wnd1, titles);

   // Need at least one page in order to add a page
   if(GetTabControl()->GetSel() < 0)
      return -1;

   // Tell the currently selected Tab we're about to change
   TNotify dummy;
   EvTabControlSelChanging(dummy);

   int index = Add(*wnd1, titles);
   wnd1->ModifyStyle(WS_VISIBLE, 0);

   TLayoutMetrics lmWindow;
   GetChildLayoutMetrics(*GetPage(0), lmWindow);

   if(!wnd1->IsWindow())
      wnd1->Create();

   SetChildLayoutMetrics(*wnd1, lmWindow);
   Layout();

   // Tell the new Tab we've changed to it
   EvTabControlSelChange(dummy);

   return index;
}
bool TTabbedWindow::DeletePage(TWindow& wnd)
{
   TWindow* wnd1 = (TWindow*)&wnd;
  uint count = GetTabControl()->GetCount();
   for(uint i = 0; i < count; i++){
      if(wnd1 == GetPage(i)) {
         // Tell the currently selected Tab we're about to change
         TNotify dummy;
         EvTabControlSelChanging(dummy);
         GetTabControl()->Delete(i);
         EvTabControlSelChange(dummy);
         return true;
      }
   }
   return false;
}

void TTabbedWindow::ResizeTabControl(int newHeight)
{
  TNoteTab* t = GetTabControl(); CHECK(t);
  TLayoutMetrics m;
  GetChildLayoutMetrics(*t, m);
  m.Height.Absolute(newHeight);
  m.SetMeasurementUnits(lmPixels);
  SetChildLayoutMetrics(*t, m);
  Layout();
  t->Invalidate();
}

// TTabbedView Implementation

DEFINE_RESPONSE_TABLE1(TTabbedView, TTabbedWindow)
  EV_VN_ISWINDOW,
END_RESPONSE_TABLE;

TTabbedView::TTabbedView(TDocument& doc, TWindow* parent)
:
  TTabbedWindow(parent, 0, doc.GetDocManager().GetApplication()),
  TView(doc)
{
}

// Does a given HWND belong to this view? Yes if it is us, or a child of us

/*
IMPLEMENT_STREAMABLE2(TTabbedWindow, TLayoutWindow, TTabbed);
IMPLEMENT_STREAMABLE2(TTabbedView, TTabbedWindow, TView);

#if !defined(BI_NO_OBJ_STREAMING)

void*
*/
/*
{
  ReadBaseObject((TTabbedWindow*)GetObject(), is);
  ReadBaseObject((TView*)GetObject(), is);
  return GetObject();
}

void
TTabbedView::Streamer::Write(opstream& os) const
{
  WriteBaseObject((TTabbedWindow*)GetObject(), os);
  WriteBaseObject((TView*)GetObject(), os);
}

#endif  // if !defined(BI_NO_OBJ_STREAMING)

*/

} // OWL namespace


switch(Position){
    case tpLeft:
      pgRect += TPoint(tRect.Width(), 0);
      break;
    case tpRight:
       if(pgRect.right > tRect.left)
         pgRect.right = tRect.left;
      break;
    case tpTop:
       pgRect += TPoint(0, tRect.Height());
      break;
    case tpBottom:
       if(pgRect.bottom > tRect.top)
         pgRect.bottom = tRect.top;
      break;
  }

//  ObjectWindows
/// \file
///  Source file for implementation of TTabbedBase.
///  Added dynamic Add/DeletePage. Contributed by David Linch.
//@doc TTabbedBase
#include <owl/pch.h>

#include <owl/defs.h>
#include <owl/docmanag.h>
#include <owl/layoutwi.h>
#include <owl/tabbed.h>

OWL_DIAGINFO;

namespace owl {

static void setWindowLayoutMetrics(TLayoutMetrics& lm, TNoteTab* tab,
                             TTabbedBase::TTabPosition pos)
{
  if(pos == TTabbedBase::tpLeft){
    lm.X.LeftOf(tab, 0);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.SameAs(lmParent, lmWidth);
    lm.Height.SameAs(lmParent, lmHeight);
  }
  else if(pos == TTabbedBase::tpRight){
    lm.X.SameAs(lmParent, lmLeft);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.RightOf(tab);
    lm.Height.SameAs(lmParent, lmHeight);
  }
  else if(pos == TTabbedBase::tpTop){
    lm.X.Below(tab);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.SameAs(lmParent, lmWidth);
    lm.Height.SameAs(lmParent, lmHeight);
  }
  else if(pos == TTabbedBase::tpBottom){
    lm.X.SameAs(lmParent, lmLeft);
    lm.Y.SameAs(lmParent, lmTop);
    lm.Width.SameAs(lmParent, lmRight);
    lm.Height.Above(tab);
  }
}

// TTabbedBase
DEFINE_RESPONSE_TABLE1(TTabbedBase,   TEventHandler)
  EV_TCN_SELCHANGE(IDC_TABCONTROL,     EvTabControlSelChange),
  EV_TCN_SELCHANGING(IDC_TABCONTROL,  EvTabControlSelChanging),
END_RESPONSE_TABLE;

TTabbedBase::TTabbedBase(TWindow* self, int x, int y, int w, int h, TTabPosition pos, TNoteTab* tab)
:
  Position(pos)
{
   NoteTab = tab;
  if(!NoteTab)
    NoteTab = new TNoteTab(self, IDC_TABCONTROL, x,y,w,h);
}
TTabbedBase::TTabbedBase(TWindow* self, TTabPosition pos, TNoteTab* tab)
:
  Position(pos)
{
   NoteTab = tab;
  if(!NoteTab)
    NoteTab = new TNoteTab(self, IDC_TABCONTROL);
}
/// Add a new tab page.
/// \note Use before the Tab window is created!
int TTabbedBase::Add(TWindow& wnd, LPCTSTR titles)
{
  LPCTSTR text = titles ? titles : wnd.GetCaption();
  if (!text)
    text = _T("");

  int index = NoteTab->Add(text, INT_PTR((TWindow*)&wnd));
#if 0  // this code now in AddPage()
  if(NoteTab->GetHandle() && index >=0 ){
    TLayoutWindow* layoutWnd = TYPESAFE_DOWNCAST(this, TLayoutWindow);
    if(layoutWnd){
      TLayoutMetrics lmWindow;
      setWindowLayoutMetrics(lmWindow, NoteTab, Position);
      if(!wnd.GetHandle())
        wnd.Create();

      layoutWnd->SetChildLayoutMetrics(wnd, lmWindow);
      layoutWnd->Layout();
    }
    else{
      if(!wnd.GetHandle())
        wnd.Create();
      AdjustPage(wnd);
    }
    wnd.ShowWindow(SW_SHOWNORMAL);

  }
#endif
  return index;
}
void TTabbedBase::AdjustPage(TWindow& page)
{
  // assumes that note tab positioning
  TWindow* parent = NoteTab->GetParentO();

  // Retrieve area of tab (in terms of tabbed window)
  TRect tRect;
  NoteTab->GetWindowRect(tRect);
  ::MapWindowPoints(HWND_DESKTOP, *parent, LPPOINT(&tRect), 2);
  TRect pgRect;
  page.GetWindowRect(pgRect);
  ::MapWindowPoints(HWND_DESKTOP, *parent, LPPOINT(&pgRect), 2);
  pgRect.left = pgRect.top   = 0;

  page.SetWindowPos(HWND_TOP, pgRect, SWP_NOACTIVATE|SWP_NOREDRAW|SWP_NOZORDER);
}
void TTabbedBase::SetupPages()
{
  // first set all titles
  TWindow* wnd;
  TNoteTabItem Item;
  int count = NoteTab->GetCount();
  for(int i = 0; i< count; i++){
    NoteTab->GetItem(i, Item);
    wnd = ((TWindow*)Item.ClientData);
    if (!wnd->IsWindow())
    {
      wnd->Create();
    wnd->ShowWindow(SW_HIDE);
    }

    // it will be here only for dialog ????
    if(Item.Label.empty()){
      tchar title[MAX_PATH];
       wnd->GetWindowText(title, MAX_PATH);
      Item.Label = title;
      NoteTab->SetItem(i, Item);
    }
    // Adjust child position
    AdjustPage(*wnd);
  }
  if(count){
    int Selected = NoteTab->GetSel();
    NoteTab->GetItem(Selected, Item);
    wnd = (TWindow*)Item.ClientData;
    // Show the newly selected tab, if any.
    if(wnd)
      wnd->ShowWindow(SW_SHOWNORMAL);
  }
}
TWindow* TTabbedBase::GetPage(int index)
{
  if(index == -1)
    index = NoteTab->GetSel();
  TNoteTabItem Item;
  NoteTab->GetItem(index, Item);

  return (TWindow*)Item.ClientData;
}
void TTabbedBase::EvTabControlSelChange(TNotify&)
{
  int Selected = NoteTab->GetSel();
  TNoteTabItem Item;
  NoteTab->GetItem(Selected, Item);

  // Set the pointer to currently selected item
  TWindow* wnd = (TWindow*)Item.ClientData;
  if(wnd)
    wnd->ShowWindow(SW_SHOWNORMAL);
}
bool TTabbedBase::EvTabControlSelChanging(TNotify &)
{
  int Selected = NoteTab->GetSel();
  TNoteTabItem Item;
  NoteTab->GetItem(Selected, Item);
  TWindow* wnd = (TWindow*)Item.ClientData;

  if (wnd)
    wnd->ShowWindow(SW_HIDE);

  return false;
}

DEFINE_RESPONSE_TABLE2(TTabbedWindow, TLayoutWindow, TTabbedBase)
END_RESPONSE_TABLE;

TTabbedWindow::TTabbedWindow(TWindow* parent, LPCTSTR title, TModule* module)
:
  TLayoutWindow(parent, title, module),
  TTabbedBase(this,0,0,0,0)
{
  Init();
}

TTabbedWindow::TTabbedWindow(TWindow* parent, const tstring& title, TModule* module)
  : TLayoutWindow(parent, title, module),
  TTabbedBase(this,0,0,0,0)
{
  Init();
}

void TTabbedWindow::Init()
{
  SetBkgndColor(TColor::Transparent); // No erase; see TWindow::EvEraseBkgnd.
  ModifyStyle(0, WS_CLIPCHILDREN | WS_CLIPSIBLINGS); // Clipping reduces resize flicker.
}

bool TTabbedWindow::Create()
{
  uint count = GetTabControl()->GetCount();
  uint i;
  for(i = 0; i < count; i++){
    TWindow* wnd = GetPage(i);
    wnd->ModifyStyle(WS_VISIBLE, 0);
  }

  // Calculate tab control height and layout.
  uint height = GetTabControl()->GetMinimalHeight() + 1;
  TLayoutMetrics lmWindow;
  TLayoutMetrics lmNoteTab;
  setWindowLayoutMetrics(lmWindow, GetTabControl(), Position);
  switch(Position){
    case tpLeft:
      lmNoteTab.X.SameAs(lmParent, lmLeft);
      lmNoteTab.Y.SameAs(lmParent, lmTop);
      lmNoteTab.Width.Absolute(lmRight, height);
      lmNoteTab.Width.Units = lmPixels;
      lmNoteTab.Height.SameAs(lmParent, lmHeight);
      break;
    case tpRight:
      lmNoteTab.X.SameAs(lmParent, lmRight);
      lmNoteTab.Y.SameAs(lmParent, lmTop);
      lmNoteTab.Height.SameAs(lmParent, lmHeight);
      lmNoteTab.Width.Absolute(lmLeft, height);
      lmNoteTab.Width.Units = lmPixels;
      break;
    case tpTop:
      lmNoteTab.X.SameAs(lmParent, lmLeft);
      lmNoteTab.Y.SameAs(lmParent, lmTop);
      lmNoteTab.Width.SameAs(lmParent, lmWidth);
      lmNoteTab.Height.Absolute(lmBottom, height);
      lmNoteTab.Height.Units = lmPixels;
      break;
    case tpBottom:
      lmNoteTab.X.SameAs(lmParent, lmLeft);

      // Needed an adjustment of -1 here, otherwise there is a 1 pixel gap
      // below the tab control.
      // TODO: Determine if this is a bug in TLayoutWindow,  or whether this
      // bottom pixel exclusion is indeed correct.
      lmNoteTab.Y.Set(lmBottom, lmAbove, lmParent, lmBottom, -1);
      lmNoteTab.Height.Absolute(height);
      lmNoteTab.Height.Units = lmPixels;
      lmNoteTab.Width.SameAs(lmParent, lmRight);
      break;
  }
  SetChildLayoutMetrics(*GetTabControl(), lmNoteTab);

  for(i = 0; i < count; i++)
    SetChildLayoutMetrics(*GetPage(i), lmWindow);

  TLayoutWindow::Create();

  SetupPages();

  return true;
}
/// Add a new tab page.
///\note Need at least one page in order to add a page. The initial page must be added before creating the window.
int TTabbedWindow::AddPage(TWindow& wnd, LPCTSTR titles)
{
   TWindow* wnd1 = (TWindow*)&wnd;
   //  If the layout window hasn't been created, just Add the tab
   if(!GetHandle())
      return Add(*wnd1, titles);

   // Need at least one page in order to add a page
   if(GetTabControl()->GetSel() < 0)
      return -1;

   // Tell the currently selected Tab we're about to change
   TNotify dummy;
   EvTabControlSelChanging(dummy);

   int index = Add(*wnd1, titles);
   wnd1->ModifyStyle(WS_VISIBLE, 0);

   TLayoutMetrics lmWindow;
   GetChildLayoutMetrics(*GetPage(0), lmWindow);

   if(!wnd1->IsWindow())
      wnd1->Create();

   SetChildLayoutMetrics(*wnd1, lmWindow);
   Layout();

   // Tell the new Tab we've changed to it
   EvTabControlSelChange(dummy);

   return index;
}
bool TTabbedWindow::DeletePage(TWindow& wnd)
{
   TWindow* wnd1 = (TWindow*)&wnd;
  uint count = GetTabControl()->GetCount();
   for(uint i = 0; i < count; i++){
      if(wnd1 == GetPage(i)) {
         // Tell the currently selected Tab we're about to change
         TNotify dummy;
         EvTabControlSelChanging(dummy);
         GetTabControl()->Delete(i);
         EvTabControlSelChange(dummy);
         return true;
      }
   }
   return false;
}

void TTabbedWindow::ResizeTabControl(int newHeight)
{
  TNoteTab* t = GetTabControl(); CHECK(t);
  TLayoutMetrics m;
  GetChildLayoutMetrics(*t, m);
  m.Height.Absolute(newHeight);
  m.SetMeasurementUnits(lmPixels);
  SetChildLayoutMetrics(*t, m);
  Layout();
  t->Invalidate();
}

// TTabbedView Implementation

DEFINE_RESPONSE_TABLE1(TTabbedView, TTabbedWindow)
  EV_VN_ISWINDOW,
END_RESPONSE_TABLE;

TTabbedView::TTabbedView(TDocument& doc, TWindow* parent)
:
  TTabbedWindow(parent, 0, doc.GetDocManager().GetApplication()),
  TView(doc)
{
}

// Does a given HWND belong to this view? Yes if it is us, or a child of us
bool
TTabbedView::VnIsWindow(HWND hWnd)
{
  return hWnd == GetHandle() || IsChild(hWnd);
}

/*
IMPLEMENT_STREAMABLE2(TTabbedWindow, TLayoutWindow, TTabbed);
IMPLEMENT_STREAMABLE2(TTabbedView, TTabbedWindow, TView);

#if !defined(BI_NO_OBJ_STREAMING)

void*
*/
/*
{
  ReadBaseObject((TTabbedWindow*)GetObject(), is);
  ReadBaseObject((TView*)GetObject(), is);
  return GetObject();
}

void
TTabbedView::Streamer::Write(opstream& os) const
{
  WriteBaseObject((TTabbedWindow*)GetObject(), os);
  WriteBaseObject((TView*)GetObject(), os);
}

#endif  // if !defined(BI_NO_OBJ_STREAMING)

*/

} // OWL namespace


void GCAMConsumer::initTechVintageVector() {
    const Modeltime* modeltime = scenario->getModeltime();
    objects::InitializeTechVectorHelper helper( 0, modeltime->getmaxper() );
    helper.initializeTechVintageVector( this );
}

/*
* LEGAL NOTICE
* This computer software was prepared by Battelle Memorial Institute,
* hereinafter the Contractor, under Contract No. DE-AC05-76RL0 1830
* with the Department of Energy (DOE). NEITHER THE GOVERNMENT NOR THE
* CONTRACTOR MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY
* LIABILITY FOR THE USE OF THIS SOFTWARE. This notice including this
* sentence must appear on any copies of this computer software.
*
* EXPORT CONTROL
* User agrees that the Software will not be shipped, transferred or
* exported into any country or used in any manner prohibited by the
* United States Export Administration Act or any other applicable
* export laws, restrictions or regulations (collectively the "Export Laws").
* Export of the Software may require some form of license or other
* authority from the U.S. Government, and failure to obtain such
* export control license may result in criminal liability under
* U.S. laws. In addition, if the Software is identified as export controlled
* items under the Export Laws, User represents and warrants that User
* is not a citizen, or otherwise located within, an embargoed nation
* (including without limitation Iran, Syria, Sudan, Cuba, and North Korea)
*     and that User is not otherwise prohibited
* under the Export Laws from receiving the Software.
*
* Copyright 2011 Battelle Memorial Institute.  All Rights Reserved.
* Distributed as open-source under the terms of the Educational Community
* License version 2.0 (ECL 2.0). http://www.opensource.org/licenses/ecl2.php
*
* For further details, see: http://www.globalchange.umd.edu/models/gcam/
*
*/

/*!
 * \file gcam_consumer.cpp
 * \ingroup Objects
 * \brief The GCAMConsumer class source file.
 * \author Pralit Patel
 * \author Jiyong Eom
 */
#include "util/base/include/definitions.h"
#include <xercesc/dom/DOMNode.hpp>

#include "consumers/include/gcam_consumer.h"
#include "util/base/include/xml_helper.h"
#include "containers/include/scenario.h"
#include "util/base/include/ivisitor.h"
#include "functions/include/node_input.h"
#include "containers/include/iinfo.h"
#include "demographics/include/demographic.h"
#include "containers/include/gdp.h"
#include "sectors/include/sector_utils.h"
#include "technologies/include/generic_output.h"
#include "emissions/include/aghg.h"

#include "util/base/include/initialize_tech_vector_helper.hpp"

using namespace std;
using namespace xercesc;

extern Scenario* scenario;

//! Default GCAMConsumer
GCAMConsumer::GCAMConsumer()
{
    // GCAM consumers do not vary by year.
    year = 0;
}

GCAMConsumer::~GCAMConsumer() {
}

void GCAMConsumer::copyParam( const BaseTechnology* aBaseTech, const int aPeriod ) {
    BaseTechnology::copyParam( aBaseTech, aPeriod );
    aBaseTech->copyParamsInto( *this, aPeriod );
}

GCAMConsumer* GCAMConsumer::clone() const {
    GCAMConsumer* clone = new GCAMConsumer();
    clone->copy( *this );
    return clone;
}

void GCAMConsumer::copy( const GCAMConsumer& aOther ) {
    Consumer::copy( aOther );
    mSubregionalPopulation = aOther.mSubregionalPopulation;
    mSubregionalIncome = aOther.mSubregionalIncome;
    mSubregionalPopulationShare = aOther.mSubregionalPopulationShare;
    mSubregionalIncomeShare = aOther.mSubregionalIncomeShare;
}

/*! \brief Get the XML node name for output to XML.
 *
 * This public function accesses the private constant string, XML_NAME.
 * This way the tag is always consistent for both read-in and output and can be easily changed.
 * This function may be virtual to be overriden by derived class pointers.
 * \author Josh Lurz, James Blackwood
 * \return The constant XML_NAME.
 */
const string& GCAMConsumer::getXMLName() const {
    return getXMLNameStatic();
}

/*! \brief Get the XML node name in static form for comparison when parsing XML.
 *
 * This public function accesses the private constant string, XML_NAME.
 * This way the tag is always consistent for both read-in and output and can be easily changed.
 * The "==" operator that is used when parsing, required this second function to return static.
 * \note A function cannot be static and virtual.
 * \author Josh Lurz, James Blackwood
 * \return The constant XML_NAME as a static.
 */
const string& GCAMConsumer::getXMLNameStatic() {
    const static string XML_NAME = "gcam-consumer";
    return XML_NAME;
}

bool GCAMConsumer::XMLDerivedClassParse( const string& aNodeName, const DOMNode* aCurr ) {
    if ( aNodeName == "subregional-income-share" ) {
        XMLHelper<Value>::insertValueIntoVector( aCurr, mSubregionalIncomeShare, scenario->getModeltime() );
    }
    else if ( aNodeName == "subregional-population-share" ) {
        XMLHelper<Value>::insertValueIntoVector( aCurr, mSubregionalPopulationShare, scenario->getModeltime() );
    }
    else {
        return false;
    }
    return true;
}

void GCAMConsumer::toDebugXMLDerived( const int aPeriod, ostream& aOut, Tabs* aTabs ) const {
    XMLWriteElement( mSubregionalPopulation[ aPeriod ], "subregional-population", aOut, aTabs );
    XMLWriteElement( mSubregionalIncome[ aPeriod ], "subregional-income", aOut, aTabs );
    XMLWriteElement( mSubregionalPopulationShare[ aPeriod ], "subregional-population-share", aOut, aTabs );
    XMLWriteElement( mSubregionalIncomeShare[ aPeriod ], "subregional-income-share", aOut, aTabs );
}

void GCAMConsumer::completeInit( const string& aRegionName, const string& aSectorName,
                                 const string& aSubsectorName ) {
    BaseTechnology::completeInit( aRegionName, aSectorName, aSubsectorName );

    // Basetechnology creates a SGMOutput. This is not needed for GCAM and
    // could cause problems with asserts so replace it with a GenericOutput
    for( unsigned int i = 0; i < mOutputs.size(); ++i ){
        delete mOutputs[ i ];
    }
    mOutputs.clear();
    mOutputs.insert( mOutputs.begin(), new GenericOutput( "generic-output" ) );
    // Basetechnology creates a CO2 object however they are not needed for GCAMConsumers
    // and they may cause trouble with tech vector sizing therefore just delete it.
    for( unsigned int i = 0; i < mGhgs.size(); ++i ){
        delete mGhgs[ i ];
    }
    mGhgs.clear();
    initTechVintageVector();

    // Interpolate shares for missing periods.
    SectorUtils::fillMissingPeriodVectorInterpolated( mSubregionalPopulationShare );
    SectorUtils::fillMissingPeriodVectorInterpolated( mSubregionalIncomeShare );
}

void GCAMConsumer::initCalc( const string& aRegionName,
                             const string& aSectorName, NationalAccount& aNationalAccount,
                             const Demographic* aDemographics, const GDP* aGDP,
                             const double aCapitalStock, const int aPeriod )
{
    double population = aDemographics->getTotal( aPeriod );
    double gdp = aGDP->getGDP( aPeriod );
    double gdpPPP = aGDP->getPPPGDPperCap( aPeriod ) * population;

    double subregionalPopulation = mSubregionalPopulationShare[ aPeriod ] * population;
    double subregionalIncome = mSubregionalIncomeShare[ aPeriod ] * gdp / subregionalPopulation;
    double subregionalIncomePPP = mSubregionalIncomeShare[ aPeriod ] * gdpPPP / subregionalPopulation;

    // store the subregional population and income for reporting
    mSubregionalPopulation[ aPeriod ] = subregionalPopulation;
    mSubregionalIncome[ aPeriod ] = subregionalIncome;

    // Set the subregional income and population into an info object so that nodes in the
    // nesting structure can have access to the values.
    mTechInfo->setDouble( "subregional-population", subregionalPopulation );
    mTechInfo->setDouble( "subregional-income", subregionalIncome );
    mTechInfo->setDouble( "subregional-income-ppp", subregionalIncomePPP );
    Consumer::initCalc( aRegionName, aSectorName, aNationalAccount,
                        aDemographics, aCapitalStock, aPeriod );
}

void GCAMConsumer::operate( NationalAccount& aNationalAccount, const Demographic* aDemographics,
                            const string& aRegionName,
                            const string& aSectorName, const bool aIsNewVintageMode, int aPeriod )
{
    expenditures[ aPeriod ].reset();

    // In calibration periods we will back out coefficients to reproduce the read in base year
    // values in the nested input structure.
    const Modeltime* modeltime = scenario->getModeltime();
    const bool calibrationPeriod = aPeriod > 0 && aPeriod <= modeltime->getFinalCalibrationPeriod();
    if( calibrationPeriod ) {
        mNestedInputRoot->calcCoefficient( aRegionName, aSectorName, aPeriod );
    }

    // Set prices for all leafs and node inputs.
    BaseTechnology::calcPricePaid( aRegionName, aSectorName, aPeriod,
                                   modeltime->gettimestep( aPeriod ) );
    mNestedInputRoot->calcLevelizedCost( aRegionName, aSectorName, aPeriod,
                                         mNestedInputRoot->getCoefficient( aPeriod ) ); // alpha zero is the root's alpha

    // Drive input demands.
    calcInputDemand( 0, aRegionName, aSectorName, aPeriod );

    // Reset speed optimization flags.
    mPricePaidCached = false;
    mNestedInputRoot->resetCalcLevelizedCostFlag();
}

void GCAMConsumer::postCalc( const string& aRegionName, const string& aSectorName, const int aPeriod ) {
    // nothing to do
}

void GCAMConsumer::accept( IVisitor* aVisitor, const int aPeriod ) const {
    aVisitor->startVisitGCAMConsumer( this, aPeriod );

    BaseTechnology::accept( aVisitor, aPeriod );

    aVisitor->endVisitGCAMConsumer( this, aPeriod );
}

/*!
 * \brief Initialize any TechVintageVector in any object that may be contained
 *        in this class.
 * \details GCAMConsumer does not have vintaging so the vectors will be sized for
 *          the entire model time.
 */


idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::idMenuDataSource_ControlSettings() {
	fields.SetNum( MAX_CONTROL_FIELDS );
	originalFields.SetNum( MAX_CONTROL_FIELDS );
}

/*
===========================================================================

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/
#pragma hdrstop
#include "../../idLib/precompiled.h"
#include "../Game_local.h"

const static int NUM_CONTROLS_OPTIONS = 8;

enum contorlsMenuCmds_t {
	CONTROLS_CMD_BINDINGS,
	CONTROLS_CMD_GAMEPAD,
	CONTROLS_CMD_GAMEPAD_ENABLED,
	CONTROLS_CMD_INVERT,
	CONTROLS_CMD_MOUSE_SENS
};

/*
========================
idMenuScreen_Shell_Controls::Initialize
========================
*/
void idMenuScreen_Shell_Controls::Initialize( idMenuHandler * data ) {
	idMenuScreen::Initialize( data );

	if ( data != NULL ) {
		menuGUI = data->GetGUI();
	}

	SetSpritePath( "menuControls" );

	options = new (TAG_SWF) idMenuWidget_DynamicList();
	options->SetNumVisibleOptions( NUM_CONTROLS_OPTIONS );
	options->SetSpritePath( GetSpritePath(), "info", "options" );
	options->SetWrappingAllowed( true );
	options->SetControlList( true );
	options->Initialize( data );
	AddChild( options );

	idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help();
	helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" );
	AddChild( helpWidget );

	btnBack = new (TAG_SWF) idMenuWidget_Button();
	btnBack->Initialize( data );
	btnBack->SetLabel( "#str_swf_settings" );
	btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" );
	btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK );
	AddChild( btnBack );

	idMenuWidget_ControlButton * control;
	control = new (TAG_SWF) idMenuWidget_ControlButton();
	control->SetOptionType( OPTION_BUTTON_TEXT );
	control->SetLabel( "#str_swf_keyboard" );	// KEY BINDINGS
	control->SetDescription( "#str_swf_binding_desc" );
	control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_BINDINGS );
	control->RegisterEventObserver( helpWidget );
	options->AddChild( control );

	control = new (TAG_SWF) idMenuWidget_ControlButton();
	control->SetOptionType( OPTION_BUTTON_TEXT );
	control->SetLabel( "#str_swf_gamepad" );	// Gamepad
	control->SetDescription( "#str_swf_gamepad_desc" );
	control->RegisterEventObserver( helpWidget );
	control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_GAMEPAD );
	options->AddChild( control );

	control = new (TAG_SWF) idMenuWidget_ControlButton();
	control->SetOptionType( OPTION_SLIDER_TOGGLE );
	control->SetLabel( "#str_swf_gamepad_enabled" );	// Gamepad Enabled
	control->SetDataSource( &controlData, idMenuDataSource_ControlSettings::CONTROLS_FIELD_GAMEPAD_ENABLED );
	control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() );
	control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_GAMEPAD_ENABLED );
	control->RegisterEventObserver( helpWidget );
	options->AddChild( control );

	control = new (TAG_SWF) idMenuWidget_ControlButton();
	control->SetOptionType( OPTION_SLIDER_TOGGLE );
	control->SetLabel( "#str_swf_invert_mouse" );	// Invert Mouse
	control->SetDataSource( &controlData, idMenuDataSource_ControlSettings::CONTROLS_FIELD_INVERT_MOUSE );
	control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() );
	control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_INVERT );
	control->RegisterEventObserver( helpWidget );
	options->AddChild( control );

	control = new (TAG_SWF) idMenuWidget_ControlButton();
	control->SetOptionType( OPTION_SLIDER_BAR );
	control->SetLabel( "#str_swf_mouse_sens" );	// Mouse Sensitivity
	control->SetDataSource( &controlData, idMenuDataSource_ControlSettings::CONTROLS_FIELD_MOUSE_SENS );
	control->SetupEvents( 2, options->GetChildren().Num() );
	control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_MOUSE_SENS );
	control->RegisterEventObserver( helpWidget );
	options->AddChild( control );

	options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) );
	options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) );
}

/*
========================
idMenuScreen_Shell_Controls::Update
========================
*/
void idMenuScreen_Shell_Controls::Update() {

	if ( menuData != NULL ) {
		idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar();
		if ( cmdBar != NULL ) {
			cmdBar->ClearAllButtons();
			idMenuWidget_CommandBar::buttonInfo_t * buttonInfo;
			buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 );
			if ( menuData->GetPlatform() != 2 ) {
				buttonInfo->label = "#str_00395";
			}
			buttonInfo->action.Set( WIDGET_ACTION_GO_BACK );

			buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 );
			if ( menuData->GetPlatform() != 2 ) {
				buttonInfo->label = "#str_SWF_SELECT";
			}
			buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED );
		}
	}

	idSWFScriptObject & root = GetSWFObject()->GetRootObject();
	if ( BindSprite( root ) ) {
		idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" );
		if ( heading != NULL ) {
			idStr controls( idLocalization::GetString( "#str_04158" ) );
			controls.ToUpper();
			heading->SetText( controls );	// CONTROLS
			heading->SetStrokeInfo( true, 0.75f, 1.75f );
		}

		idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" );
		if ( gradient != NULL && heading != NULL ) {
			gradient->SetXPos( heading->GetTextLength() );
		}
	}

	if ( btnBack != NULL ) {
		btnBack->BindSprite( root );
	}

	idMenuScreen::Update();
}

/*
========================
idMenuScreen_Shell_Controls::ShowScreen
========================
*/
void idMenuScreen_Shell_Controls::ShowScreen( const mainMenuTransition_t transitionType ) {
	controlData.LoadData();
	idMenuScreen::ShowScreen( transitionType );
}

/*
========================
idMenuScreen_Shell_Controls::HideScreen
========================
*/
void idMenuScreen_Shell_Controls::HideScreen( const mainMenuTransition_t transitionType ) {

	if ( controlData.IsDataChanged() ) {
		controlData.CommitData();
	}

	if ( menuData != NULL ) {
		idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * >( menuData );
		if ( handler != NULL ) {
			handler->SetupPCOptions();
		}
	}

	idMenuScreen::HideScreen( transitionType );
}

/*
========================
idMenuScreen_Shell_Controls::HandleAction
========================
*/
bool idMenuScreen_Shell_Controls::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) {

	if ( menuData == NULL ) {
		return true;
	}

	if ( menuData->ActiveScreen() != SHELL_AREA_CONTROLS ) {
		return false;
	}

	widgetAction_t actionType = action.GetType();
	const idSWFParmList & parms = action.GetParms();

	switch ( actionType ) {
		case WIDGET_ACTION_GO_BACK: {
			menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE );
			return true;
		}

		case WIDGET_ACTION_COMMAND: {

			if ( options == NULL ) {
				return true;
			}

			int selectionIndex = options->GetFocusIndex();
			if ( parms.Num() > 0 ) {
				selectionIndex = parms[0].ToInteger();
			}

			if ( selectionIndex != options->GetFocusIndex() ) {
				options->SetViewIndex( options->GetViewOffset() + selectionIndex );
				options->SetFocusIndex( selectionIndex );
			}

			switch ( parms[0].ToInteger() ) {
				case CONTROLS_CMD_BINDINGS: {
					menuData->SetNextScreen( SHELL_AREA_KEYBOARD, MENU_TRANSITION_SIMPLE );
					break;
				}
				case CONTROLS_CMD_GAMEPAD: {
					menuData->SetNextScreen( SHELL_AREA_GAMEPAD, MENU_TRANSITION_SIMPLE );
					break;
				}
				case CONTROLS_CMD_INVERT: {
					controlData.AdjustField( idMenuDataSource_ControlSettings::CONTROLS_FIELD_INVERT_MOUSE, 1 );
					if ( options != NULL ) {
						options->Update();
					}
					break;
				}
				case CONTROLS_CMD_MOUSE_SENS: {
					controlData.AdjustField( idMenuDataSource_ControlSettings::CONTROLS_FIELD_MOUSE_SENS, 1 );
					if ( options != NULL ) {
						options->Update();
					}
					break;
				}
				case CONTROLS_CMD_GAMEPAD_ENABLED: {
					controlData.AdjustField( idMenuDataSource_ControlSettings::CONTROLS_FIELD_GAMEPAD_ENABLED, 1 );
					if ( options != NULL ) {
						options->Update();
					}
					break;
				}
			}

			return true;
		}
		case WIDGET_ACTION_START_REPEATER: {

			if ( options == NULL ) {
				return true;
			}

			if ( parms.Num() == 4 ) {
				int selectionIndex = parms[3].ToInteger();
				if ( selectionIndex != options->GetFocusIndex() ) {
					options->SetViewIndex( options->GetViewOffset() + selectionIndex );
					options->SetFocusIndex( selectionIndex );
				}
			}
			break;
		}
	}

	return idMenuWidget::HandleAction( action, event, widget, forceHandled );
}

// SCREEN SETTINGS

extern idCVar in_mouseInvertLook;
extern idCVar in_mouseSpeed;
extern idCVar in_useJoystick;

/*
========================
idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::idMenuDataSource_AudioSettings
========================
*/

/*
========================
idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::LoadData
========================
*/
void idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::LoadData() {
	fields[ CONTROLS_FIELD_INVERT_MOUSE ].SetBool( in_mouseInvertLook.GetBool() );
	float mouseSpeed = ( ( in_mouseSpeed.GetFloat() - 0.25f ) / ( 4.0f - 0.25 ) ) * 100.0f;
	fields[ CONTROLS_FIELD_MOUSE_SENS ].SetFloat( mouseSpeed );
	fields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].SetBool( in_useJoystick.GetBool() );

	originalFields = fields;
}

/*
========================
idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::CommitData
========================
*/
void idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::CommitData() {

	in_mouseInvertLook.SetBool( fields[ CONTROLS_FIELD_INVERT_MOUSE ].ToBool() );
	float mouseSpeed = 0.25f + ( ( 4.0f - 0.25 ) * ( fields[ CONTROLS_FIELD_MOUSE_SENS ].ToFloat() / 100.0f ) );
	in_mouseSpeed.SetFloat( mouseSpeed );
	in_useJoystick.SetBool( fields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].ToBool() );

	cvarSystem->SetModifiedFlags( CVAR_ARCHIVE );

	// make the committed fields into the backup fields
	originalFields = fields;
}

/*
========================
idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::AdjustField
========================
*/
void idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::AdjustField( const int fieldIndex, const int adjustAmount ) {
	if ( fieldIndex == CONTROLS_FIELD_INVERT_MOUSE || fieldIndex == CONTROLS_FIELD_GAMEPAD_ENABLED ) {
		fields[ fieldIndex ].SetBool( !fields[ fieldIndex ].ToBool() );
	} else if ( fieldIndex == CONTROLS_FIELD_MOUSE_SENS ) {
		float newValue = idMath::ClampFloat( 0.0f, 100.0f, fields[ fieldIndex ].ToFloat() + adjustAmount );
		fields[ fieldIndex ].SetFloat( newValue );
	}
}

/*
========================
idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::IsDataChanged
========================
*/
bool idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::IsDataChanged() const {

	if ( fields[ CONTROLS_FIELD_INVERT_MOUSE ].ToBool() != originalFields[ CONTROLS_FIELD_INVERT_MOUSE ].ToBool() ) {
		return true;
	}

	if ( fields[ CONTROLS_FIELD_MOUSE_SENS ].ToFloat() != originalFields[ CONTROLS_FIELD_MOUSE_SENS ].ToFloat() ) {
		return true;
	}

	if ( fields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].ToFloat() != originalFields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].ToFloat() ) {
		return true;
	}

	return false;
}


SkillDiscoveryEntry()
        : spellId(0), reqSkillValue(0), chance(0) { }

/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 */

#include "SkillDiscovery.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
#include "Log.h"
#include "Player.h"
#include "Random.h"
#include "SpellMgr.h"
#include "SpellInfo.h"
#include "Util.h"
#include "World.h"
#include <map>
#include <sstream>

struct SkillDiscoveryEntry
{
    uint32  spellId;                                        // discavered spell
    uint32  reqSkillValue;                                  // skill level limitation
    float   chance;                                         // chance

    SkillDiscoveryEntry(uint32 _spellId, uint32 req_skill_val, float _chance)
        : spellId(_spellId), reqSkillValue(req_skill_val), chance(_chance) { }
};

typedef std::list<SkillDiscoveryEntry> SkillDiscoveryList;
typedef std::unordered_map<int32, SkillDiscoveryList> SkillDiscoveryMap;

static SkillDiscoveryMap SkillDiscoveryStore;

void LoadSkillDiscoveryTable()
{
    uint32 oldMSTime = getMSTime();

    SkillDiscoveryStore.clear();                            // need for reload

    QueryResult result = WorldDatabase.Query("SELECT spellId, reqSpell, reqSkillValue, chance FROM skill_discovery_template");

    if (!result)
    {
        TC_LOG_INFO("server.loading", ">> Loaded 0 skill discovery definitions. DB table `skill_discovery_template` is empty.");
        return;
    }

    uint32 count = 0;

    std::ostringstream ssNonDiscoverableEntries;
    std::set<uint32> reportedReqSpells;

    do
    {
        Field* fields = result->Fetch();

        uint32 spellId         = fields[0].GetUInt32();
        int32  reqSkillOrSpell = fields[1].GetInt32();
        uint32 reqSkillValue   = fields[2].GetUInt16();
        float  chance          = fields[3].GetFloat();

        if (chance <= 0)                                    // chance
        {
            ssNonDiscoverableEntries << "spellId = " << spellId << " reqSkillOrSpell = " << reqSkillOrSpell
                << " reqSkillValue = " << reqSkillValue << " chance = " << chance << "(chance problem)\n";
            continue;
        }

        if (reqSkillOrSpell > 0)                            // spell case
        {
            uint32 absReqSkillOrSpell = uint32(reqSkillOrSpell);
            SpellInfo const* reqSpellInfo = sSpellMgr->GetSpellInfo(absReqSkillOrSpell, DIFFICULTY_NONE);
            if (!reqSpellInfo)
            {
                if (reportedReqSpells.find(absReqSkillOrSpell) == reportedReqSpells.end())
                {
                    TC_LOG_ERROR("sql.sql", "Spell (ID: %u) has a non-existing spell (ID: %i) in `reqSpell` field in the `skill_discovery_template` table.", spellId, reqSkillOrSpell);
                    reportedReqSpells.insert(absReqSkillOrSpell);
                }
                continue;
            }

            // mechanic discovery
            if (reqSpellInfo->Mechanic != MECHANIC_DISCOVERY &&
                // explicit discovery ability
                !reqSpellInfo->IsExplicitDiscovery())
            {
                if (reportedReqSpells.find(absReqSkillOrSpell) == reportedReqSpells.end())
                {
                    TC_LOG_ERROR("sql.sql", "Spell (ID: %u) does not have any MECHANIC_DISCOVERY (28) value in the Mechanic field in spell.dbc"
                        " nor 100%% chance random discovery ability, but is listed for spellId %u (and maybe more) in the `skill_discovery_template` table.",
                        absReqSkillOrSpell, spellId);
                    reportedReqSpells.insert(absReqSkillOrSpell);
                }
                continue;
            }

            SkillDiscoveryStore[reqSkillOrSpell].push_back(SkillDiscoveryEntry(spellId, reqSkillValue, chance));
        }
        else if (reqSkillOrSpell == 0)                      // skill case
        {
            SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);

            if (bounds.first == bounds.second)
            {
                TC_LOG_ERROR("sql.sql", "Spell (ID: %u) is not listed in `SkillLineAbility.dbc`, but listed with `reqSpell`= 0 in the `skill_discovery_template` table.", spellId);
                continue;
            }

            for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
                SkillDiscoveryStore[-int32(_spell_idx->second->SkillLine)].push_back(SkillDiscoveryEntry(spellId, reqSkillValue, chance));
        }
        else
        {
            TC_LOG_ERROR("sql.sql", "Spell (ID: %u) has a negative value in `reqSpell` field in the `skill_discovery_template` table.", spellId);
            continue;
        }

        ++count;
    }
    while (result->NextRow());

    if (!ssNonDiscoverableEntries.str().empty())
        TC_LOG_ERROR("sql.sql", "Some items can't be successfully discovered, their chance field value is < 0.000001 in the `skill_discovery_template` DB table. List:\n%s", ssNonDiscoverableEntries.str().c_str());

    // report about empty data for explicit discovery spells
    for (SpellNameEntry const* spellNameEntry : sSpellNameStore)
    {
        SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spellNameEntry->ID, DIFFICULTY_NONE);
        if (!spellEntry)
            continue;

        // skip not explicit discovery spells
        if (!spellEntry->IsExplicitDiscovery())
            continue;

        if (SkillDiscoveryStore.find(int32(spellEntry->Id)) == SkillDiscoveryStore.end())
            TC_LOG_ERROR("sql.sql", "Spell (ID: %u) has got 100%% chance random discovery ability, but does not have data in the `skill_discovery_template` table.", spellEntry->Id);
    }

    TC_LOG_INFO("server.loading", ">> Loaded %u skill discovery definitions in %u ms.", count, GetMSTimeDiffToNow(oldMSTime));
}

uint32 GetExplicitDiscoverySpell(uint32 spellId, Player* player)
{
    // explicit discovery spell chances (always success if case exist)
    // in this case we have both skill and spell
    SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));
    if (tab == SkillDiscoveryStore.end())
        return 0;

    SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
    uint32 skillvalue = bounds.first != bounds.second ? player->GetSkillValue(bounds.first->second->SkillLine) : uint32(0);

    float full_chance = 0;
    for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
        if (item_iter->reqSkillValue <= skillvalue)
            if (!player->HasSpell(item_iter->spellId))
                full_chance += item_iter->chance;

    float rate = full_chance / 100.0f;
    float roll = (float)rand_chance() * rate;                      // roll now in range 0..full_chance

    for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
    {
        if (item_iter->reqSkillValue > skillvalue)
            continue;

        if (player->HasSpell(item_iter->spellId))
            continue;

        if (item_iter->chance > roll)
            return item_iter->spellId;

        roll -= item_iter->chance;
    }

    return 0;
}

bool HasDiscoveredAllSpells(uint32 spellId, Player* player)
{
    SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));
    if (tab == SkillDiscoveryStore.end())
        return true;

    for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
        if (!player->HasSpell(item_iter->spellId))
            return false;

    return true;
}

uint32 GetSkillDiscoverySpell(uint32 skillId, uint32 spellId, Player* player)
{
    uint32 skillvalue = skillId ? player->GetSkillValue(skillId) : uint32(0);

    // check spell case
    SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));

    if (tab != SkillDiscoveryStore.end())
    {
        for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
        {
            if (roll_chance_f(item_iter->chance * sWorld->getRate(RATE_SKILL_DISCOVERY)) &&
                item_iter->reqSkillValue <= skillvalue &&
                !player->HasSpell(item_iter->spellId))
                return item_iter->spellId;
        }

        return 0;
    }

    if (!skillId)
        return 0;

    // check skill line case
    tab = SkillDiscoveryStore.find(-(int32)skillId);
    if (tab != SkillDiscoveryStore.end())
    {
        for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
        {
            if (roll_chance_f(item_iter->chance * sWorld->getRate(RATE_SKILL_DISCOVERY)) &&
                item_iter->reqSkillValue <= skillvalue &&
                !player->HasSpell(item_iter->spellId))
                return item_iter->spellId;
        }

        return 0;
    }

    return 0;
}