/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.gatein.api.management;

import org.apache.commons.lang.LocaleUtils;
import org.gatein.api.Portal;
import org.gatein.api.PortalRequest;
import org.gatein.api.common.Pagination;
import org.gatein.api.common.i18n.Localized;
import org.gatein.api.common.i18n.LocalizedString;
import org.gatein.api.internal.StringJoiner;
import org.gatein.api.navigation.NodePath;
import org.gatein.api.page.Page;
import org.gatein.api.page.PageId;
import org.gatein.api.security.Membership;
import org.gatein.api.security.Permission;
import org.gatein.api.security.User;
import org.gatein.api.site.Site;
import org.gatein.api.site.SiteId;
import org.gatein.management.api.exceptions.InvalidDataException;
import org.gatein.management.api.exceptions.NotAuthorizedException;
import org.gatein.management.api.exceptions.ResourceExistsException;
import org.gatein.management.api.exceptions.ResourceNotFoundException;
import org.gatein.management.api.model.Model;
import org.gatein.management.api.model.ModelBoolean;
import org.gatein.management.api.model.ModelList;
import org.gatein.management.api.model.ModelNumber;
import org.gatein.management.api.model.ModelObject;
import org.gatein.management.api.model.ModelReference;
import org.gatein.management.api.model.ModelString;
import org.gatein.management.api.model.ModelValue;
import org.gatein.management.api.operation.OperationContext;

import javax.xml.bind.DatatypeConverter;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * @author <a href="mailto:nscavell@redhat.com">Nick Scavelli</a>
 */
class Utils {
    private Utils() {
    }

    public static void verifyAccess(Site site, OperationContext context) throws NotAuthorizedException {
        if (!hasPermission(site.getAccessPermission())) {
            throw new NotAuthorizedException(context.getUser(), context.getOperationName());
        }
    }

    public static void verifyAccess(Page page, OperationContext context) throws NotAuthorizedException {
        if (!hasPermission(page.getAccessPermission())) {
            throw new NotAuthorizedException(context.getUser(), context.getOperationName());
        }
    }

    public static boolean hasPermission(Permission permission) {
        PortalRequest request = PortalRequest.getInstance();
        Portal portal = request.getPortal();
        User user = request.getUser();

        return portal.hasPermission(user, permission);
    }

    public static void populate(String fieldName, Locale locale, ModelObject model) {
        if (locale == null) {
            model.set(fieldName, (String) null);
            return;
        }

        String localeString = locale.getLanguage();
        if (localeString == null) {
            throw new RuntimeException("Language was null for locale " + locale);
        }
        String country = locale.getCountry();
        if (country != null && country.length() > 0) {
            localeString += "-" + country.toUpperCase();
        }

        model.set(fieldName, localeString);
    }

    public static void populate(String fieldName, LocalizedString string, ModelObject model) {
        if (string == null)
            return;

        ModelList list = model.get(fieldName, ModelList.class);
        if (string.isLocalized()) {
            for (Localized.Value<String> value : string.getLocalizedValues()) {
                ModelObject localizedModel = list.add().asValue(ModelObject.class);
                localizedModel.set("value", value.getValue());
                populate("lang", value.getLocale(), localizedModel);
            }
        } else {
            list.add().asValue(ModelObject.class).set("value", string.getValue());
        }
    }

    public static void populate(String fieldName, Permission permission, ModelObject model) {
        if (permission != null) {
            ModelList list = model.get(fieldName, ModelList.class);
            if (permission.isAccessibleToEveryone()) {
                list.add("Everyone");
            }

            for (Membership membership : permission.getMemberships()) {
                list.add(membership.toString());
            }
        }
    }

    public static void set(String name, Object value, ModelObject model) {
        String s = (value == null) ? null : value.toString();
        model.set(name, s);
    }

    public static void set(String name, Date value, ModelObject model) {
        String s = null;
        if (value != null) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(value);
            s = DatatypeConverter.printDateTime(cal);
        }

        set(name, s, model);
    }

    public static <T extends ModelValue> T get(ModelObject modelObject, Class<T> type, String...names) {
        Model model = modelObject.get(names);
        try {
            return model.asValue(type);
        } catch (IllegalArgumentException e) {
            ModelValue.ModelValueType expected;
            if (type == ModelString.class) {
                expected = ModelValue.ModelValueType.STRING;
            } else if (type == ModelNumber.class) {
                expected = ModelValue.ModelValueType.NUMBER;
            } else if (type == ModelBoolean.class) {
                expected = ModelValue.ModelValueType.BOOLEAN;
            } else if (type == ModelList.class) {
                expected = ModelValue.ModelValueType.LIST;
            } else if (type == ModelObject.class) {
                expected = ModelValue.ModelValueType.OBJECT;
            } else if (type == ModelReference.class) {
                expected = ModelValue.ModelValueType.REFERENCE;
            } else {
                expected = ModelValue.ModelValueType.UNDEFINED;
            }
            throw invalidType(model, expected, names);
        }
    }

    public static String nonNullString(ModelObject model, String... names) {
        ModelString string = get(model, ModelString.class, names);
        if (!string.isDefined()) {
            throw invalidValue(null, names);
        }
        String value = string.getValue();
        if (value == null) {
            throw invalidValue(null, names);
        }

        return value;
    }

    public static Date getDate(ModelObject model, String...names) {
        String string = get(model, ModelString.class, names).getValue();
        if (string != null) {
            try {
                return DatatypeConverter.parseDateTime(string).getTime();
            } catch (IllegalArgumentException e) {
                throw invalidValue(string, names);
            }
        }

        return null;
    }

    public static Locale getLocale(ModelObject model, String...names) {
        String string = get(model, ModelString.class, names).getValue();
        if (string != null) {
            try {
                char[] lang = string.toCharArray();
                if (lang.length > 2 && lang[2] == '-') {
                    lang[2] = '_';
                }
                return LocaleUtils.toLocale(new String(lang));
            } catch (IllegalArgumentException e) {
                throw invalidValue(string, names);
            }
        }

        return null;
    }

    public static Permission getPermission(ModelObject model, boolean allowNull, String...names) {
        ModelList permissionsModel = get(model, ModelList.class, names);
        if (!allowNull && !permissionsModel.isDefined()) {
            throw invalidValue(null, names);
        }
        Permission permission = null;
        for (int i=0; i<permissionsModel.size(); i++) {
            ModelValue mv = permissionsModel.get(i);
            String field = resolveField(names) + "[" + i + "]"; // Used for error reporting
            if (mv.getValueType() != ModelValue.ModelValueType.STRING) {
                throw invalidType(mv, ModelValue.ModelValueType.STRING, field);
            }
            String perm = mv.asValue(ModelString.class).getValue();
            if (perm == null) {
                throw requiredFieldWhen("permissions are defined", field);
            }

            if (perm.equals("Everyone")) {
                if (permission != null) {
                    throw invalidData("Only one value is allowed when 'Everyone' is defined for %s", field);
                }
                permission = Permission.everyone();
            } else {
                if (permission != null && permission.isAccessibleToEveryone()) {
                    throw invalidData("Only one value is allowed when 'Everyone' is defined for %s", field);
                } else if (permission != null) {
                    permission.addMembership(Membership.fromString(perm));
                } else {
                    permission = new Permission(Membership.fromString(perm));
                }
            }
        }

        return permission;
    }

    public static Pagination getPagination(String offsetParam, String limitParam, Pagination pagination) {
        if (offsetParam != null) {
            try {
                int offset = Integer.parseInt(offsetParam);
                pagination = new Pagination(offset, pagination.getLimit());
            } catch (NumberFormatException nfe) {
                throw invalidRequestParameter("offset", offsetParam);
            }
        }
        if (limitParam != null) {
            try {
                int limit = Integer.parseInt(limitParam);
                pagination = new Pagination(pagination.getOffset(), limit);
            } catch (NumberFormatException nfe) {
                throw invalidRequestParameter("limit", limitParam);
            }
        }

        return pagination;
    }

    public static InvalidDataException invalidRequestParameter(String name, String value) {
        throw new InvalidDataException("Invalid value '" + value + "' for request parameter " + name);
    }

    public static InvalidDataException invalidValue(String value, String...names) {
        return invalidData("Invalid value '" + value + "' for %s", names);
    }

    public static InvalidDataException invalidType(ModelValue value, ModelValue.ModelValueType type, String...names) {
        return invalidData("Invalid value type " + value.getValueType() + " for %s. Was expecting " + type, names);
    }

    public static InvalidDataException requiredField(String...names) {
        return invalidData("%s is required", names);
    }

    public static InvalidDataException requiredFieldWhen(String when, String...names) {
        return invalidData("%s is required when " + when, names);
    }

    public static InvalidDataException invalidData(String format, String... names) {
        if (names == null) {
            throw new InvalidDataException(format);
        }
        return new InvalidDataException(String.format(format, resolveField(names)));
    }

    public static String resolveField(String...names) {
        return StringJoiner.joiner(".").join(names);
    }

    public static ResourceExistsException alreadyExists(String message, SiteId id) {
        return new ResourceExistsException(message + ". Site " + id + " already exists.");
    }

    public static ResourceExistsException alreadyExists(String message, PageId id) {
        return new ResourceExistsException(message + ". Page " + id.getPageName() + " already exists for site " + id.getSiteId());
    }

    public static ResourceExistsException alreadyExists(String message, SiteId id, NodePath nodePath) {
        return new ResourceExistsException(message + ". Node " + nodePath + " already exists for site " + id);
    }

    public static ResourceNotFoundException notFound(String message, SiteId id) {
        return new ResourceNotFoundException(message + ". Site " + id + " does not exist.");
    }

    public static ResourceNotFoundException notFound(String message, PageId id) {
        return new ResourceNotFoundException(message + ". Page " + id.getPageName() + " does not exist for site " + id.getSiteId());
    }

    public static ResourceNotFoundException notFound(String message, SiteId id, NodePath nodePath) {
        return new ResourceNotFoundException(message + ". Node " + nodePath + " does not exist for site " + id);
    }
}
