package org.openstack4j.openstack.networking.domain;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.fasterxml.jackson.annotation.*;
import org.openstack4j.util.ToStringHelper;
import org.openstack4j.model.common.builder.ResourceBuilder;
import org.openstack4j.model.network.*;
import org.openstack4j.model.network.builder.SubnetBuilder;
import org.openstack4j.openstack.common.ListResult;

/**
 * A Subnet is a network with Pools and network based settings
 *
 * @author Jeremy Unruh
 */
@JsonRootName("subnet")
@JsonIgnoreProperties(ignoreUnknown = true)
public class NeutronSubnet implements Subnet {

    private static final long serialVersionUID = 1L;

    private String id;
    private String name;
    @JsonProperty("enable_dhcp")
    private boolean enableDHCP;
    @JsonProperty("network_id")
    private String networkId;
    @JsonProperty("tenant_id")
    private String tenantId;
    @JsonProperty("dns_nameservers")
    private List<String> dnsNames;
    @JsonProperty("allocation_pools")
    private List<NeutronPool> pools;
    @JsonProperty("host_routes")
    private List<NeutronHostRoute> hostRoutes;
    @JsonProperty("ip_version")
    private IPVersionType ipVersion;
    @JsonProperty("gateway_ip")
    private String gateway;
    private String cidr;
    @JsonProperty("ipv6_address_mode")
    private Ipv6AddressMode ipv6AddressMode;
    @JsonProperty("ipv6_ra_mode")
    private Ipv6RaMode ipv6RaMode;
    @JsonProperty("created_at")
    private Date createdTime;
    @JsonProperty("updated_at")
    private Date updatedTime;

    public NeutronSubnet() {
    }

    public NeutronSubnet(String id, String name, boolean enableDHCP, String networkId, String tenantId, List<String> dnsNames,
            List<NeutronPool> pools, List<NeutronHostRoute> hostRoutes, IPVersionType ipVersion,
            String gateway, String cidr, Ipv6AddressMode ipv6AddressMode, Ipv6RaMode ipv6RaMode) {
        this.id = id;
        this.name = name;
        this.enableDHCP = enableDHCP;
        this.networkId = networkId;
        this.tenantId = tenantId;
        this.dnsNames = dnsNames;
        this.pools = pools;
        this.hostRoutes = hostRoutes;
        this.ipVersion = ipVersion;
        this.gateway = gateway;
        this.cidr = cidr;
        this.ipv6AddressMode = ipv6AddressMode;
        this.ipv6RaMode = ipv6RaMode;
    }

    public static SubnetBuilder builder() {
        return new SubnetConcreteBuilder();
    }

    @Override
    public SubnetBuilder toBuilder() {
        return new SubnetConcreteBuilder(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setId(String id) {
        this.id = id;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setName(String name) {
        this.name = name;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsonIgnore
    public boolean isDHCPEnabled() {
        return enableDHCP;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getNetworkId() {
        return networkId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTenantId() {
        return tenantId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTenantId(String tenantId) {
        this.tenantId = tenantId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> getDnsNames() {
        return dnsNames;
    }

    /**
     * {@inheritDoc}
     */
    @JsonIgnore
    @Override
    public List<? extends Pool> getAllocationPools() {
        return pools;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<? extends HostRoute> getHostRoutes() {
        return hostRoutes;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPVersionType getIpVersion() {
        return ipVersion;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getGateway() {
        return gateway;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getCidr() {
        return cidr;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Ipv6AddressMode getIpv6AddressMode() {
        return ipv6AddressMode;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Ipv6RaMode getIpv6RaMode() {
        return ipv6RaMode;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Date getCreatedTime() {
        return createdTime;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Date getUpdatedTime() {
        return updatedTime;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return new ToStringHelper(this)
                .add("id", id).add("name", name).add("enableDHCP", enableDHCP).add("network-id", networkId)
                .add("tenant_id", tenantId).add("dns_nameservers", dnsNames).add("allocation_pools", pools)
                .add("host_routes", hostRoutes).add("ip_version", ipVersion).add("gateway_ip", gateway).add("cidr", cidr)
                .add("ipv6AddressMode", ipv6AddressMode).add("ipv6RaMode", ipv6RaMode)
                .add("created_at", createdTime).add("updated_at", updatedTime)
                .toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return java.util.Objects.hash(id, name, enableDHCP, networkId,
                tenantId, dnsNames, pools, hostRoutes, ipVersion, gateway,
                cidr, ipv6AddressMode, ipv6RaMode, createdTime, updatedTime);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj instanceof NeutronSubnet) {
            NeutronSubnet that = (NeutronSubnet) obj;
            if (java.util.Objects.equals(id, that.id) &&
                    java.util.Objects.equals(name, that.name) &&
                    java.util.Objects.equals(enableDHCP, that.enableDHCP) &&
                    java.util.Objects.equals(networkId, that.networkId) &&
                    java.util.Objects.equals(tenantId, that.tenantId) &&
                    java.util.Objects.equals(dnsNames, that.dnsNames) &&
                    java.util.Objects.equals(pools, that.pools) &&
                    java.util.Objects.equals(hostRoutes, that.hostRoutes) &&
                    java.util.Objects.equals(ipVersion, that.ipVersion) &&
                    java.util.Objects.equals(gateway, that.gateway) &&
                    java.util.Objects.equals(cidr, that.cidr) &&
                    java.util.Objects.equals(ipv6AddressMode, that.ipv6AddressMode) &&
                    java.util.Objects.equals(ipv6RaMode, that.ipv6RaMode) &&
                    java.util.Objects.equals(createdTime, that.createdTime) &&
                    java.util.Objects.equals(updatedTime, that.updatedTime)) {
                return true;
            }
        }
        return false;
    }

    @JsonRootName("subnet")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class NeutronSubnetNoGateway extends NeutronSubnet {

        @JsonProperty("gateway_ip")
        @JsonInclude
        private String gateway;

        public NeutronSubnetNoGateway(String id, String name, boolean enableDHCP, String networkId, String tenantId,
                List<String> dnsNames, List<NeutronPool> pools, List<NeutronHostRoute> hostRoutes,
                IPVersionType ipVersion, String cidr, Ipv6AddressMode ipv6AddressMode, Ipv6RaMode ipv6RaMode) {
            super(id, name, enableDHCP, networkId, tenantId, dnsNames, pools, hostRoutes, ipVersion, null, cidr, ipv6AddressMode, ipv6RaMode);
            this.gateway = null;
        }
    }

    public static class Subnets extends ListResult<NeutronSubnet> {

        private static final long serialVersionUID = 1L;

        @JsonProperty("subnets")
        private List<NeutronSubnet> subnets;

        @Override
        protected List<NeutronSubnet> value() {
            return subnets;
        }
    }

    public static class SubnetConcreteBuilder extends ResourceBuilder<Subnet, SubnetConcreteBuilder> implements SubnetBuilder {

        private NeutronSubnet m;
        private boolean isNoGateway = false;

        SubnetConcreteBuilder() {
            this(new NeutronSubnet());
        }

        SubnetConcreteBuilder(NeutronSubnet m) {
            this.m = m;
        }

        @Override
        public SubnetBuilder networkId(String networkId) {
            m.networkId = networkId;
            return this;
        }

        @Override
        public SubnetBuilder network(Network network) {
            m.networkId = network.getId();
            return this;
        }

        @Override
        public SubnetBuilder ipVersion(IPVersionType ipVersion) {
            m.ipVersion = ipVersion;
            return this;
        }

        @Override
        public SubnetBuilder cidr(String cidr) {
            m.cidr = cidr;
            return this;
        }

        @Override
        public SubnetBuilder gateway(String gateway) {
            m.gateway = gateway;
            return this;
        }

        @Override
        public SubnetBuilder addPool(String start, String end) {
            if (m.pools == null)
                m.pools = new ArrayList<>();
            m.pools.add(new NeutronPool(start, end));
            return this;
        }

        @Override
        public SubnetBuilder enableDHCP(boolean enable) {
            m.enableDHCP = enable;
            return this;
        }

        @Override
        public SubnetBuilder noGateway() {
            isNoGateway = true;
            return this;
        }

        @Override
        public SubnetBuilder ipv6AddressMode(Ipv6AddressMode ipv6AddressMode) {
            m.ipv6AddressMode = ipv6AddressMode;
            return this;
        }

        @Override
        public SubnetBuilder ipv6RaMode(Ipv6RaMode ipv6RaMode) {
            m.ipv6RaMode = ipv6RaMode;
            return this;
        }

        @Override
        public Subnet build() {
            if (isNoGateway) {
                return new NeutronSubnetNoGateway(m.id, m.name, m.enableDHCP, m.networkId,
                        m.tenantId, m.dnsNames, m.pools, m.hostRoutes, m.ipVersion, m.cidr, m.ipv6AddressMode, m.ipv6RaMode);
            }
            return m;
        }

        @Override
        public SubnetBuilder from(Subnet in) {
            return this;
        }

        @Override
        protected Subnet reference() {
            return m;
        }

        @Override
        public SubnetBuilder addDNSNameServer(String host) {
            if (host == null || host.isEmpty())
                return this;

            if (m.dnsNames == null)
                m.dnsNames = new ArrayList<>();

            m.dnsNames.add(host);
            return this;
        }

        @Override
        public SubnetBuilder addHostRoute(String destination, String nexthop) {
            if (nexthop == null || destination == null) throw new IllegalArgumentException("NextHop and Destination must have a value");
            if (m.hostRoutes == null)
                m.hostRoutes = new ArrayList<>();

            m.hostRoutes.add(new NeutronHostRoute(destination, nexthop));
            return this;
        }
    }
}
