package com.taobao.tddl.dbsync.binlog.event;

import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;

import com.taobao.tddl.dbsync.binlog.CharsetConversion;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;

/**
 * User_var_log_event. Every time a query uses the value of a user variable, a
 * User_var_log_event is written before the Query_log_event, to set the user
 * variable.
 * 
 * @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
 * @version 1.0
 */
public final class UserVarLogEvent extends LogEvent {

    /**
     * Fixed data part: Empty
     * <p>
     * Variable data part:
     * <ul>
     * <li>4 bytes. the size of the user variable name.</li>
     * <li>The user variable name.</li>
     * <li>1 byte. Non-zero if the variable value is the SQL NULL value, 0
     * otherwise. If this byte is 0, the following parts exist in the event.</li>
     * <li>1 byte. The user variable type. The value corresponds to elements of
     * enum Item_result defined in include/mysql_com.h.</li>
     * <li>4 bytes. The number of the character set for the user variable
     * (needed for a string variable). The character set number is really a
     * collation number that indicates a character set/collation pair.</li>
     * <li>4 bytes. The size of the user variable value (corresponds to member
     * val_len of class Item_string).</li>
     * <li>Variable-sized. For a string variable, this is the string. For a
     * float or integer variable, this is its value in 8 bytes.</li>
     * </ul>
     * Source : http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
     */
    private final String       name;
    private final Serializable value;
    private final int          type;
    private final int          charsetNumber;
    private final boolean      isNull;

    /**
     * The following is for user defined functions
     * 
     * @see mysql-5.1.60//include/mysql_com.h
     */
    public static final int    STRING_RESULT          = 0;
    public static final int    REAL_RESULT            = 1;
    public static final int    INT_RESULT             = 2;
    public static final int    ROW_RESULT             = 3;
    public static final int    DECIMAL_RESULT         = 4;

    /* User_var event data */
    public static final int    UV_VAL_LEN_SIZE        = 4;
    public static final int    UV_VAL_IS_NULL         = 1;
    public static final int    UV_VAL_TYPE_SIZE       = 1;
    public static final int    UV_NAME_LEN_SIZE       = 4;
    public static final int    UV_CHARSET_NUMBER_SIZE = 4;

    public UserVarLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent)
                                                                                                          throws IOException{
        super(header);

        /* The Post-Header is empty. The Variable Data part begins immediately. */
        buffer.position(descriptionEvent.commonHeaderLen + descriptionEvent.postHeaderLen[USER_VAR_EVENT - 1]);
        final int nameLen = (int) buffer.getUint32();
        name = buffer.getFixString(nameLen); // UV_NAME_LEN_SIZE
        isNull = (0 != buffer.getInt8());

        if (isNull) {
            type = STRING_RESULT;
            charsetNumber = 63; /* binary */
            value = null;
        } else {
            type = buffer.getInt8(); // UV_VAL_IS_NULL
            charsetNumber = (int) buffer.getUint32(); // buf + UV_VAL_TYPE_SIZE
            final int valueLen = (int) buffer.getUint32(); // buf +
                                                           // UV_CHARSET_NUMBER_SIZE
            final int limit = buffer.limit(); /* for restore */
            buffer.limit(buffer.position() + valueLen);

            /* @see User_var_log_event::print */
            switch (type) {
                case REAL_RESULT:
                    value = Double.valueOf(buffer.getDouble64()); // float8get
                    break;
                case INT_RESULT:
                    if (valueLen == 8) value = Long.valueOf(buffer.getLong64()); // !uint8korr
                    else if (valueLen == 4) value = Long.valueOf(buffer.getUint32());
                    else throw new IOException("Error INT_RESULT length: " + valueLen);
                    break;
                case DECIMAL_RESULT:
                    final int precision = buffer.getInt8();
                    final int scale = buffer.getInt8();
                    value = buffer.getDecimal(precision, scale); // bin2decimal
                    break;
                case STRING_RESULT:
                    Charset charset = CharsetConversion.getNioCharset(charsetNumber);
                    value = buffer.getFixString(valueLen, charset);
                    break;
                case ROW_RESULT:
                    // this seems to be banned in MySQL altogether
                    throw new IOException("ROW_RESULT is unsupported");
                default:
                    value = null;
                    break;
            }
            buffer.limit(limit);
        }
    }

    public final String getQuery() {
        if (value == null) {
            return "SET @" + name + " := NULL";
        } else if (type == STRING_RESULT) {
            // TODO: do escaping !?
            return "SET @" + name + " := \'" + value + '\'';
        } else {
            return "SET @" + name + " := " + String.valueOf(value);
        }
    }
}
