package me.chyxion.tigon.model;

import java.util.*;
import java.lang.reflect.Field;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.util.WordUtils;
import me.chyxion.tigon.mybatis.PrimaryKey;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;

/**
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Feb 21, 2017 18:39:39
 */
@Slf4j
public abstract class BaseModel<PK> extends MappableSupport implements Mappable {
    private static final long serialVersionUID = 1L;

    // id
    public static final String ID = "id";

    /**
     * get primary key
     * @return primary key name
     */
    public String primaryKeyName() {
        final String[] annoPk = new String[1];
        final String[] id = new String[1];
        ReflectionUtils.doWithFields(getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                final String fieldName = field.getName();
                if (id[0] == null && ID.equals(fieldName)) {
                    id[0] = ID;
                }
                if (annoPk[0] == null &&
                    field.isAnnotationPresent(PrimaryKey.class)) {
                    annoPk[0] = fieldName;
                }
            }
        }, fieldFilter(false));
        String primaryKey = annoPk[0] != null ? annoPk[0] : id[0];
        if (primaryKey == null) {
            throw new IllegalStateException(
                "No Primary Key Found For Model [" + getClass() + "]");
        }
        return primaryKey;
    }

    /**
     * get primary key value
     * @return primary key value
     */
    @SuppressWarnings("unchecked")
    public PK primaryKeyValue() {
        return (PK) ReflectionUtils.getField(
            ReflectionUtils.findField(getClass(), primaryKeyName()), this);
    }

    /**
     * set primary key
     * @param value primary key
     * @return previous primary key value
     */
    public void primaryKeyValue(PK value) {
        ReflectionUtils.setField(
            ReflectionUtils.findField(getClass(),
                primaryKeyName()), this, value);
    }

    /**
     * return update map
     * @return update map
     */
    public Map<String, Object> updateMap() {
        Map<String, Object> mapModel = toMap(true);
        Map<String, Object> mapRtn =
            new HashMap<String, Object>(mapModel.size());
        for (Map.Entry<String, Object> kv : mapModel.entrySet()) {
            mapRtn.put(fieldToCol(kv.getKey()), kv.getValue());
        }
        return mapRtn;
    }

    /**
     * return model cols
     * @return cols model cols
     */
    public Collection<String> cols() {
        return cols(null);
    }

    /**
     * return model cols
     * @param table table
     * @return cols model cols
     */
    public Collection<String> cols(final String table) {
        Map<String, Object> mapModel = toMap();
        List<String> cols = new ArrayList<String>(mapModel.size());
        for (String field : mapModel.keySet()) {
            final String col = fieldToCol(field);
            cols.add(StringUtils.isNotBlank(table) ? table + "." + col : col);
        }
        return cols;
    }

    /**
     * return model values
     * @return values model values
     */
    public Collection<Object> values() {
        Map<String, Object> mapRaw = toMap();
        List<Object> values = new ArrayList<Object>(mapRaw.size());
        for (String col : mapRaw.keySet()) {
            values.add(mapRaw.get(col));
        }
        return values;
    }

    /**
     * before insert model, for override
     */
    public void beforeInsert() {
        log.debug("Model [{}] Before Insert.", this);
    }

    /**
     * before update model, for override
     */
    public void beforeUpdate() {
        log.debug("Model [{}] Before Update.", this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

    // --
    // protected methods

    protected String fieldToCol(String field) {
        return WordUtils.convertCamelCase(field, "_").toLowerCase();
    }
}
