/* ====================================================================
   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.poi.hssf.record.formula;

import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;

/**
 * "Special Attributes"
 * This seems to be a Misc Stuff and Junk record.  One function it serves is
 * in SUM functions (i.e. SUM(A1:A3) causes an area PTG then an ATTR with the SUM option set)
 * @author  andy
 * @author Jason Height (jheight at chariot dot net dot au)
 */
public final class AttrPtg extends ControlPtg {
    public final static byte sid  = 0x19;
    private final static int  SIZE = 4;
    private final byte _options;
    private final short _data;

    /** only used for tAttrChoose: table of offsets to starts of args */
    private final int[] _jumpTable;
    /** only used for tAttrChoose: offset to the tFuncVar for CHOOSE() */
    private final int   _chooseFuncOffset;

    // flags 'volatile' and 'space', can be combined.
    // OOO spec says other combinations are theoretically possible but not likely to occur.
    private static final BitField semiVolatile = BitFieldFactory.getInstance(0x01);
    private static final BitField optiIf       = BitFieldFactory.getInstance(0x02);
    private static final BitField optiChoose   = BitFieldFactory.getInstance(0x04);
    private static final BitField optiSkip     = BitFieldFactory.getInstance(0x08);
    private static final BitField optiSum      = BitFieldFactory.getInstance(0x10);
    private static final BitField baxcel       = BitFieldFactory.getInstance(0x20); // 'assignment-style formula in a macro sheet'
    private static final BitField space        = BitFieldFactory.getInstance(0x40);


    public AttrPtg(LittleEndianInput in) {
        _options = in.readByte();
        _data    = in.readShort();
        if (isOptimizedChoose()) {
            int nCases = _data;
            int[] jumpTable = new int[nCases];
            for (int i = 0; i < jumpTable.length; i++) {
                jumpTable[i] = in.readUShort();
            }
            _jumpTable = jumpTable;
            _chooseFuncOffset = in.readUShort();
        } else {
            _jumpTable = null;
            _chooseFuncOffset = -1;
        }

    }

    public boolean isSemiVolatile() {
        return semiVolatile.isSet(_options);
    }

    public boolean isOptimizedIf() {
        return optiIf.isSet(_options);
    }

    public boolean isOptimizedChoose() {
        return optiChoose.isSet(_options);
    }

    public boolean isSum() {
        return optiSum.isSet(_options);
    }
    public boolean isSkip() {
        return optiSkip.isSet(_options);
    }

    // lets hope no one uses this anymore
    private boolean isBaxcel() {
        return baxcel.isSet(_options);
    }

    public boolean isSpace() {
        return space.isSet(_options);
    }

    public String toString() {
        StringBuffer sb = new StringBuffer(64);
        sb.append(getClass().getName()).append(" [");

        if(isSemiVolatile()) {
            sb.append("volatile ");
        }
        if(isSpace()) {
            sb.append("space count=").append((_data >> 8) & 0x00FF);
            sb.append(" type=").append(_data & 0x00FF).append(" ");
        }
        // the rest seem to be mutually exclusive
        if(isOptimizedIf()) {
            sb.append("if dist=").append(_data);
        } else if(isOptimizedChoose()) {
            sb.append("choose nCases=").append(_data);
        } else if(isSkip()) {
            sb.append("skip dist=").append(_data);
        } else if(isSum()) {
            sb.append("sum ");
        } else if(isBaxcel()) {
            sb.append("assign ");
        }
        sb.append("]");
        return sb.toString();
    }

    public void write(LittleEndianOutput out) {
        out.writeByte(sid + getPtgClass());
        out.writeByte(_options);
        out.writeShort(_data);
        int[] jt = _jumpTable;
        if (jt != null) {
            for (int i = 0; i < jt.length; i++) {
                out.writeShort(jt[i]);
            }
            out.writeShort(_chooseFuncOffset);
        }
    }

    public int getSize() {
        if (_jumpTable != null) {
            return SIZE + (_jumpTable.length + 1) * LittleEndian.SHORT_SIZE;
        }
        return SIZE;
    }

    public String toFormulaString(String[] operands) {
        if(space.isSet(_options)) {
            return operands[ 0 ];
        } else if (optiIf.isSet(_options)) {
            return toFormulaString() + "(" + operands[0] + ")";
        } else if (optiSkip.isSet(_options)) {
            return toFormulaString() + operands[0];   //goto isn't a real formula element should not show up
        } else {
            return toFormulaString() + "(" + operands[0] + ")";
        }
    }


    public int getNumberOfOperands() {
        return 1;
    }

   public String toFormulaString() {
      if(semiVolatile.isSet(_options)) {
        return "ATTR(semiVolatile)";
      }
      if(optiIf.isSet(_options)) {
        return "IF";
      }
      if( optiChoose.isSet(_options)) {
        return "CHOOSE";
      }
      if(optiSkip.isSet(_options)) {
        return "";
      }
      if(optiSum.isSet(_options)) {
        return "SUM";
      }
      if(baxcel.isSet(_options)) {
        return "ATTR(baxcel)";
      }
      if(space.isSet(_options)) {
        return "";
      }
      return "UNKNOWN ATTRIBUTE";
     }
}
