/* ====================================================================
   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.hwpf.model;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;

import static org.apache.logging.log4j.util.Unbox.box;

@Internal
public final class FIBFieldHandler
{
    // 154 == 0x009A; 158 == 0x009E
  public static final int STSHFORIG = 0;
  public static final int STSHF = 1;
  public static final int PLCFFNDREF = 2;
  public static final int PLCFFNDTXT = 3;
  public static final int PLCFANDREF = 4;
  public static final int PLCFANDTXT = 5;
  public static final int PLCFSED = 6;
  public static final int PLCFPAD = 7;
  public static final int PLCFPHE = 8;
  public static final int STTBGLSY = 9;
  // 234 == 0x00EA; 238 == 0x00EE
  public static final int PLCFGLSY = 10;
  // 242 == 0200F2; 246 == 0x00F6
  public static final int PLCFHDD = 11;
  public static final int PLCFBTECHPX = 12;
  public static final int PLCFBTEPAPX = 13;
  public static final int PLCFSEA = 14;
  public static final int STTBFFFN = 15;
  public static final int PLCFFLDMOM = 16;
  public static final int PLCFFLDHDR = 17;
  // 298 == 0x12A; 302 == 0x12E
  public static final int PLCFFLDFTN = 18;
  // 306 == 0x132; 310 == 0x0136
  public static final int PLCFFLDATN = 19;
  // 314 == 0x013A; 318 == 0x013E
  public static final int PLCFFLDMCR = 20;
  // 322 == 0x0142; 326 == 0x0146
  public static final int STTBFBKMK = 21;
  // 330 == 0x014A; 334 == 0x014E
  public static final int PLCFBKF = 22;
  public static final int PLCFBKL = 23;
  public static final int CMDS = 24;
  public static final int PLCMCR = 25;
  public static final int STTBFMCR = 26;
  public static final int PRDRVR = 27;
  public static final int PRENVPORT = 28;
  public static final int PRENVLAND = 29;
  public static final int WSS = 30;
  // 402 == 0x0192; 406 == 0x0196
  public static final int DOP = 31;
  public static final int STTBFASSOC = 32;
  public static final int CLX = 33;
  public static final int PLCFPGDFTN = 34;
  public static final int AUTOSAVESOURCE = 35;
  public static final int GRPXSTATNOWNERS = 36;//validated
  public static final int STTBFATNBKMK = 37;
  public static final int PLCFDOAMOM = 38;
  public static final int PLCDOAHDR = 39;
    // 474 == 0x01DA; 478 == 0x01DE
    public static final int PLCSPAMOM = 40;
    // 482 == 0x01E2; 490 == 0x01E6
    public static final int PLCSPAHDR = 41;
    public static final int PLCFATNBKF = 42;
    // 498 == 0x01F2; 502 == 0x01F6
    public static final int PLCFATNBKL = 43;
    // 506 == 0x01FA; 510 == 0x01FE
    public static final int PMS = 44;
    // 514 == 0x0202; 518 == 0x0206
    public static final int FORMFLDSTTBS = 45;
  public static final int PLCFENDREF = 46;
  public static final int PLCFENDTXT = 47;
  public static final int PLCFFLDEDN = 48;
  public static final int PLCFPGDEDN = 49;
    // 554 == 0x022A; 558 == 0x022E -- long
    public static final int DGGINFO = 50;
  public static final int STTBFRMARK = 51;
  public static final int STTBCAPTION = 52;
  public static final int STTBAUTOCAPTION = 53;
  public static final int PLCFWKB = 54;
  public static final int PLCFSPL = 55;
  public static final int PLCFTXBXTXT = 56;
    // 610 -- 0x0262; 614 == 0x0266
    public static final int PLCFFLDTXBX = 57;// validated
  public static final int PLCFHDRTXBXTXT = 58;
  public static final int PLCFFLDHDRTXBX = 59;
  public static final int STWUSER = 60;
  public static final int STTBTTMBD = 61;
  public static final int UNUSED = 62;
  public static final int PGDMOTHER = 63;
  public static final int BKDMOTHER = 64;
  public static final int PGDFTN = 65;
  public static final int BKDFTN = 66;
  public static final int PGDEDN = 67;
  public static final int BKDEDN = 68;
  public static final int STTBFINTFLD = 69;
  public static final int ROUTESLIP = 70;
  public static final int STTBSAVEDBY = 71;
  public static final int STTBFNM = 72;
  public static final int PLFLST = 73;
  @Deprecated
  public static final int PLCFLST = PLFLST;
  public static final int PLFLFO = 74;
  public static final int PLCFTXBXBKD = 75;//validated
  public static final int PLCFTXBXHDRBKD = 76;
  public static final int DOCUNDO = 77;
  public static final int RGBUSE = 78;
  public static final int USP = 79;
  public static final int USKF = 80;
  public static final int PLCUPCRGBUSE = 81;
  public static final int PLCUPCUSP = 82;
  public static final int STTBGLSYSTYLE = 83;
  public static final int PLGOSL = 84;
  public static final int PLCOCX = 85;
  public static final int PLCFBTELVC = 86;
  public static final int MODIFIED = 87;
  public static final int PLCFLVC = 88;
  public static final int PLCASUMY = 89;
  public static final int PLCFGRAM = 90;
  public static final int STTBLISTNAMES = 91;
  public static final int STTBFUSSR = 92;

  private static final Logger LOGGER = LogManager.getLogger(FIBFieldHandler.class);

  private static final int FIELD_SIZE = LittleEndianConsts.INT_SIZE * 2;

  private Map<Integer, UnhandledDataStructure> _unknownMap = new HashMap<>();
  private int[] _fields;


  FIBFieldHandler(byte[] mainStream, int offset, int cbRgFcLcb, byte[] tableStream,
                         HashSet<Integer> offsetList, boolean areKnown)
  {
    _fields = new int[cbRgFcLcb * 2];

    for (int x = 0; x < cbRgFcLcb; x++)
    {
      int fieldOffset = (x * FIELD_SIZE) + offset;
      int dsOffset = LittleEndian.getInt(mainStream, fieldOffset);
      fieldOffset += LittleEndianConsts.INT_SIZE;
      int dsSize = LittleEndian.getInt(mainStream, fieldOffset);

      if (offsetList.contains(Integer.valueOf(x)) ^ areKnown)
      {
        if (dsSize > 0)
        {
          if (dsOffset + dsSize > tableStream.length)
          {
            LOGGER.atWarn().log("Unhandled data structure points to outside the buffer. offset = {}, length = {}, buffer length = {}", box(dsOffset),box(dsSize),box(tableStream.length));
          }
          else
          {
            UnhandledDataStructure unhandled = new UnhandledDataStructure(
              tableStream, dsOffset, dsSize);
            _unknownMap.put(Integer.valueOf(x), unhandled);
          }
        }
      }
      _fields[x*2] = dsOffset;
      _fields[(x*2) + 1] = dsSize;
    }
  }

  public void clearFields()
  {
    Arrays.fill(_fields, 0);
  }

  public int getFieldOffset(int field)
  {
    return _fields[field*2];
  }

  public int getFieldSize(int field)
  {
    return _fields[(field*2) + 1];
  }

  public void setFieldOffset(int field, int offset)
  {
    _fields[field*2] = offset;
  }

  public void setFieldSize(int field, int size)
  {
    _fields[(field*2) + 1] = size;
  }

  public int sizeInBytes()
  {
    return (_fields.length * LittleEndianConsts.INT_SIZE);
  }

  public int getFieldsCount() {
      return _fields.length / 2;
  }

  void writeTo(byte[] mainStream, int offset, ByteArrayOutputStream tableStream)
    throws IOException
  {
    for (int x = 0; x < _fields.length/2; x++)
    {
      UnhandledDataStructure ds = _unknownMap.get(Integer.valueOf(x));
      if (ds != null)
      {
        _fields[x * 2] = tableStream.size();
        LittleEndian.putInt(mainStream, offset, tableStream.size());
        offset += LittleEndianConsts.INT_SIZE;

        byte[] buf = ds.getBuf();
        tableStream.write(buf);

        _fields[(x * 2) + 1] = buf.length;
        LittleEndian.putInt(mainStream, offset, buf.length);
        offset += LittleEndianConsts.INT_SIZE;
      }
      else
      {
        LittleEndian.putInt(mainStream, offset, _fields[x * 2]);
        offset += LittleEndianConsts.INT_SIZE;
        LittleEndian.putInt(mainStream, offset, _fields[(x * 2) + 1]);
        offset += LittleEndianConsts.INT_SIZE;
      }
    }
  }

    private static String leftPad( String text, int value, char padChar )
    {
        if ( text.length() >= value )
            return text;

        StringBuilder result = new StringBuilder();
        for ( int i = 0; i < ( value - text.length() ); i++ )
        {
            result.append( padChar );
        }
        result.append( text );
        return result.toString();
    }

    @Override
    public String toString()
    {
        StringBuilder result = new StringBuilder();
        result.append( "[FIBFieldHandler]:\n" );

        result.append( "\tFields:\n" );
        result.append( "\t" );
        result.append( leftPad( "Index", 8, ' ' ) );
        result.append( leftPad( "FIB offset", 15, ' ' ) );
        result.append( leftPad( "Offset", 8, ' ' ) );
        result.append( leftPad( "Size", 8, ' ' ) );
        result.append( '\n' );
        for ( int x = 0; x < _fields.length / 2; x++ )
        {
            result.append( '\t' );
            result.append( leftPad( Integer.toString( x ), 8, ' ' ) );
            result.append( leftPad( Integer.toString( 154 + x * LittleEndianConsts.INT_SIZE * 2 ), 6, ' ' ) );
            result.append( "   0x" );
            result.append( leftPad( Integer.toHexString( 154 + x * LittleEndianConsts.INT_SIZE * 2 ), 4, '0' ) );
            result.append( leftPad( Integer.toString( getFieldOffset( x ) ), 8, ' ' ) );
            result.append( leftPad( Integer.toString( getFieldSize( x ) ), 8, ' ' ) );

            UnhandledDataStructure structure = _unknownMap.get( Integer.valueOf( x ) );
            if ( structure != null )
            {
                result.append( " => Unknown structure of size " );
                result.append( structure.getBuf().length );
            }
            result.append( '\n' );
        }
        return result.toString();
    }
}
