/*
 *  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.causeway.viewer.restfulobjects.applib.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.RuntimeDelegate;

import org.apache.causeway.commons.internal._Constants;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.commons.internal.collections._Lists;
import org.apache.causeway.viewer.restfulobjects.applib.JsonRepresentation;

/**
 * @since 1.x {@index}
 */
public abstract class Parser<T> {

    public T valueOf(final List<String> str) {
        if (str == null) {
            return null;
        }
        if (str.size() == 0) {
            return null;
        }
        return valueOf(str.get(0));
    }

    public T valueOf(final String[] str) {
        if (str == null) {
            return null;
        }
        if (str.length == 0) {
            return null;
        }
        return valueOf(str[0]);
    }

    public T valueOf(final JsonRepresentation jsonRepresentation) {
        if (jsonRepresentation == null) {
            return null;
        }
        return valueOf(jsonRepresentation.asString());
    }

    public JsonRepresentation asJsonRepresentation(final T t) {
        return JsonRepresentation.newMap("dummy", asString(t)).getRepresentation("dummy");
    }

    public abstract T valueOf(String str);

    public abstract String asString(T t);

    public static final Parser<String> forString() {
        return new Parser<String>() {
            @Override
            public String valueOf(final String str) {
                return str;
            }

            @Override
            public String asString(final String t) {
                return t;
            }
        };
    }

    public static Parser<Date> forDate() {

        return new Parser<Date>() {
            private final SimpleDateFormat RFC1123_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyyy HH:mm:ss z");

            @Override
            public Date valueOf(final String str) {
                if (str == null) {
                    return null;
                }
                try {
                    return RFC1123_DATE_FORMAT.parse(str);
                } catch (final ParseException e) {
                    return null;
                }
            }

            @Override
            public String asString(final Date t) {
                return RFC1123_DATE_FORMAT.format(t);
            }
        };
    }

    public static Parser<CacheControl> forCacheControl() {
        return new Parser<CacheControl>() {

            @Override
            public CacheControl valueOf(final String str) {
                if (str == null) {
                    return null;
                }
                CacheControl cacheControl =
                        RuntimeDelegate.getInstance().createHeaderDelegate(CacheControl.class)
                        .fromString(str);
                // workaround for bug in CacheControl's equals() method
                cacheControl.getCacheExtension();
                cacheControl.getNoCacheFields();
                return cacheControl;
            }

            @Override
            public String asString(final CacheControl cacheControl) {
                return RuntimeDelegate.getInstance().createHeaderDelegate(CacheControl.class)
                        .toString(cacheControl);
            }
        };
    }

    public static Parser<MediaType> forJaxRsMediaType() {
        return new Parser<MediaType>() {
            @Override
            public MediaType valueOf(final String str) {
                if (str == null) {
                    return null;
                }
                return MediaType.valueOf(str);
            }

            @Override
            public String asString(final MediaType t) {
                return t.toString();
            }

        };
    }

    public static Parser<Boolean> forBoolean() {
        return new Parser<Boolean>() {
            @Override
            public Boolean valueOf(final String str) {
                if (str == null) {
                    return null;
                }
                return "yes".equalsIgnoreCase(str) || "true".equalsIgnoreCase(str)
                        ? Boolean.TRUE
                                : Boolean.FALSE;
            }

            @Override
            public String asString(final Boolean t) {
                return t ? "yes" : "no";
            }

        };
    }

    public static Parser<Integer> forInteger() {
        return new Parser<Integer>() {

            @Override
            public Integer valueOf(final String str) {
                if (str == null) {
                    return null;
                }
                return Integer.valueOf(str);
            }

            @Override
            public String asString(final Integer t) {
                return t.toString();
            }
        };
    }

    public static Parser<List<String>> forListOfStrings() {
        return new Parser<List<String>>() {

            @Override
            public List<String> valueOf(final List<String> strings) {
                if (strings == null) {
                    return Collections.emptyList();
                }
                if (strings.size() == 1) {
                    // special case processing to handle comma-separated values
                    return valueOf(strings.get(0));
                }
                return strings;
            }

            @Override
            public List<String> valueOf(final String[] strings) {
                if (strings == null) {
                    return Collections.emptyList();
                }
                if (strings.length == 1) {
                    // special case processing to handle comma-separated values
                    return valueOf(strings[0]);
                }
                return Arrays.asList(strings);
            }

            @Override
            public List<String> valueOf(final String str) {
                if (str == null) {
                    return Collections.emptyList();
                }
                return _Strings.splitThenStream(str, ",")
                        .collect(Collectors.toList());
            }

            @Override
            public String asString(final List<String> strings) {
                return _NullSafe.stream(strings)
                        .collect(Collectors.joining(","));
            }
        };
    }

    public static Parser<List<List<String>>> forListOfListOfStrings() {
        return new Parser<List<List<String>>>() {

            @Override
            public List<List<String>> valueOf(final List<String> str) {
                if (str == null) {
                    return null;
                }
                if (str.size() == 0) {
                    return null;
                }
                final List<List<String>> listOfLists = _Lists.newArrayList();
                for (final String s : str) {
                    listOfLists.add(PathNode.split(s));
                }
                return listOfLists;
            }

            @Override
            public List<List<String>> valueOf(final String[] str) {
                if (str == null) {
                    return null;
                }
                if (str.length == 0) {
                    return null;
                }
                return valueOf(Arrays.asList(str));
            }

            @Override
            public List<List<String>> valueOf(final String str) {
                if (str == null || str.isEmpty()) {
                    return Collections.emptyList();
                }

                return _Strings.splitThenStream(str, ",")
                        .map(PathNode::split)
                        .collect(Collectors.toList());
            }

            @Override
            public String asString(final List<List<String>> listOfLists) {

                return _NullSafe.stream(listOfLists)
                        .map(listOfStrings->_NullSafe.stream(listOfStrings).collect(Collectors.joining(".")))
                        .collect(Collectors.joining(","));
            }
        };
    }

    public static Parser<String[]> forArrayOfStrings() {
        return new Parser<String[]>() {

            @Override
            public String[] valueOf(final List<String> strings) {
                if (strings == null) {
                    return _Constants.emptyStringArray;
                }
                if (strings.size() == 1) {
                    // special case processing to handle comma-separated values
                    return valueOf(strings.get(0));
                }
                return strings.toArray(new String[] {});
            }

            @Override
            public String[] valueOf(final String[] strings) {
                if (strings == null) {
                    return _Constants.emptyStringArray;
                }
                if (strings.length == 1) {
                    // special case processing to handle comma-separated values
                    return valueOf(strings[0]);
                }
                return strings;
            }

            @Override
            public String[] valueOf(final String str) {
                if (str == null) {
                    return _Constants.emptyStringArray;
                }

                return _Strings.splitThenStream(str, ",")
                        .collect(Collectors.toList())
                        .toArray(_Constants.emptyStringArray);
            }

            @Override
            public String asString(final String[] strings) {
                return _NullSafe.stream(strings)
                        .collect(Collectors.joining(","));
            }
        };
    }

    public static Parser<List<MediaType>> forListOfJaxRsMediaTypes() {
        return new Parser<List<MediaType>>() {

            @Override
            public List<MediaType> valueOf(final String str) {
                if (str == null) {
                    return Collections.emptyList();
                }

                return _Strings.splitThenStream(str, ",")
                        .map(MediaType::valueOf)
                        .collect(Collectors.toList());
            }

            @Override
            public String asString(final List<MediaType> listOfMediaTypes) {

                return _NullSafe.stream(listOfMediaTypes)
                        .map(MediaType::toString)
                        .collect(Collectors.joining(","));
            }
        };
    }

    public static Parser<String> forETag() {
        return new Parser<String>(){

            @Override
            public String valueOf(final String str) {
                return null;
            }

            @Override
            public String asString(final String t) {
                return null;
            }};
    }

}
