/*
 * Copyright (c) 2017, Brooss, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.imageio.plugins.webp.vp8;

import java.io.IOException;

final class MacroBlock {

    private int filterLevel;
    private final boolean keepDebugInfo;
    private int segmentId;
    private int skipCoeff;
    private boolean skipInnerLoopFilter;
    final SubBlock[][] uSubBlocks;
    private int uVFilterLevel;

    private int uvMode;
    final SubBlock[][] vSubBlocks;
    private final int x, y;
    final SubBlock y2SubBlock;
    private int yMode;
    final SubBlock[][] ySubBlocks;

    MacroBlock(int x, int y, boolean keepDebugInfo) {
        this.x = x - 1;
        this.y = y - 1;
        this.keepDebugInfo = keepDebugInfo;

        ySubBlocks = new SubBlock[4][4];
        uSubBlocks = new SubBlock[2][2];
        vSubBlocks = new SubBlock[2][2];
        SubBlock above;
        SubBlock left;

        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                left = null;
                above = null;
                if (j > 0) {
                    left = ySubBlocks[j - 1][i];
                }
                if (i > 0) {
                    above = ySubBlocks[j][i - 1];
                }
                ySubBlocks[j][i] = new SubBlock(this, above, left,
                        SubBlock.Plane.Y1);
            }
        }

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                left = null;
                above = null;
                if (j > 0) {
                    left = uSubBlocks[j - 1][i];
                }
                if (i > 0) {
                    above = uSubBlocks[j][i - 1];
                }
                uSubBlocks[j][i] = new SubBlock(this, above, left,
                        SubBlock.Plane.U);
            }
        }

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                left = null;
                above = null;
                if (j > 0) {
                    left = vSubBlocks[j - 1][i];
                }
                if (i > 0) {
                    above = vSubBlocks[j][i - 1];
                }
                vSubBlocks[j][i] = new SubBlock(this, above, left, SubBlock.Plane.V);
            }
        }
        y2SubBlock = new SubBlock(this, null, null, SubBlock.Plane.Y2);
    }

    public void decodeMacroBlock(VP8Frame frame) throws IOException {
        MacroBlock mb = this;
        if (mb.getSkipCoeff() > 0) {
            if (mb.getYMode() != Globals.B_PRED) {
                mb.skipInnerLoopFilter = true;
            }
        }
        else {
            decodeMacroBlockTokens(frame, mb.getYMode() != Globals.B_PRED);
        }
    }

    private void decodeMacroBlockTokens(VP8Frame frame, boolean withY2) throws IOException {
        if (withY2) {
            skipInnerLoopFilter = decodePlaneTokens(frame, 1, SubBlock.Plane.Y2, false);
        }

        skipInnerLoopFilter |= decodePlaneTokens(frame, 4, SubBlock.Plane.Y1, withY2);
        skipInnerLoopFilter |= decodePlaneTokens(frame, 2, SubBlock.Plane.U, false);
        skipInnerLoopFilter |= decodePlaneTokens(frame, 2, SubBlock.Plane.V, false);

        skipInnerLoopFilter = !skipInnerLoopFilter;
    }

    private boolean decodePlaneTokens(VP8Frame frame, int dimentions,
                                      SubBlock.Plane plane, boolean withY2) throws IOException {
        MacroBlock mb = this;
        boolean r = false;

        for (int y = 0; y < dimentions; y++) {
            for (int x = 0; x < dimentions; x++) {
                int L = 0;
                int A = 0;
                int lc = 0;

                SubBlock sb = mb.getSubBlock(plane, x, y);
                SubBlock left = frame.getLeftSubBlock(sb, plane);
                SubBlock above = frame.getAboveSubBlock(sb, plane);

                if (left.hasNoZeroToken()) {
                    L = 1;
                }

                lc += L;

                if (above.hasNoZeroToken()) {
                    A = 1;
                }

                lc += A;
                sb.decodeSubBlock(frame.getTokenBoolDecoder(), frame.getCoefProbs(), lc, SubBlock.planeToType(plane, withY2), withY2);
                r = r | sb.hasNoZeroToken();
            }
        }
        return r;
    }

    public void dequantMacroBlock(VP8Frame frame) {
        MacroBlock mb = this;
        if (mb.getYMode() != Globals.B_PRED) {
            SubBlock sb = mb.getY2SubBlock();
            int acQValue = frame.getSegmentQuants().getSegQuants()[this.getSegmentId()]
                    .getY2ac_delta_q();
            int dcQValue = frame.getSegmentQuants().getSegQuants()[this.getSegmentId()].getY2dc();

            int[] input = new int[16];
            input[0] = sb.getTokens()[0] * dcQValue;

            for (int x = 1; x < 16; x++) {
                input[x] = sb.getTokens()[x] * acQValue;
            }

            sb.setDiff(IDCT.iwalsh4x4(input));
            for (int j = 0; j < 4; j++) {
                for (int i = 0; i < 4; i++) {
                    SubBlock ysb = mb.getYSubBlock(i, j);
                    ysb.dequantSubBlock(frame, sb.getDiff()[i][j]);
                }
            }

            mb.predictY(frame);
            mb.predictUV(frame);
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    SubBlock uvsb = mb.getUSubBlock(j, i);
                    uvsb.dequantSubBlock(frame, null);
                    uvsb = mb.getVSubBlock(i, j);
                    uvsb.dequantSubBlock(frame, null);
                }
            }
            mb.recon_mb();
        }
        else {
            for (int j = 0; j < 4; j++) {
                for (int i = 0; i < 4; i++) {
                    SubBlock sb = mb.getYSubBlock(i, j);
                    sb.dequantSubBlock(frame, null);
                    sb.predict(frame);
                    sb.reconstruct();
                }
            }
            mb.predictUV(frame);
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    SubBlock sb = mb.getUSubBlock(j, i);
                    sb.dequantSubBlock(frame, null);
                    sb.reconstruct();
                }
            }
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    SubBlock sb = mb.getVSubBlock(j, i);
                    sb.dequantSubBlock(frame, null);
                    sb.reconstruct();
                }
            }
        }
    }

    public void drawDebug() {
        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                SubBlock sb = ySubBlocks[i][0];
                sb.drawDebugH();
                sb = ySubBlocks[0][j];
                sb.drawDebugV();
            }
        }
    }

    public String getDebugString() {
        String r = " YMode: " + Globals.getModeAsString(yMode);
        r = r + "\n UVMode: " + Globals.getModeAsString(uvMode);
        r = r + "\n SegmentID: " + segmentId;
        r = r + "\n Filter Level: " + filterLevel;
        r = r + "\n UV Filter Level: " + uVFilterLevel;
        r = r + "\n Skip Coeff: " + skipCoeff;

        return r;
    }

    public int getFilterLevel() {
        return this.filterLevel;
    }

    public SubBlock getBottomSubBlock(int x, SubBlock.Plane plane) {
        switch (plane) {
            case Y1:
                return ySubBlocks[x][3];
            case U:
                return uSubBlocks[x][1];
            case V:
                return vSubBlocks[x][1];
            case Y2:
                return y2SubBlock;
        }

        throw new IllegalArgumentException("Bad plane: " + plane);
    }

    public SubBlock getLeftSubBlock(int y, SubBlock.Plane plane) {
        switch (plane) {
            case Y1:
                return ySubBlocks[0][y];
            case U:
                return uSubBlocks[0][y];
            case V:
                return vSubBlocks[0][y];
            case Y2:
                return y2SubBlock;
        }

        throw new IllegalArgumentException("Bad plane: " + plane);
    }

    public SubBlock getRightSubBlock(int y, SubBlock.Plane plane) {
        switch (plane) {
            case Y1:
                return ySubBlocks[3][y];
            case U:
                return uSubBlocks[1][y];
            case V:
                return vSubBlocks[1][y];
            case Y2:
                return y2SubBlock;
        }

        throw new IllegalArgumentException("Bad plane: " + plane);
    }

    public int getSkipCoeff() {
        return skipCoeff;
    }

    public SubBlock getSubBlock(SubBlock.Plane plane, int i, int j) {
        switch (plane) {
            case Y1:
                return getYSubBlock(i, j);
            case U:
                return getUSubBlock(i, j);
            case V:
                return getVSubBlock(i, j);
            case Y2:
                return getY2SubBlock();
        }

        throw new IllegalArgumentException("Bad plane: " + plane);
    }

    public int getSubblockX(SubBlock sb) {
        if (sb.getPlane() == SubBlock.Plane.Y1) {
            for (int y = 0; y < 4; y++) {
                for (int x = 0; x < 4; x++) {
                    if (ySubBlocks[x][y] == sb) {
                        return x;
                    }
                }
            }
        }
        else if (sb.getPlane() == SubBlock.Plane.U) {
            for (int y = 0; y < 2; y++) {
                for (int x = 0; x < 2; x++) {
                    if (uSubBlocks[x][y] == sb) {
                        return x;
                    }
                }
            }
        }
        else if (sb.getPlane() == SubBlock.Plane.V) {
            for (int y = 0; y < 2; y++) {
                for (int x = 0; x < 2; x++) {
                    if (vSubBlocks[x][y] == sb) {
                        return x;
                    }
                }
            }
        }
        else if (sb.getPlane() == SubBlock.Plane.Y2) {
            return 0;
        }

        return -100;
    }

    public int getSubblockY(SubBlock sb) {
        if (sb.getPlane() == SubBlock.Plane.Y1) {
            for (int y = 0; y < 4; y++) {
                for (int x = 0; x < 4; x++) {
                    if (ySubBlocks[x][y] == sb) {
                        return y;
                    }
                }
            }
        }
        else if (sb.getPlane() == SubBlock.Plane.U) {
            for (int y = 0; y < 2; y++) {
                for (int x = 0; x < 2; x++) {
                    if (uSubBlocks[x][y] == sb) {
                        return y;
                    }
                }
            }
        }
        else if (sb.getPlane() == SubBlock.Plane.V) {
            for (int y = 0; y < 2; y++) {
                for (int x = 0; x < 2; x++) {
                    if (vSubBlocks[x][y] == sb) {
                        return y;
                    }
                }
            }
        }
        else if (sb.getPlane() == SubBlock.Plane.Y2) {
            return 0;
        }

        return -100;
    }

    public SubBlock getUSubBlock(int i, int j) {
        return uSubBlocks[i][j];
    }

    public int getUVFilterLevel() {
        return this.uVFilterLevel;
    }

    public int getUvMode() {
        return uvMode;
    }

    public SubBlock getVSubBlock(int i, int j) {
        return vSubBlocks[i][j];
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public SubBlock getY2SubBlock() {
        return y2SubBlock;
    }

    public int getYMode() {
        return yMode;
    }

    public SubBlock getYSubBlock(int i, int j) {
        return ySubBlocks[i][j];
    }

    public boolean isKeepDebugInfo() {
        return keepDebugInfo;
    }

    public boolean isSkip_inner_lf() {
        return skipInnerLoopFilter;
    }

    public void predictUV(VP8Frame frame) {
        MacroBlock aboveMb = frame.getMacroBlock(x, y - 1);
        MacroBlock leftMb = frame.getMacroBlock(x - 1, y);

        switch (this.uvMode) {
            case Globals.DC_PRED:
                // System.out.println("UV DC_PRED");

                boolean up_available = false;
                boolean left_available = false;
                int Uaverage = 0;
                int Vaverage = 0;
                int expected_udc;
                int expected_vdc;
                if (x > 0) {
                    left_available = true;
                }
                if (y > 0) {
                    up_available = true;
                }
                if (up_available || left_available) {
                    if (up_available) {
                        for (int j = 0; j < 2; j++) {
                            SubBlock usb = aboveMb.getUSubBlock(j, 1);
                            SubBlock vsb = aboveMb.getVSubBlock(j, 1);
                            for (int i = 0; i < 4; i++) {
                                Uaverage += usb.getDest()[i][3];
                                Vaverage += vsb.getDest()[i][3];
                            }
                        }
                    }

                    if (left_available) {
                        for (int j = 0; j < 2; j++) {
                            SubBlock usb = leftMb.getUSubBlock(1, j);
                            SubBlock vsb = leftMb.getVSubBlock(1, j);
                            for (int i = 0; i < 4; i++) {
                                Uaverage += usb.getDest()[3][i];
                                Vaverage += vsb.getDest()[3][i];
                            }
                        }
                    }

                    int shift = 2;
                    if (up_available) {
                        shift++;
                    }
                    if (left_available) {
                        shift++;
                    }

                    expected_udc = (Uaverage + (1 << (shift - 1))) >> shift;
                    expected_vdc = (Vaverage + (1 << (shift - 1))) >> shift;
                }
                else {
                    expected_udc = 128;
                    expected_vdc = 128;
                }

                int[][] ufill = new int[4][4];
                for (int y = 0; y < 4; y++) {
                    for (int x = 0; x < 4; x++) {
                        ufill[x][y] = expected_udc;
                    }
                }

                int[][] vfill = new int[4][4];
                for (int y = 0; y < 4; y++) {
                    for (int x = 0; x < 4; x++) {
                        vfill[x][y] = expected_vdc;
                    }
                }

                for (int y = 0; y < 2; y++) {
                    for (int x = 0; x < 2; x++) {
                        SubBlock usb = uSubBlocks[x][y];
                        SubBlock vsb = vSubBlocks[x][y];
                        usb.setPredict(ufill);
                        vsb.setPredict(vfill);
                    }
                }

                break;
            case Globals.V_PRED:
                // System.out.println("UV V_PRED");

                SubBlock[] aboveUSb = new SubBlock[2];
                SubBlock[] aboveVSb = new SubBlock[2];
                for (int x = 0; x < 2; x++) {
                    aboveUSb[x] = aboveMb.getUSubBlock(x, 1);
                    aboveVSb[x] = aboveMb.getVSubBlock(x, 1);
                }

                for (int y = 0; y < 2; y++) {
                    for (int x = 0; x < 2; x++) {
                        SubBlock usb = uSubBlocks[y][x];
                        SubBlock vsb = vSubBlocks[y][x];
                        int[][] ublock = new int[4][4];
                        int[][] vblock = new int[4][4];
                        for (int j = 0; j < 4; j++) {
                            for (int i = 0; i < 4; i++) {
                                ublock[j][i] = aboveUSb[y]
                                        .getMacroBlockPredict(Globals.V_PRED)[j][3];
                                vblock[j][i] = aboveVSb[y]
                                        .getMacroBlockPredict(Globals.V_PRED)[j][3];
                            }
                        }
                        usb.setPredict(ublock);
                        vsb.setPredict(vblock);
                    }
                }

                break;

            case Globals.H_PRED:
                // System.out.println("UV H_PRED");

                SubBlock[] leftUSb = new SubBlock[2];
                SubBlock[] leftVSb = new SubBlock[2];
                for (int x = 0; x < 2; x++) {
                    leftUSb[x] = leftMb.getUSubBlock(1, x);
                    leftVSb[x] = leftMb.getVSubBlock(1, x);
                }

                for (int y = 0; y < 2; y++) {
                    for (int x = 0; x < 2; x++) {
                        SubBlock usb = uSubBlocks[x][y];
                        SubBlock vsb = vSubBlocks[x][y];
                        int[][] ublock = new int[4][4];
                        int[][] vblock = new int[4][4];
                        for (int j = 0; j < 4; j++) {
                            for (int i = 0; i < 4; i++) {
                                ublock[i][j] = leftUSb[y]
                                        .getMacroBlockPredict(Globals.H_PRED)[3][j];
                                vblock[i][j] = leftVSb[y]
                                        .getMacroBlockPredict(Globals.H_PRED)[3][j];
                            }
                        }
                        usb.setPredict(ublock);
                        vsb.setPredict(vblock);
                    }
                }

                break;
            case Globals.TM_PRED:
                // TODO:
                // System.out.println("UV TM_PRED MB");
                MacroBlock ALMb = frame.getMacroBlock(x - 1, y - 1);
                SubBlock ALUSb = ALMb.getUSubBlock(1, 1);
                int alu = ALUSb.getDest()[3][3];
                SubBlock ALVSb = ALMb.getVSubBlock(1, 1);
                int alv = ALVSb.getDest()[3][3];

                aboveUSb = new SubBlock[2];
                leftUSb = new SubBlock[2];
                aboveVSb = new SubBlock[2];
                leftVSb = new SubBlock[2];
                for (int x = 0; x < 2; x++) {
                    aboveUSb[x] = aboveMb.getUSubBlock(x, 1);
                    leftUSb[x] = leftMb.getUSubBlock(1, x);
                    aboveVSb[x] = aboveMb.getVSubBlock(x, 1);
                    leftVSb[x] = leftMb.getVSubBlock(1, x);
                }

                for (int b = 0; b < 2; b++) {
                    for (int a = 0; a < 4; a++) {
                        for (int d = 0; d < 2; d++) {
                            for (int c = 0; c < 4; c++) {

                                int upred = leftUSb[b].getDest()[3][a]
                                        + aboveUSb[d].getDest()[c][3] - alu;
                                upred = Globals.clamp(upred, 255);
                                uSubBlocks[d][b].setPixel(c, a, upred);

                                int vpred = leftVSb[b].getDest()[3][a]
                                        + aboveVSb[d].getDest()[c][3] - alv;
                                vpred = Globals.clamp(vpred, 255);
                                vSubBlocks[d][b].setPixel(c, a, vpred);
                            }
                        }
                    }
                }

                break;
            default:
                // TODO: FixME!
                throw new AssertionError("TODO predict_mb_uv: " + this.yMode);
        }
    }

    public void predictY(VP8Frame frame) {
        MacroBlock aboveMb = frame.getMacroBlock(x, y - 1);
        MacroBlock leftMb = frame.getMacroBlock(x - 1, y);

        switch (this.yMode) {
            case Globals.DC_PRED:
                // System.out.println("DC_PRED");
                boolean up_available = false;
                boolean left_available = false;

                int average = 0;
                int expected_dc;
                if (x > 0) {
                    left_available = true;
                }
                if (y > 0) {
                    up_available = true;
                }

                if (up_available || left_available) {
                    if (up_available) {
                        for (int j = 0; j < 4; j++) {
                            SubBlock sb = aboveMb.getYSubBlock(j, 3);
                            for (int i = 0; i < 4; i++) {
                                average += sb.getDest()[i][3];
                            }
                        }
                    }

                    if (left_available) {
                        for (int j = 0; j < 4; j++) {
                            SubBlock sb = leftMb.getYSubBlock(3, j);
                            for (int i = 0; i < 4; i++) {
                                average += sb.getDest()[3][i];
                            }
                        }
                    }

                    int shift = 3;
                    if (up_available) {
                        shift++;
                    }
                    if (left_available) {
                        shift++;
                    }

                    expected_dc = (average + (1 << (shift - 1))) >> shift;
                }
                else {
                    expected_dc = 128;
                }

                int[][] fill = new int[4][4];
                for (int y = 0; y < 4; y++) {
                    for (int x = 0; x < 4; x++) {
                        fill[x][y] = expected_dc;
                    }
                }
                for (int y = 0; y < 4; y++) {
                    for (int x = 0; x < 4; x++) {
                        SubBlock sb = ySubBlocks[x][y];
                        sb.setPredict(fill);
                    }
                }

                break;
            case Globals.V_PRED:
                // System.out.println("V_PRED");

                SubBlock[] aboveYSb = new SubBlock[4];
                for (int x = 0; x < 4; x++) {
                    aboveYSb[x] = aboveMb.getYSubBlock(x, 3);
                }

                for (int y = 0; y < 4; y++) {
                    for (int x = 0; x < 4; x++) {
                        SubBlock sb = ySubBlocks[x][y];
                        int[][] block = new int[4][4];
                        for (int j = 0; j < 4; j++) {
                            for (int i = 0; i < 4; i++) {
                                block[i][j] = aboveYSb[x].getPredict(
                                        Globals.B_VE_PRED, false)[i][3];
                            }
                        }
                        sb.setPredict(block);
                    }
                }

                break;

            case Globals.H_PRED:
                // System.out.println("H_PRED");

                SubBlock[] leftYSb = new SubBlock[4];
                for (int x = 0; x < 4; x++) {
                    leftYSb[x] = leftMb.getYSubBlock(3, x);
                }

                for (int y = 0; y < 4; y++) {
                    for (int x = 0; x < 4; x++) {
                        SubBlock sb = ySubBlocks[x][y];
                        int[][] block = new int[4][4];
                        for (int j = 0; j < 4; j++) {
                            for (int i = 0; i < 4; i++) {
                                block[i][j] = leftYSb[y].getPredict(
                                        Globals.B_DC_PRED, true)[3][j];
                            }
                        }
                        sb.setPredict(block);
                    }
                }

//                 SubBlock[] leftUSb = new SubBlock[2];
//                 for (int x = 0; x < 2; x++) {
//                     leftUSb[x] = leftMb.getYSubBlock(1, x);
//                 }

                break;
            case Globals.TM_PRED:
                // System.out.println("TM_PRED MB");
                MacroBlock ALMb = frame.getMacroBlock(x - 1, y - 1);
                SubBlock ALSb = ALMb.getYSubBlock(3, 3);
                int al = ALSb.getDest()[3][3];

                aboveYSb = new SubBlock[4];
                leftYSb = new SubBlock[4];
                for (int x = 0; x < 4; x++) {
                    aboveYSb[x] = aboveMb.getYSubBlock(x, 3);
                }
                for (int x = 0; x < 4; x++) {
                    leftYSb[x] = leftMb.getYSubBlock(3, x);
                }
//                 fill = new int[4][4];

                for (int b = 0; b < 4; b++) {
                    for (int a = 0; a < 4; a++) {

                        for (int d = 0; d < 4; d++) {
                            for (int c = 0; c < 4; c++) {
                                int pred = leftYSb[b].getDest()[3][a]
                                        + aboveYSb[d].getDest()[c][3] - al;

                                ySubBlocks[d][b].setPixel(c, a,
                                        Globals.clamp(pred, 255));
                            }
                        }
                    }
                }

                break;
            default:
                System.out.println("TODO predict_mb_y: " + this.yMode);
                System.exit(0);
        }
    }

    public void recon_mb() {
        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                SubBlock sb = ySubBlocks[i][j];
                sb.reconstruct();
            }
        }

        for (int j = 0; j < 2; j++) {
            for (int i = 0; i < 2; i++) {
                SubBlock sb = uSubBlocks[i][j];
                sb.reconstruct();
            }
        }
        for (int j = 0; j < 2; j++) {
            for (int i = 0; i < 2; i++) {
                SubBlock sb = vSubBlocks[i][j];
                sb.reconstruct();
            }
        }
    }

    public void setFilterLevel(int value) {
        this.filterLevel = value;
    }

    public void setSegmentId(int value) {
        this.segmentId = value;
    }

    public void setSkipCoeff(int mbSkipCoeff) {
        skipCoeff = mbSkipCoeff;
    }

    public void setUVFilterLevel(int value) {
        this.uVFilterLevel = value;
    }

    public void setUvMode(int mode) {
        this.uvMode = mode;
    }

    public void setYMode(int yMode) {
        this.yMode = yMode;
    }

    public String toString() {
        return "x: " + x + "y: " + y;
    }

    public int getSegmentId() {
        return segmentId;
    }
}
