/*
 * Copyright (c) 2015-2016 Adrian Siekierka
 *
 * Licensed 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 pl.asie.charset.lib.render;

import com.google.common.collect.ImmutableList;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ModelRotation;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.property.IExtendedBlockState;
import net.minecraftforge.common.property.IUnlistedProperty;
import org.lwjgl.util.vector.Vector3f;
import pl.asie.charset.lib.misc.IConnectable;
import pl.asie.charset.lib.utils.RenderUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public abstract class ModelPipeLike<T extends IConnectable> extends BaseBakedModel {
    private static final EnumFacing[][] CONNECTION_DIRS = new EnumFacing[][]{
            {EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.EAST},
            {EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.WEST, EnumFacing.EAST},
            {EnumFacing.UP, EnumFacing.DOWN, EnumFacing.WEST, EnumFacing.EAST},
            {EnumFacing.UP, EnumFacing.DOWN, EnumFacing.EAST, EnumFacing.WEST},
            {EnumFacing.UP, EnumFacing.DOWN, EnumFacing.SOUTH, EnumFacing.NORTH},
            {EnumFacing.UP, EnumFacing.DOWN, EnumFacing.NORTH, EnumFacing.SOUTH}
    };

    private final ModelRotation[] ROTATIONS = new ModelRotation[]{
            ModelRotation.X0_Y0,
            ModelRotation.X180_Y0,
            ModelRotation.X270_Y0,
            ModelRotation.X270_Y180,
            ModelRotation.X270_Y270,
            ModelRotation.X270_Y90
    };

    private final List<BakedQuad>[] lists = new List[65];
    private final IUnlistedProperty<T> property;

    public ModelPipeLike(IUnlistedProperty<T> property) {
        this.property = property;

        for (int i = 0; i < 64; i++) {
            boolean[] connections = new boolean[6];
            for (int j = 0; j < 6; j++) {
                if ((i & (1 << j)) != 0) {
                    connections[5 - j] = true;
                }
            }

            lists[i] = ImmutableList.copyOf(generateQuads(connections));
        }

        lists[64] = lists[48];
        addDefaultBlockTransforms();
    }

    public int getOutsideColor(EnumFacing facing) {
        return -1;
    }

    public int getInsideColor(EnumFacing facing) {
        return -1;
    }

    public abstract float getThickness();
    public abstract boolean isOpaque();
    public abstract TextureAtlasSprite getTexture(EnumFacing side, int connectionMatrix);

    protected List<BakedQuad> generateQuads(boolean[] connections) {
        List<BakedQuad> quads = new ArrayList<>();
        Vector3f from, to;

        for (EnumFacing facing : EnumFacing.VALUES) {
            EnumFacing[] neighbors = CONNECTION_DIRS[facing.ordinal()];
            int connectionMatrix = (connections[neighbors[0].ordinal()] ? 8 : 0) | (connections[neighbors[1].ordinal()] ? 4 : 0)
                    | (connections[neighbors[2].ordinal()] ? 2 : 0) | (connections[neighbors[3].ordinal()] ? 1 : 0);
            TextureAtlasSprite sprite = getTexture(facing, connectionMatrix);
            float min = 8 - (getThickness() / 2);
            float max = 8 + (getThickness() / 2);
            int outsideColor = getOutsideColor(facing);
            int insideColor = getInsideColor(facing);
            if (!isOpaque() && connections[facing.ordinal()]) {
                // Connected; render up to four quads.
                if (connections[neighbors[2].ordinal()]) {
                    from = new Vector3f(0, min, min);
                    to = new Vector3f(min, min, max);
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, outsideColor, sprite, EnumFacing.DOWN, ROTATIONS[facing.ordinal()], true));
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, insideColor, sprite, EnumFacing.UP, ROTATIONS[facing.ordinal()], true));
                }

                if (connections[neighbors[0].ordinal()]) {
                    from = new Vector3f(min, min, 0);
                    to = new Vector3f(max, min, min);
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, outsideColor, sprite, EnumFacing.DOWN, ROTATIONS[facing.ordinal()], true));
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, insideColor, sprite, EnumFacing.UP, ROTATIONS[facing.ordinal()], true));
                }

                if (connections[neighbors[3].ordinal()]) {
                    from = new Vector3f(max, min, min);
                    to = new Vector3f(16, min, max);
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, outsideColor, sprite, EnumFacing.DOWN, ROTATIONS[facing.ordinal()], true));
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, insideColor, sprite, EnumFacing.UP, ROTATIONS[facing.ordinal()], true));
                }

                if (connections[neighbors[1].ordinal()]) {
                    from = new Vector3f(min, min, max);
                    to = new Vector3f(max, min, 16);
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, outsideColor, sprite, EnumFacing.DOWN, ROTATIONS[facing.ordinal()], true));
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, insideColor, sprite, EnumFacing.UP, ROTATIONS[facing.ordinal()], true));
                }
            } else {
                // Not connected; render one quad.
                from = new Vector3f(connections[neighbors[2].ordinal()] ? 0 : min, min, connections[neighbors[0].ordinal()] ? 0 : min);
                to = new Vector3f(connections[neighbors[3].ordinal()] ? 16 : max, min, connections[neighbors[1].ordinal()] ? 16 : max);
                quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, outsideColor, sprite, EnumFacing.DOWN, ROTATIONS[facing.ordinal()], true));
                if (!isOpaque()) {
                    quads.add(RenderUtils.BAKERY.makeBakedQuad(from, to, insideColor, sprite, EnumFacing.UP, ROTATIONS[facing.ordinal()], true));
                }
            }
        }
        return quads;
    }

    @Override
    public List<BakedQuad> getQuads(IBlockState state, EnumFacing side, long rand) {

        if (side != null) {
            return Collections.emptyList();
        }

        T target = null;
        if (state instanceof IExtendedBlockState) {
            target = ((IExtendedBlockState) state).getValue(property);
        }

        if (target == null) {
            return lists[64];
        }

        int pointer = 0;
        for (EnumFacing f : EnumFacing.VALUES) {
            pointer = (pointer << 1) | (target.connects(f) ? 1 : 0);
        }

        return lists[pointer];
    }
}