/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.gatein.pc.test.controller.unit;

import org.gatein.pc.api.Mode;
import org.gatein.pc.api.WindowState;
import org.gatein.common.io.Serialization;
import org.gatein.pc.api.StateString;
import org.gatein.pc.controller.state.PageNavigationalState;
import org.gatein.pc.controller.state.StateControllerContext;
import org.gatein.pc.controller.state.WindowNavigationalState;

import javax.xml.namespace.QName;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Implement serialization of a page navigational state in a compact manner.
 *
 */
public class PageNavigationalStateSerialization implements Serialization<PageNavigationalState>
{

   /** . */
   private static final Map<org.gatein.pc.api.WindowState, Integer> windowStateToCode = new HashMap<WindowState, Integer>();

   /** . */
   private static final Map<Mode, Integer> modeToCode = new HashMap<Mode, Integer>();

   /** . */
   private static final Map<Integer, org.gatein.pc.api.WindowState> codeToWindowState = new HashMap<Integer, org.gatein.pc.api.WindowState>();

   /** . */
   private static final Map<Integer, org.gatein.pc.api.Mode> codeToMode = new HashMap<Integer, Mode>();

   static
   {
      windowStateToCode.put(WindowState.NORMAL, 1);
      windowStateToCode.put(org.gatein.pc.api.WindowState.MINIMIZED, 2);
      windowStateToCode.put(WindowState.MAXIMIZED, 3);
      codeToWindowState.put(1, WindowState.NORMAL);
      codeToWindowState.put(2, org.gatein.pc.api.WindowState.MINIMIZED);
      codeToWindowState.put(3, org.gatein.pc.api.WindowState.MAXIMIZED);
      modeToCode.put(Mode.VIEW, 1);
      modeToCode.put(org.gatein.pc.api.Mode.EDIT, 2);
      modeToCode.put(Mode.HELP, 3);
      codeToMode.put(1, Mode.VIEW);
      codeToMode.put(2, Mode.EDIT);
      codeToMode.put(3, Mode.HELP);
   }

   /** . */
   private final StateControllerContext context;

   public PageNavigationalStateSerialization(StateControllerContext context)
   {
      if (context == null)
      {
         throw new IllegalArgumentException();
      }
      this.context = context;
   }

   public void serialize(PageNavigationalState pageNavigationalState, OutputStream out) throws IOException, IllegalArgumentException
   {
      DataOutputStream data = out instanceof DataOutputStream ? (DataOutputStream)out : new DataOutputStream(out);

      //
      data.writeInt(pageNavigationalState.getWindowIds().size());
      for (String windowId : pageNavigationalState.getWindowIds())
      {
         data.writeUTF(windowId);
         WindowNavigationalState windowNS = pageNavigationalState.getWindowNavigationalState(windowId);
         byte header = 0;
         int decision = 0;
         if (windowNS.getPortletNavigationalState() != null)
         {
            header |= 1;
            decision |= 1;
         }
         if (windowNS.getWindowState() != null)
         {
            Integer value = windowStateToCode.get(windowNS.getWindowState());
            if (value != null)
            {
               header |= value << 1;
            }
            else
            {
               header |= 4 << 1;
               decision |= 2;
            }
         }
         if (windowNS.getMode() != null)
         {
            Integer value = modeToCode.get(windowNS.getMode());
            if (value != null)
            {
               header |= value << 4;
            }
            else
            {
               header |= 4 << 4;
               decision |= 4;
            }
         }
         data.writeByte(header);
         if ((decision & 1) != 0)
         {
            windowNS.getPortletNavigationalState().writeTo(data);
         }
         if ((decision & 2) != 0)
         {
            data.writeUTF(windowNS.getWindowState().toString());
         }
         if ((decision & 4) != 0)
         {
            data.writeUTF(windowNS.getMode().toString());
         }
      }

      //
      Set<QName> publicNames = pageNavigationalState.getPublicNames();
      data.writeInt(publicNames.size());
      for (QName name : pageNavigationalState.getPublicNames())
      {
         data.writeUTF(name.getNamespaceURI());
         data.writeUTF(name.getLocalPart());
         String[] values = pageNavigationalState.getPublicNavigationalState(name);
         int length = values.length;
         data.writeInt(length);
         for (String value : values)
         {
            data.writeUTF(value);
         }
      }

      // Need to flush before returning otherwise bytes may stay in the buffer when we allocated
      // a data stream
      data.flush();
   }

   public PageNavigationalState unserialize(InputStream in) throws IOException, IllegalArgumentException
   {
      DataInputStream data = in instanceof DataInputStream ? (DataInputStream)in : new DataInputStream(in);

      //
      PageNavigationalState pageNS = new PageNavigationalState(true);

      //
      int size = data.readInt();
      while (size-- > 0)
      {
         String windowId = data.readUTF();
         byte header = data.readByte();
         StateString portletNS = null;
         if ((header & 1) != 0)
         {
            portletNS = StateString.create(data);
         }
         org.gatein.pc.api.WindowState windowState = null;
         int windowStateHeader = (header & (7 << 1)) >> 1;
         switch (windowStateHeader)
         {
            case 0:
               break;
            case 4:
               windowState = org.gatein.pc.api.WindowState.create(data.readUTF());
               break;
            default:
               windowState = codeToWindowState.get(windowStateHeader);
         }
         org.gatein.pc.api.Mode mode = null;
         int modeStateHeader = (header & (7 << (1 + 3))) >> (1 + 3);
         switch (modeStateHeader)
         {
            case 0:
               break;
            case 4:
               mode = Mode.create(data.readUTF());
               break;
            default:
               mode = codeToMode.get(modeStateHeader);
         }
         WindowNavigationalState windowNS = new WindowNavigationalState(portletNS, mode, windowState);
         pageNS.setWindowNavigationalState(windowId, windowNS);
      }

      //
      size = data.readInt();
      while (size-- > 0)
      {
         String namespaceURI = data.readUTF();
         String localName = data.readUTF();
         QName name = new QName(namespaceURI, localName);
         int length = data.readInt();
         String[] values = new String[length];
         for (int i = 0; i < length; i++)
         {
            values[i] = data.readUTF();
         }
         pageNS.setPublicNavigationalState(name, values);
      }

      //
      return pageNS;
   }
}
