/*
 * 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.tika.parser.chm.lzx;

import java.util.concurrent.CancellationException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.parser.chm.core.ChmCommons;
import org.apache.tika.parser.chm.core.ChmCommons.IntelState;
import org.apache.tika.parser.chm.core.ChmCommons.LzxState;
import org.apache.tika.parser.chm.core.ChmConstants;
import org.apache.tika.parser.chm.exception.ChmParsingException;

public class ChmLzxState implements Cloneable {
    /* Class' members */
    private int window; /* the actual decoding window */
    private long window_size; /* window size (32Kb through 2Mb) */
    private int window_position; /* current offset within the window */
    private int main_tree_elements; /* number of main tree elements */
    private LzxState hadStarted; /* have we started decoding at all yet? */
    private int block_type; /* type of this block */
    private int block_length; /* uncompressed length of this block */
    private int block_remaining; /* uncompressed bytes still left to decode */
    private int frames_read; /* the number of CFDATA blocks processed */
    private int intel_file_size; /* magic header value used for transform */
    private long intel_current_possition; /* current offset in transform space */
    private IntelState intel_state; /* have we seen any translatable data yet? */
    private long R0; /* for the LRU offset system */
    private long R1; /* for the LRU offset system */
    private long R2; /* for the LRU offset system */

    // Trees - PRETREE, MAINTREE, LENGTH, ALIGNED
    protected short[] mainTreeLengtsTable;
    protected short[] mainTreeTable;

    protected short[] lengthTreeTable;
    protected short[] lengthTreeLengtsTable;

    protected short[] alignedLenTable;
    protected short[] alignedTreeTable;

    @Override
    public ChmLzxState clone() {
        try {
          ChmLzxState clone = (ChmLzxState)super.clone();
          clone.mainTreeLengtsTable = arrayClone(mainTreeLengtsTable);
          clone.mainTreeTable = arrayClone(mainTreeTable);
          clone.lengthTreeTable = arrayClone(lengthTreeTable);
          clone.lengthTreeLengtsTable = arrayClone(lengthTreeLengtsTable);
          clone.alignedLenTable = arrayClone(alignedLenTable);
          clone.alignedTreeTable = arrayClone(alignedTreeTable);
          return clone;
        } catch (CloneNotSupportedException ex) {
           return null;
        }
    }
    
    protected short[] getMainTreeTable() {
        return mainTreeTable;
    }

    protected short[] getAlignedTreeTable() {
        return alignedTreeTable;
    }

    protected void setAlignedTreeTable(short[] alignedTreeTable) {
        this.alignedTreeTable = alignedTreeTable;
    }

    protected short[] getLengthTreeTable() throws TikaException {
        if (lengthTreeTable != null)
            return this.lengthTreeTable;
        else
            throw new ChmParsingException("lengthTreeTable is null");
    }

    protected void setLengthTreeTable(short[] lengthTreeTable) {
        this.lengthTreeTable = lengthTreeTable;
    }

    protected void setMainTreeTable(short[] mainTreeTable) {
        this.mainTreeTable = mainTreeTable;
    }

    protected short[] getAlignedLenTable() {
        return this.alignedLenTable;
    }

    protected void setAlignedLenTable(short[] alignedLenTable) {
        this.alignedLenTable = alignedLenTable;
    }

    /**
     * It suits for informative outlook
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("actual decoding window:=" + getWindow()
                + System.getProperty("line.separator"));
        sb.append("window size (32Kb through 2Mb):=" + getWindowSize()
                + System.getProperty("line.separator"));
        sb.append("current offset within the window:=" + getWindowPosition()
                + System.getProperty("line.separator"));
        sb.append("number of main tree elements:=" + getMainTreeElements()
                + System.getProperty("line.separator"));
        sb.append("have we started decoding at all yet?:=" + getHadStarted()
                + System.getProperty("line.separator"));
        sb.append("type of this block:=" + getBlockType()
                + System.getProperty("line.separator"));
        sb.append("uncompressed length of this block:=" + getBlockLength()
                + System.getProperty("line.separator"));
        sb.append("uncompressed bytes still left to decode:="
                + getBlockRemaining() + System.getProperty("line.separator"));
        sb.append("the number of CFDATA blocks processed:=" + getFramesRead()
                + System.getProperty("line.separator"));
        sb.append("magic header value used for transform:="
                + getIntelFileSize() + System.getProperty("line.separator"));
        sb.append("current offset in transform space:="
                + getIntelCurrentPossition()
                + System.getProperty("line.separator"));
        sb.append("have we seen any translatable data yet?:=" + getIntelState()
                + System.getProperty("line.separator"));
        sb.append("R0 for the LRU offset system:=" + getR0()
                + System.getProperty("line.separator"));
        sb.append("R1 for the LRU offset system:=" + getR1()
                + System.getProperty("line.separator"));
        sb.append("R2 for the LRU offset system:=" + getR2()
                + System.getProperty("line.separator"));
        sb.append("main tree length:=" + getMainTreeLengtsTable().length
                + System.getProperty("line.separator"));
        sb.append("secondary tree length:=" + getLengthTreeLengtsTable().length
                + System.getProperty("line.separator"));
        return sb.toString();
    }

    public ChmLzxState(int window) throws TikaException {
        if (window >= 0) {
            int position_slots;
            int win = ChmCommons.getWindowSize(window);
            setWindowSize(1 << win);
            /* LZX supports window sizes of 2^15 (32Kb) through 2^21 (2Mb) */
            if (win < 15 || win > 21)
                throw new ChmParsingException("window less than 15 or window greater than 21");

            /* Calculates required position slots */
            if (win == 20)
                position_slots = 42;
            else if (win == 21)
                position_slots = 50;
            else
                position_slots = win << 1;
            //TODO: position_slots is not used ?
            setR0(1);
            setR1(1);
            setR2(1);
            setMainTreeElements(512);
            setHadStarted(LzxState.NOT_STARTED_DECODING);
            setFramesRead(0);
            setBlockRemaining(0);
            setBlockType(ChmConstants.LZX_BLOCKTYPE_INVALID);
            setIntelCurrentPossition(0);
            setIntelState(IntelState.NOT_STARTED);
            setWindowPosition(0);
            setMainTreeLengtsTable(new short[getMainTreeElements()]);
            setLengthTreeLengtsTable(new short[ChmConstants.LZX_NUM_SECONDARY_LENGTHS]);
        } else
            throw new CancellationException(
                    "window size should be more than zero");
    }

    protected void setWindow(int window) {
        this.window = window;
    }

    protected int getWindow() {
        return window;
    }

    protected void setWindowSize(long window_size) {
        this.window_size = window_size;
    }

    protected long getWindowSize() {
        return window_size;
    }

    protected void setWindowPosition(int window_position) {
        this.window_position = window_position;
    }

    protected int getWindowPosition() {
        return window_position;
    }

    protected void setMainTreeElements(int main_tree_elements) {
        this.main_tree_elements = main_tree_elements;
    }

    protected int getMainTreeElements() {
        return main_tree_elements;
    }

    protected void setHadStarted(LzxState hadStarted) {
        this.hadStarted = hadStarted;
    }

    protected LzxState getHadStarted() {
        return hadStarted;
    }

    protected void setBlockType(int block_type) {
        this.block_type = block_type;
    }

    public int getBlockType() {
        return block_type;
    }

    protected void setBlockLength(int block_length) {
        this.block_length = block_length;
    }

    protected int getBlockLength() {
        return block_length;
    }

    protected void setBlockRemaining(int block_remaining) {
        this.block_remaining = block_remaining;
    }

    protected int getBlockRemaining() {
        return block_remaining;
    }

    protected void setFramesRead(int frames_read) {
        this.frames_read = frames_read;
    }

    protected void increaseFramesRead() {
        this.frames_read = getFramesRead() + 1;
    }

    protected int getFramesRead() {
        return frames_read;
    }

    protected void setIntelFileSize(int intel_file_size) {
        this.intel_file_size = intel_file_size;
    }

    protected int getIntelFileSize() {
        return intel_file_size;
    }

    protected void setIntelCurrentPossition(long intel_current_possition) {
        this.intel_current_possition = intel_current_possition;
    }

    protected long getIntelCurrentPossition() {
        return intel_current_possition;
    }

    protected void setIntelState(IntelState intel_state) {
        this.intel_state = intel_state;
    }

    protected IntelState getIntelState() {
        return intel_state;
    }

    protected void setR0(long r0) {
        R0 = r0;
    }

    protected long getR0() {
        return R0;
    }

    protected void setR1(long r1) {
        R1 = r1;
    }

    protected long getR1() {
        return R1;
    }

    protected void setR2(long r2) {
        R2 = r2;
    }

    protected long getR2() {
        return R2;
    }

    public void setMainTreeLengtsTable(short[] mainTreeLengtsTable) {
        this.mainTreeLengtsTable = mainTreeLengtsTable;
    }

    public short[] getMainTreeLengtsTable() {
        return mainTreeLengtsTable;
    }

    public void setLengthTreeLengtsTable(short[] lengthTreeLengtsTable) {
        this.lengthTreeLengtsTable = lengthTreeLengtsTable;
    }

    public short[] getLengthTreeLengtsTable() {
        return lengthTreeLengtsTable;
    }
    
    private static short[] arrayClone(short[] a) {
        return a==null ? null : (short[]) a.clone();
    }
}
