/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.seatunnel.transform.colmerge;

import com.google.auto.service.AutoService;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.seatunnel.api.configuration.ReadonlyConfig;
import org.apache.seatunnel.api.configuration.util.ConfigValidator;
import org.apache.seatunnel.api.table.catalog.*;
import org.apache.seatunnel.api.table.type.*;
import org.apache.seatunnel.api.transform.SeaTunnelTransform;
import org.apache.seatunnel.shade.com.typesafe.config.Config;
import org.apache.seatunnel.transform.common.MultipleFieldOutputTransform;
import org.apache.seatunnel.transform.common.SeaTunnelRowAccessor;

import java.lang.reflect.Array;
import java.util.*;

@AutoService(SeaTunnelTransform.class)
@NoArgsConstructor
public class ColMergeTransform extends MultipleFieldOutputTransform {
    public static String PLUGIN_NAME = "ColMerge";
    private ColMergeTransformConfig config;

    private List<String> fieldNames;
    private List<String> fieldOriginalIndexs= new ArrayList<>();
    private List<SeaTunnelDataType> fieldTypes;

    public ColMergeTransform(
            @NonNull ColMergeTransformConfig config, @NonNull CatalogTable catalogTable) {
        super(catalogTable);
        this.config = config;
    }

    @Override
    public String getPluginName() {
        return PLUGIN_NAME;
    }

    @Override
    protected void setConfig(Config pluginConfig) {
        ConfigValidator.of(ReadonlyConfig.fromConfig(pluginConfig))
                .validate(new ColMergeTransformFactory().optionRule());
        config = ColMergeTransformConfig.of(ReadonlyConfig.fromConfig(pluginConfig));
    }

    @Override
    protected void setInputRowType(SeaTunnelRowType inputRowType) {
    }



    @Override
    protected String[] getOutputFieldNames() {
        return fieldNames.toArray(new String[0]);
    }

    @Override
    protected SeaTunnelDataType[] getOutputFieldDataTypes() {
        return fieldTypes.toArray(new SeaTunnelDataType[0]);
    }

    @Override
    protected Column[] getOutputColumns() {
        Column[] columns = new Column[fieldNames.size()];
        for (int i = 0; i < fieldNames.size(); i++) {
            columns[i] =
                    PhysicalColumn.of(fieldNames.get(i), fieldTypes.get(i), 200, true, "", "");
        }
        return columns;
    }


    @Override
    protected SeaTunnelRowType transformRowType(SeaTunnelRowType inputRowType) {
        List<String> fieldNames = new ArrayList<>();
        List<SeaTunnelDataType> fieldsType = new ArrayList<>();
        List<String> rowFieldNames= Arrays.asList(inputRowType.getFieldNames());
        List<SeaTunnelDataType<?>> seaTunnelDataTypes = Arrays.asList(inputRowType.getFieldTypes());
        String destField = config.getDestField();
        List<String> fields = config.getFields();
        boolean retain = config.isRetain();
        StringBuilder sb =new StringBuilder();
        rowFieldNames.forEach(x->{
            if (retain){
                fieldNames.add(x);
                fieldsType.add(seaTunnelDataTypes.get(inputRowType.indexOf(x)));
                fieldOriginalIndexs.add(inputRowType.indexOf(x)+"");


//                if (fields.contains(x)){
//                    sb.append(inputRowType.indexOf(x)).append(",");
//                }
            }else {
                if (!fields.contains(x)){
                    fieldNames.add(x);
                    fieldsType.add(seaTunnelDataTypes.get(inputRowType.indexOf(x)));
                    fieldOriginalIndexs.add(inputRowType.indexOf(x)+"");
                }
            }

        });
        fields.forEach(f->{
            int index = rowFieldNames.indexOf(f);
            sb.append(index).append(",");

        });

        fieldOriginalIndexs.add(sb.deleteCharAt(sb.length() - 1).toString());
        fieldNames.add(destField);
        fieldsType.add(BasicType.STRING_TYPE);
        SeaTunnelRowType outputRowType = new SeaTunnelRowType(fieldNames.toArray(new String[0]), fieldsType.toArray(new SeaTunnelDataType[0]));
        this.fieldNames = fieldNames;
        this.fieldTypes = fieldsType;
        return outputRowType;
    }


    @Override
    protected SeaTunnelRow transformRow(SeaTunnelRow inputRow) {
        Object[] fieldValues = new Object[fieldNames.size()];
        String separator = config.getSeparator();
        for (int i = 0; i < fieldOriginalIndexs.size(); i++) {
            if (!fieldOriginalIndexs.get(i).contains(",")){
                fieldValues[i] = clone(fieldTypes.get(i), inputRow.getField(Integer.parseInt(fieldOriginalIndexs.get(i))));
            }else {
                List<String> indexList = Arrays.asList(fieldOriginalIndexs.get(i).split(","));
                StringBuilder sb =new StringBuilder();
                indexList.forEach(x->{
                    if (separator!=null){
                        if (StringUtils.isBlank(separator)){
                            sb.append(inputRow.getField(Integer.parseInt(x))).append(" ");
                        }else {
                            sb.append(inputRow.getField(Integer.parseInt(x))).append(separator);
                        }
                    }else {
                            sb.append(inputRow.getField(Integer.parseInt(x)));

                    }

                });
                if (separator!=null){
                    fieldValues[i]=sb.deleteCharAt(sb.length() - 1).toString();
                }else {
                    fieldValues[i]=sb.toString();
                }

            }
        }
        return new SeaTunnelRow(fieldValues);
    }


    @Override
    protected Object[] getOutputFieldValues(SeaTunnelRowAccessor inputRow) {
        return null;
    }

    private Object clone(SeaTunnelDataType dataType, Object value) {
        if (value == null) {
            return null;
        }
        switch (dataType.getSqlType()) {
            case BOOLEAN:
            case STRING:
            case TINYINT:
            case SMALLINT:
            case INT:
            case BIGINT:
            case FLOAT:
            case DOUBLE:
            case DECIMAL:
            case DATE:
            case TIME:
            case TIMESTAMP:
                return value;
            case BYTES:
                byte[] bytes = (byte[]) value;
                if (bytes == null) {
                    return null;
                }
                byte[] newBytes = new byte[bytes.length];
                System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
                return newBytes;
            case ARRAY:
                ArrayType arrayType = (ArrayType) dataType;
                Object[] array = (Object[]) value;
                if (array == null) {
                    return null;
                }
                Object newArray =
                        Array.newInstance(arrayType.getElementType().getTypeClass(), array.length);
                for (int i = 0; i < array.length; i++) {
                    Array.set(newArray, i, clone(arrayType.getElementType(), array[i]));
                }
                return newArray;
            case MAP:
                MapType mapType = (MapType) dataType;
                Map map = (Map) value;
                Map newMap = new HashMap();
                for (Object key : map.keySet()) {
                    newMap.put(
                            clone(mapType.getKeyType(), key),
                            clone(mapType.getValueType(), map.get(key)));
                }
                return newMap;
            case ROW:
                SeaTunnelRowType rowType = (SeaTunnelRowType) dataType;
                SeaTunnelRow row = (SeaTunnelRow) value;
                if (row == null) {
                    return null;
                }

                Object[] newFields = new Object[rowType.getTotalFields()];
                for (int i = 0; i < rowType.getTotalFields(); i++) {
                    newFields[i] = clone(rowType.getFieldType(i), row.getField(i));
                }
                SeaTunnelRow newRow = new SeaTunnelRow(newFields);
                newRow.setRowKind(row.getRowKind());
                newRow.setTableId(row.getTableId());
                return newRow;
            case NULL:
                return null;
            default:
                throw new UnsupportedOperationException(
                        "Unsupported type: " + dataType.getSqlType());
        }
    }
}
